##// END OF EJS Templates
Merge pull request #4085 from jdfreder/fix_sphinx_data_win_again...
Thomas Kluyver -
r12411:5e4d9f3b merge
parent child Browse files
Show More
@@ -1,519 +1,519
1 """This module defines Exporter, a highly configurable converter
1 """This module defines Exporter, a highly configurable converter
2 that uses Jinja2 to export notebook files into different formats.
2 that uses Jinja2 to export notebook files into different formats.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import io
20 import io
21 import os
21 import os
22 import inspect
22 import inspect
23 import copy
23 import copy
24 import collections
24 import collections
25 import datetime
25 import datetime
26
26
27 # other libs/dependencies
27 # other libs/dependencies
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
29
29
30 # IPython imports
30 # IPython imports
31 from IPython.config.configurable import LoggingConfigurable
31 from IPython.config.configurable import LoggingConfigurable
32 from IPython.config import Config
32 from IPython.config import Config
33 from IPython.nbformat import current as nbformat
33 from IPython.nbformat import current as nbformat
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict, Any
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict, Any
35 from IPython.utils.importstring import import_item
35 from IPython.utils.importstring import import_item
36 from IPython.utils.text import indent
36 from IPython.utils import text
37 from IPython.utils import py3compat
37 from IPython.utils import py3compat
38
38
39 from IPython.nbconvert import preprocessors as nbpreprocessors
39 from IPython.nbconvert import preprocessors as nbpreprocessors
40 from IPython.nbconvert import filters
40 from IPython.nbconvert import filters
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Globals and constants
43 # Globals and constants
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 #Jinja2 extensions to load.
46 #Jinja2 extensions to load.
47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
48
48
49 default_filters = {
49 default_filters = {
50 'indent': indent,
50 'indent': text.indent,
51 'markdown2html': filters.markdown2html,
51 'markdown2html': filters.markdown2html,
52 'ansi2html': filters.ansi2html,
52 'ansi2html': filters.ansi2html,
53 'filter_data_type': filters.DataTypeFilter,
53 'filter_data_type': filters.DataTypeFilter,
54 'get_lines': filters.get_lines,
54 'get_lines': filters.get_lines,
55 'highlight2html': filters.highlight2html,
55 'highlight2html': filters.highlight2html,
56 'highlight2latex': filters.highlight2latex,
56 'highlight2latex': filters.highlight2latex,
57 'ipython2python': filters.ipython2python,
57 'ipython2python': filters.ipython2python,
58 'posix_path': filters.posix_path,
58 'posix_path': filters.posix_path,
59 'markdown2latex': filters.markdown2latex,
59 'markdown2latex': filters.markdown2latex,
60 'markdown2rst': filters.markdown2rst,
60 'markdown2rst': filters.markdown2rst,
61 'comment_lines': filters.comment_lines,
61 'comment_lines': filters.comment_lines,
62 'strip_ansi': filters.strip_ansi,
62 'strip_ansi': filters.strip_ansi,
63 'strip_dollars': filters.strip_dollars,
63 'strip_dollars': filters.strip_dollars,
64 'strip_files_prefix': filters.strip_files_prefix,
64 'strip_files_prefix': filters.strip_files_prefix,
65 'html2text' : filters.html2text,
65 'html2text' : filters.html2text,
66 'add_anchor': filters.add_anchor,
66 'add_anchor': filters.add_anchor,
67 'ansi2latex': filters.ansi2latex,
67 'ansi2latex': filters.ansi2latex,
68 'strip_math_space': filters.strip_math_space,
68 'strip_math_space': filters.strip_math_space,
69 'wrap_text': filters.wrap_text,
69 'wrap_text': filters.wrap_text,
70 'escape_latex': filters.escape_latex,
70 'escape_latex': filters.escape_latex,
71 'citation2latex': filters.citation2latex
71 'citation2latex': filters.citation2latex
72 }
72 }
73
73
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75 # Class
75 # Class
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77
77
78 class ResourcesDict(collections.defaultdict):
78 class ResourcesDict(collections.defaultdict):
79 def __missing__(self, key):
79 def __missing__(self, key):
80 return ''
80 return ''
81
81
82
82
83 class Exporter(LoggingConfigurable):
83 class Exporter(LoggingConfigurable):
84 """
84 """
85 Exports notebooks into other file formats. Uses Jinja 2 templating engine
85 Exports notebooks into other file formats. Uses Jinja 2 templating engine
86 to output new formats. Inherit from this class if you are creating a new
86 to output new formats. Inherit from this class if you are creating a new
87 template type along with new filters/preprocessors. If the filters/
87 template type along with new filters/preprocessors. If the filters/
88 preprocessors provided by default suffice, there is no need to inherit from
88 preprocessors provided by default suffice, there is no need to inherit from
89 this class. Instead, override the template_file and file_extension
89 this class. Instead, override the template_file and file_extension
90 traits via a config file.
90 traits via a config file.
91
91
92 {filters}
92 {filters}
93 """
93 """
94
94
95 # finish the docstring
95 # finish the docstring
96 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
96 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
97
97
98
98
99 template_file = Unicode(u'default',
99 template_file = Unicode(u'default',
100 config=True,
100 config=True,
101 help="Name of the template file to use")
101 help="Name of the template file to use")
102 def _template_file_changed(self, name, old, new):
102 def _template_file_changed(self, name, old, new):
103 if new=='default':
103 if new=='default':
104 self.template_file = self.default_template
104 self.template_file = self.default_template
105 else:
105 else:
106 self.template_file = new
106 self.template_file = new
107 self.template = None
107 self.template = None
108 self._load_template()
108 self._load_template()
109
109
110 default_template = Unicode(u'')
110 default_template = Unicode(u'')
111 template = Any()
111 template = Any()
112 environment = Any()
112 environment = Any()
113
113
114 file_extension = Unicode(
114 file_extension = Unicode(
115 'txt', config=True,
115 'txt', config=True,
116 help="Extension of the file that should be written to disk"
116 help="Extension of the file that should be written to disk"
117 )
117 )
118
118
119 template_path = List(['.'], config=True)
119 template_path = List(['.'], config=True)
120 def _template_path_changed(self, name, old, new):
120 def _template_path_changed(self, name, old, new):
121 self._load_template()
121 self._load_template()
122
122
123 default_template_path = Unicode(
123 default_template_path = Unicode(
124 os.path.join("..", "templates"),
124 os.path.join("..", "templates"),
125 help="Path where the template files are located.")
125 help="Path where the template files are located.")
126
126
127 template_skeleton_path = Unicode(
127 template_skeleton_path = Unicode(
128 os.path.join("..", "templates", "skeleton"),
128 os.path.join("..", "templates", "skeleton"),
129 help="Path where the template skeleton files are located.")
129 help="Path where the template skeleton files are located.")
130
130
131 #Jinja block definitions
131 #Jinja block definitions
132 jinja_comment_block_start = Unicode("", config=True)
132 jinja_comment_block_start = Unicode("", config=True)
133 jinja_comment_block_end = Unicode("", config=True)
133 jinja_comment_block_end = Unicode("", config=True)
134 jinja_variable_block_start = Unicode("", config=True)
134 jinja_variable_block_start = Unicode("", config=True)
135 jinja_variable_block_end = Unicode("", config=True)
135 jinja_variable_block_end = Unicode("", config=True)
136 jinja_logic_block_start = Unicode("", config=True)
136 jinja_logic_block_start = Unicode("", config=True)
137 jinja_logic_block_end = Unicode("", config=True)
137 jinja_logic_block_end = Unicode("", config=True)
138
138
139 #Extension that the template files use.
139 #Extension that the template files use.
140 template_extension = Unicode(".tpl", config=True)
140 template_extension = Unicode(".tpl", config=True)
141
141
142 #Configurability, allows the user to easily add filters and preprocessors.
142 #Configurability, allows the user to easily add filters and preprocessors.
143 preprocessors = List(config=True,
143 preprocessors = List(config=True,
144 help="""List of preprocessors, by name or namespace, to enable.""")
144 help="""List of preprocessors, by name or namespace, to enable.""")
145
145
146 filters = Dict(config=True,
146 filters = Dict(config=True,
147 help="""Dictionary of filters, by name and namespace, to add to the Jinja
147 help="""Dictionary of filters, by name and namespace, to add to the Jinja
148 environment.""")
148 environment.""")
149
149
150 default_preprocessors = List([nbpreprocessors.coalesce_streams,
150 default_preprocessors = List([nbpreprocessors.coalesce_streams,
151 nbpreprocessors.SVG2PDFPreprocessor,
151 nbpreprocessors.SVG2PDFPreprocessor,
152 nbpreprocessors.ExtractOutputPreprocessor,
152 nbpreprocessors.ExtractOutputPreprocessor,
153 nbpreprocessors.CSSHTMLHeaderPreprocessor,
153 nbpreprocessors.CSSHTMLHeaderPreprocessor,
154 nbpreprocessors.RevealHelpPreprocessor,
154 nbpreprocessors.RevealHelpPreprocessor,
155 nbpreprocessors.LatexPreprocessor,
155 nbpreprocessors.LatexPreprocessor,
156 nbpreprocessors.SphinxPreprocessor],
156 nbpreprocessors.SphinxPreprocessor],
157 config=True,
157 config=True,
158 help="""List of preprocessors available by default, by name, namespace,
158 help="""List of preprocessors available by default, by name, namespace,
159 instance, or type.""")
159 instance, or type.""")
160
160
161
161
162 def __init__(self, config=None, extra_loaders=None, **kw):
162 def __init__(self, config=None, extra_loaders=None, **kw):
163 """
163 """
164 Public constructor
164 Public constructor
165
165
166 Parameters
166 Parameters
167 ----------
167 ----------
168 config : config
168 config : config
169 User configuration instance.
169 User configuration instance.
170 extra_loaders : list[of Jinja Loaders]
170 extra_loaders : list[of Jinja Loaders]
171 ordered list of Jinja loader to find templates. Will be tried in order
171 ordered list of Jinja loader to find templates. Will be tried in order
172 before the default FileSystem ones.
172 before the default FileSystem ones.
173 template : str (optional, kw arg)
173 template : str (optional, kw arg)
174 Template to use when exporting.
174 Template to use when exporting.
175 """
175 """
176 if not config:
176 if not config:
177 config = self.default_config
177 config = self.default_config
178
178
179 super(Exporter, self).__init__(config=config, **kw)
179 super(Exporter, self).__init__(config=config, **kw)
180
180
181 #Init
181 #Init
182 self._init_template()
182 self._init_template()
183 self._init_environment(extra_loaders=extra_loaders)
183 self._init_environment(extra_loaders=extra_loaders)
184 self._init_preprocessors()
184 self._init_preprocessors()
185 self._init_filters()
185 self._init_filters()
186
186
187
187
188 @property
188 @property
189 def default_config(self):
189 def default_config(self):
190 return Config()
190 return Config()
191
191
192 def _config_changed(self, name, old, new):
192 def _config_changed(self, name, old, new):
193 """When setting config, make sure to start with our default_config"""
193 """When setting config, make sure to start with our default_config"""
194 c = self.default_config
194 c = self.default_config
195 if new:
195 if new:
196 c.merge(new)
196 c.merge(new)
197 if c != old:
197 if c != old:
198 self.config = c
198 self.config = c
199 super(Exporter, self)._config_changed(name, old, c)
199 super(Exporter, self)._config_changed(name, old, c)
200
200
201
201
202 def _load_template(self):
202 def _load_template(self):
203 """Load the Jinja template object from the template file
203 """Load the Jinja template object from the template file
204
204
205 This is a no-op if the template attribute is already defined,
205 This is a no-op if the template attribute is already defined,
206 or the Jinja environment is not setup yet.
206 or the Jinja environment is not setup yet.
207
207
208 This is triggered by various trait changes that would change the template.
208 This is triggered by various trait changes that would change the template.
209 """
209 """
210 if self.template is not None:
210 if self.template is not None:
211 return
211 return
212 # called too early, do nothing
212 # called too early, do nothing
213 if self.environment is None:
213 if self.environment is None:
214 return
214 return
215 # Try different template names during conversion. First try to load the
215 # Try different template names during conversion. First try to load the
216 # template by name with extension added, then try loading the template
216 # template by name with extension added, then try loading the template
217 # as if the name is explicitly specified, then try the name as a
217 # as if the name is explicitly specified, then try the name as a
218 # 'flavor', and lastly just try to load the template by module name.
218 # 'flavor', and lastly just try to load the template by module name.
219 module_name = self.__module__.rsplit('.', 1)[-1]
219 module_name = self.__module__.rsplit('.', 1)[-1]
220 try_names = []
220 try_names = []
221 if self.template_file:
221 if self.template_file:
222 try_names.extend([
222 try_names.extend([
223 self.template_file + self.template_extension,
223 self.template_file + self.template_extension,
224 self.template_file,
224 self.template_file,
225 module_name + '_' + self.template_file + self.template_extension,
225 module_name + '_' + self.template_file + self.template_extension,
226 ])
226 ])
227 try_names.append(module_name + self.template_extension)
227 try_names.append(module_name + self.template_extension)
228 for try_name in try_names:
228 for try_name in try_names:
229 self.log.debug("Attempting to load template %s", try_name)
229 self.log.debug("Attempting to load template %s", try_name)
230 try:
230 try:
231 self.template = self.environment.get_template(try_name)
231 self.template = self.environment.get_template(try_name)
232 except (TemplateNotFound, IOError):
232 except (TemplateNotFound, IOError):
233 pass
233 pass
234 except Exception as e:
234 except Exception as e:
235 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
235 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
236 else:
236 else:
237 self.log.info("Loaded template %s", try_name)
237 self.log.info("Loaded template %s", try_name)
238 break
238 break
239
239
240 def from_notebook_node(self, nb, resources=None, **kw):
240 def from_notebook_node(self, nb, resources=None, **kw):
241 """
241 """
242 Convert a notebook from a notebook node instance.
242 Convert a notebook from a notebook node instance.
243
243
244 Parameters
244 Parameters
245 ----------
245 ----------
246 nb : Notebook node
246 nb : Notebook node
247 resources : dict (**kw)
247 resources : dict (**kw)
248 of additional resources that can be accessed read/write by
248 of additional resources that can be accessed read/write by
249 preprocessors and filters.
249 preprocessors and filters.
250 """
250 """
251 nb_copy = copy.deepcopy(nb)
251 nb_copy = copy.deepcopy(nb)
252 resources = self._init_resources(resources)
252 resources = self._init_resources(resources)
253
253
254 # Preprocess
254 # Preprocess
255 nb_copy, resources = self._preprocess(nb_copy, resources)
255 nb_copy, resources = self._preprocess(nb_copy, resources)
256
256
257 self._load_template()
257 self._load_template()
258
258
259 if self.template is not None:
259 if self.template is not None:
260 output = self.template.render(nb=nb_copy, resources=resources)
260 output = self.template.render(nb=nb_copy, resources=resources)
261 else:
261 else:
262 raise IOError('template file "%s" could not be found' % self.template_file)
262 raise IOError('template file "%s" could not be found' % self.template_file)
263 return output, resources
263 return output, resources
264
264
265
265
266 def from_filename(self, filename, resources=None, **kw):
266 def from_filename(self, filename, resources=None, **kw):
267 """
267 """
268 Convert a notebook from a notebook file.
268 Convert a notebook from a notebook file.
269
269
270 Parameters
270 Parameters
271 ----------
271 ----------
272 filename : str
272 filename : str
273 Full filename of the notebook file to open and convert.
273 Full filename of the notebook file to open and convert.
274 """
274 """
275
275
276 #Pull the metadata from the filesystem.
276 #Pull the metadata from the filesystem.
277 if resources is None:
277 if resources is None:
278 resources = ResourcesDict()
278 resources = ResourcesDict()
279 if not 'metadata' in resources or resources['metadata'] == '':
279 if not 'metadata' in resources or resources['metadata'] == '':
280 resources['metadata'] = ResourcesDict()
280 resources['metadata'] = ResourcesDict()
281 basename = os.path.basename(filename)
281 basename = os.path.basename(filename)
282 notebook_name = basename[:basename.rfind('.')]
282 notebook_name = basename[:basename.rfind('.')]
283 resources['metadata']['name'] = notebook_name
283 resources['metadata']['name'] = notebook_name
284
284
285 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
285 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
286 resources['metadata']['modified_date'] = modified_date.strftime("%B %d, %Y")
286 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
287
287
288 with io.open(filename) as f:
288 with io.open(filename) as f:
289 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
289 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
290
290
291
291
292 def from_file(self, file_stream, resources=None, **kw):
292 def from_file(self, file_stream, resources=None, **kw):
293 """
293 """
294 Convert a notebook from a notebook file.
294 Convert a notebook from a notebook file.
295
295
296 Parameters
296 Parameters
297 ----------
297 ----------
298 file_stream : file-like object
298 file_stream : file-like object
299 Notebook file-like object to convert.
299 Notebook file-like object to convert.
300 """
300 """
301 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
301 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
302
302
303
303
304 def register_preprocessor(self, preprocessor, enabled=False):
304 def register_preprocessor(self, preprocessor, enabled=False):
305 """
305 """
306 Register a preprocessor.
306 Register a preprocessor.
307 Preprocessors are classes that act upon the notebook before it is
307 Preprocessors are classes that act upon the notebook before it is
308 passed into the Jinja templating engine. Preprocessors are also
308 passed into the Jinja templating engine. Preprocessors are also
309 capable of passing additional information to the Jinja
309 capable of passing additional information to the Jinja
310 templating engine.
310 templating engine.
311
311
312 Parameters
312 Parameters
313 ----------
313 ----------
314 preprocessor : preprocessor
314 preprocessor : preprocessor
315 """
315 """
316 if preprocessor is None:
316 if preprocessor is None:
317 raise TypeError('preprocessor')
317 raise TypeError('preprocessor')
318 isclass = isinstance(preprocessor, type)
318 isclass = isinstance(preprocessor, type)
319 constructed = not isclass
319 constructed = not isclass
320
320
321 #Handle preprocessor's registration based on it's type
321 #Handle preprocessor's registration based on it's type
322 if constructed and isinstance(preprocessor, py3compat.string_types):
322 if constructed and isinstance(preprocessor, py3compat.string_types):
323 #Preprocessor is a string, import the namespace and recursively call
323 #Preprocessor is a string, import the namespace and recursively call
324 #this register_preprocessor method
324 #this register_preprocessor method
325 preprocessor_cls = import_item(preprocessor)
325 preprocessor_cls = import_item(preprocessor)
326 return self.register_preprocessor(preprocessor_cls, enabled)
326 return self.register_preprocessor(preprocessor_cls, enabled)
327
327
328 if constructed and hasattr(preprocessor, '__call__'):
328 if constructed and hasattr(preprocessor, '__call__'):
329 #Preprocessor is a function, no need to construct it.
329 #Preprocessor is a function, no need to construct it.
330 #Register and return the preprocessor.
330 #Register and return the preprocessor.
331 if enabled:
331 if enabled:
332 preprocessor.enabled = True
332 preprocessor.enabled = True
333 self._preprocessors.append(preprocessor)
333 self._preprocessors.append(preprocessor)
334 return preprocessor
334 return preprocessor
335
335
336 elif isclass and isinstance(preprocessor, MetaHasTraits):
336 elif isclass and isinstance(preprocessor, MetaHasTraits):
337 #Preprocessor is configurable. Make sure to pass in new default for
337 #Preprocessor is configurable. Make sure to pass in new default for
338 #the enabled flag if one was specified.
338 #the enabled flag if one was specified.
339 self.register_preprocessor(preprocessor(parent=self), enabled)
339 self.register_preprocessor(preprocessor(parent=self), enabled)
340
340
341 elif isclass:
341 elif isclass:
342 #Preprocessor is not configurable, construct it
342 #Preprocessor is not configurable, construct it
343 self.register_preprocessor(preprocessor(), enabled)
343 self.register_preprocessor(preprocessor(), enabled)
344
344
345 else:
345 else:
346 #Preprocessor is an instance of something without a __call__
346 #Preprocessor is an instance of something without a __call__
347 #attribute.
347 #attribute.
348 raise TypeError('preprocessor')
348 raise TypeError('preprocessor')
349
349
350
350
351 def register_filter(self, name, jinja_filter):
351 def register_filter(self, name, jinja_filter):
352 """
352 """
353 Register a filter.
353 Register a filter.
354 A filter is a function that accepts and acts on one string.
354 A filter is a function that accepts and acts on one string.
355 The filters are accesible within the Jinja templating engine.
355 The filters are accesible within the Jinja templating engine.
356
356
357 Parameters
357 Parameters
358 ----------
358 ----------
359 name : str
359 name : str
360 name to give the filter in the Jinja engine
360 name to give the filter in the Jinja engine
361 filter : filter
361 filter : filter
362 """
362 """
363 if jinja_filter is None:
363 if jinja_filter is None:
364 raise TypeError('filter')
364 raise TypeError('filter')
365 isclass = isinstance(jinja_filter, type)
365 isclass = isinstance(jinja_filter, type)
366 constructed = not isclass
366 constructed = not isclass
367
367
368 #Handle filter's registration based on it's type
368 #Handle filter's registration based on it's type
369 if constructed and isinstance(jinja_filter, py3compat.string_types):
369 if constructed and isinstance(jinja_filter, py3compat.string_types):
370 #filter is a string, import the namespace and recursively call
370 #filter is a string, import the namespace and recursively call
371 #this register_filter method
371 #this register_filter method
372 filter_cls = import_item(jinja_filter)
372 filter_cls = import_item(jinja_filter)
373 return self.register_filter(name, filter_cls)
373 return self.register_filter(name, filter_cls)
374
374
375 if constructed and hasattr(jinja_filter, '__call__'):
375 if constructed and hasattr(jinja_filter, '__call__'):
376 #filter is a function, no need to construct it.
376 #filter is a function, no need to construct it.
377 self.environment.filters[name] = jinja_filter
377 self.environment.filters[name] = jinja_filter
378 return jinja_filter
378 return jinja_filter
379
379
380 elif isclass and isinstance(jinja_filter, MetaHasTraits):
380 elif isclass and isinstance(jinja_filter, MetaHasTraits):
381 #filter is configurable. Make sure to pass in new default for
381 #filter is configurable. Make sure to pass in new default for
382 #the enabled flag if one was specified.
382 #the enabled flag if one was specified.
383 filter_instance = jinja_filter(parent=self)
383 filter_instance = jinja_filter(parent=self)
384 self.register_filter(name, filter_instance )
384 self.register_filter(name, filter_instance )
385
385
386 elif isclass:
386 elif isclass:
387 #filter is not configurable, construct it
387 #filter is not configurable, construct it
388 filter_instance = jinja_filter()
388 filter_instance = jinja_filter()
389 self.register_filter(name, filter_instance)
389 self.register_filter(name, filter_instance)
390
390
391 else:
391 else:
392 #filter is an instance of something without a __call__
392 #filter is an instance of something without a __call__
393 #attribute.
393 #attribute.
394 raise TypeError('filter')
394 raise TypeError('filter')
395
395
396
396
397 def _init_template(self):
397 def _init_template(self):
398 """
398 """
399 Make sure a template name is specified. If one isn't specified, try to
399 Make sure a template name is specified. If one isn't specified, try to
400 build one from the information we know.
400 build one from the information we know.
401 """
401 """
402 self._template_file_changed('template_file', self.template_file, self.template_file)
402 self._template_file_changed('template_file', self.template_file, self.template_file)
403
403
404
404
405 def _init_environment(self, extra_loaders=None):
405 def _init_environment(self, extra_loaders=None):
406 """
406 """
407 Create the Jinja templating environment.
407 Create the Jinja templating environment.
408 """
408 """
409 here = os.path.dirname(os.path.realpath(__file__))
409 here = os.path.dirname(os.path.realpath(__file__))
410 loaders = []
410 loaders = []
411 if extra_loaders:
411 if extra_loaders:
412 loaders.extend(extra_loaders)
412 loaders.extend(extra_loaders)
413
413
414 paths = self.template_path
414 paths = self.template_path
415 paths.extend([os.path.join(here, self.default_template_path),
415 paths.extend([os.path.join(here, self.default_template_path),
416 os.path.join(here, self.template_skeleton_path)])
416 os.path.join(here, self.template_skeleton_path)])
417 loaders.append(FileSystemLoader(paths))
417 loaders.append(FileSystemLoader(paths))
418
418
419 self.environment = Environment(
419 self.environment = Environment(
420 loader= ChoiceLoader(loaders),
420 loader= ChoiceLoader(loaders),
421 extensions=JINJA_EXTENSIONS
421 extensions=JINJA_EXTENSIONS
422 )
422 )
423
423
424 #Set special Jinja2 syntax that will not conflict with latex.
424 #Set special Jinja2 syntax that will not conflict with latex.
425 if self.jinja_logic_block_start:
425 if self.jinja_logic_block_start:
426 self.environment.block_start_string = self.jinja_logic_block_start
426 self.environment.block_start_string = self.jinja_logic_block_start
427 if self.jinja_logic_block_end:
427 if self.jinja_logic_block_end:
428 self.environment.block_end_string = self.jinja_logic_block_end
428 self.environment.block_end_string = self.jinja_logic_block_end
429 if self.jinja_variable_block_start:
429 if self.jinja_variable_block_start:
430 self.environment.variable_start_string = self.jinja_variable_block_start
430 self.environment.variable_start_string = self.jinja_variable_block_start
431 if self.jinja_variable_block_end:
431 if self.jinja_variable_block_end:
432 self.environment.variable_end_string = self.jinja_variable_block_end
432 self.environment.variable_end_string = self.jinja_variable_block_end
433 if self.jinja_comment_block_start:
433 if self.jinja_comment_block_start:
434 self.environment.comment_start_string = self.jinja_comment_block_start
434 self.environment.comment_start_string = self.jinja_comment_block_start
435 if self.jinja_comment_block_end:
435 if self.jinja_comment_block_end:
436 self.environment.comment_end_string = self.jinja_comment_block_end
436 self.environment.comment_end_string = self.jinja_comment_block_end
437
437
438
438
439 def _init_preprocessors(self):
439 def _init_preprocessors(self):
440 """
440 """
441 Register all of the preprocessors needed for this exporter, disabled
441 Register all of the preprocessors needed for this exporter, disabled
442 unless specified explicitly.
442 unless specified explicitly.
443 """
443 """
444 self._preprocessors = []
444 self._preprocessors = []
445
445
446 #Load default preprocessors (not necessarly enabled by default).
446 #Load default preprocessors (not necessarly enabled by default).
447 if self.default_preprocessors:
447 if self.default_preprocessors:
448 for preprocessor in self.default_preprocessors:
448 for preprocessor in self.default_preprocessors:
449 self.register_preprocessor(preprocessor)
449 self.register_preprocessor(preprocessor)
450
450
451 #Load user preprocessors. Enable by default.
451 #Load user preprocessors. Enable by default.
452 if self.preprocessors:
452 if self.preprocessors:
453 for preprocessor in self.preprocessors:
453 for preprocessor in self.preprocessors:
454 self.register_preprocessor(preprocessor, enabled=True)
454 self.register_preprocessor(preprocessor, enabled=True)
455
455
456
456
457 def _init_filters(self):
457 def _init_filters(self):
458 """
458 """
459 Register all of the filters required for the exporter.
459 Register all of the filters required for the exporter.
460 """
460 """
461
461
462 #Add default filters to the Jinja2 environment
462 #Add default filters to the Jinja2 environment
463 for key, value in default_filters.items():
463 for key, value in default_filters.items():
464 self.register_filter(key, value)
464 self.register_filter(key, value)
465
465
466 #Load user filters. Overwrite existing filters if need be.
466 #Load user filters. Overwrite existing filters if need be.
467 if self.filters:
467 if self.filters:
468 for key, user_filter in self.filters.items():
468 for key, user_filter in self.filters.items():
469 self.register_filter(key, user_filter)
469 self.register_filter(key, user_filter)
470
470
471
471
472 def _init_resources(self, resources):
472 def _init_resources(self, resources):
473
473
474 #Make sure the resources dict is of ResourcesDict type.
474 #Make sure the resources dict is of ResourcesDict type.
475 if resources is None:
475 if resources is None:
476 resources = ResourcesDict()
476 resources = ResourcesDict()
477 if not isinstance(resources, ResourcesDict):
477 if not isinstance(resources, ResourcesDict):
478 new_resources = ResourcesDict()
478 new_resources = ResourcesDict()
479 new_resources.update(resources)
479 new_resources.update(resources)
480 resources = new_resources
480 resources = new_resources
481
481
482 #Make sure the metadata extension exists in resources
482 #Make sure the metadata extension exists in resources
483 if 'metadata' in resources:
483 if 'metadata' in resources:
484 if not isinstance(resources['metadata'], ResourcesDict):
484 if not isinstance(resources['metadata'], ResourcesDict):
485 resources['metadata'] = ResourcesDict(resources['metadata'])
485 resources['metadata'] = ResourcesDict(resources['metadata'])
486 else:
486 else:
487 resources['metadata'] = ResourcesDict()
487 resources['metadata'] = ResourcesDict()
488 if not resources['metadata']['name']:
488 if not resources['metadata']['name']:
489 resources['metadata']['name'] = 'Notebook'
489 resources['metadata']['name'] = 'Notebook'
490
490
491 #Set the output extension
491 #Set the output extension
492 resources['output_extension'] = self.file_extension
492 resources['output_extension'] = self.file_extension
493 return resources
493 return resources
494
494
495
495
496 def _preprocess(self, nb, resources):
496 def _preprocess(self, nb, resources):
497 """
497 """
498 Preprocess the notebook before passing it into the Jinja engine.
498 Preprocess the notebook before passing it into the Jinja engine.
499 To preprocess the notebook is to apply all of the
499 To preprocess the notebook is to apply all of the
500
500
501 Parameters
501 Parameters
502 ----------
502 ----------
503 nb : notebook node
503 nb : notebook node
504 notebook that is being exported.
504 notebook that is being exported.
505 resources : a dict of additional resources that
505 resources : a dict of additional resources that
506 can be accessed read/write by preprocessors
506 can be accessed read/write by preprocessors
507 and filters.
507 and filters.
508 """
508 """
509
509
510 # Do a copy.deepcopy first,
510 # Do a copy.deepcopy first,
511 # we are never safe enough with what the preprocessors could do.
511 # we are never safe enough with what the preprocessors could do.
512 nbc = copy.deepcopy(nb)
512 nbc = copy.deepcopy(nb)
513 resc = copy.deepcopy(resources)
513 resc = copy.deepcopy(resources)
514
514
515 #Run each preprocessor on the notebook. Carry the output along
515 #Run each preprocessor on the notebook. Carry the output along
516 #to each preprocessor
516 #to each preprocessor
517 for preprocessor in self._preprocessors:
517 for preprocessor in self._preprocessors:
518 nbc, resc = preprocessor(nbc, resc)
518 nbc, resc = preprocessor(nbc, resc)
519 return nbc, resc
519 return nbc, resc
@@ -1,264 +1,264
1 """Module that allows custom Sphinx parameters to be set on the notebook and
1 """Module that allows custom Sphinx parameters to be set on the notebook and
2 on the 'other' object passed into Jinja. Called prior to Jinja conversion
2 on the 'other' object passed into Jinja. Called prior to Jinja conversion
3 process.
3 process.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import os.path
20 import os.path
21
21
22 # Used to set the default date to today's date
22 # Used to set the default date to today's date
23 from datetime import date
23 from datetime import date
24
24
25 # Third-party imports
25 # Third-party imports
26 # Needed for Pygments latex definitions.
26 # Needed for Pygments latex definitions.
27 from pygments.formatters import LatexFormatter
27 from pygments.formatters import LatexFormatter
28
28
29 # Our own imports
29 # Our own imports
30 # Configurable traitlets
30 # Configurable traitlets
31 from IPython.utils.traitlets import Unicode, Bool
31 from IPython.utils.traitlets import Unicode, Bool
32 from IPython.utils import text
32
33
33 # Needed to override preprocessor
34 # Needed to override preprocessor
34 from .base import (Preprocessor)
35 from .base import (Preprocessor)
35
36
36 from IPython.nbconvert.utils import console
37 from IPython.nbconvert.utils import console
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Classes and functions
40 # Classes and functions
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42 class SphinxPreprocessor(Preprocessor):
43 class SphinxPreprocessor(Preprocessor):
43 """
44 """
44 Sphinx utility preprocessor.
45 Sphinx utility preprocessor.
45
46
46 This preprocessor is used to set variables needed by the latex to build
47 This preprocessor is used to set variables needed by the latex to build
47 Sphinx stylized templates.
48 Sphinx stylized templates.
48 """
49 """
49
50
50 interactive = Bool(False, config=True, help="""
51 interactive = Bool(False, config=True, help="""
51 Allows you to define whether or not the Sphinx exporter will prompt
52 Allows you to define whether or not the Sphinx exporter will prompt
52 you for input during the conversion process. If this is set to false,
53 you for input during the conversion process. If this is set to false,
53 the author, version, release, date, and chapter_style traits should
54 the author, version, release, date, and chapter_style traits should
54 be set.
55 be set.
55 """)
56 """)
56
57
57 author = Unicode("Unknown Author", config=True, help="Author name")
58 author = Unicode("Unknown Author", config=True, help="Author name")
58
59
59 version = Unicode("", config=True, help="""
60 version = Unicode("", config=True, help="""
60 Version number
61 Version number
61 You can leave this blank if you do not want to render a version number.
62 You can leave this blank if you do not want to render a version number.
62 Example: "1.0.0"
63 Example: "1.0.0"
63 """)
64 """)
64
65
65 release = Unicode("", config=True, help="""
66 release = Unicode("", config=True, help="""
66 Release name
67 Release name
67 You can leave this blank if you do not want to render a release name.
68 You can leave this blank if you do not want to render a release name.
68 Example: "Rough Draft"
69 Example: "Rough Draft"
69 """)
70 """)
70
71
71 publish_date = Unicode("", config=True, help="""
72 publish_date = Unicode("", config=True, help="""
72 Publish date
73 Publish date
73 This is the date to render on the document as the publish date.
74 This is the date to render on the document as the publish date.
74 Leave this blank to default to todays date.
75 Leave this blank to default to todays date.
75 Example: "June 12, 1990"
76 Example: "June 12, 1990"
76 """)
77 """)
77
78
78 chapter_style = Unicode("Bjarne", config=True, help="""
79 chapter_style = Unicode("Bjarne", config=True, help="""
79 Sphinx chapter style
80 Sphinx chapter style
80 This is the style to use for the chapter headers in the document.
81 This is the style to use for the chapter headers in the document.
81 You may choose one of the following:
82 You may choose one of the following:
82 "Bjarne" (default)
83 "Bjarne" (default)
83 "Lenny"
84 "Lenny"
84 "Glenn"
85 "Glenn"
85 "Conny"
86 "Conny"
86 "Rejne"
87 "Rejne"
87 "Sonny" (used for international documents)
88 "Sonny" (used for international documents)
88 """)
89 """)
89
90
90 output_style = Unicode("notebook", config=True, help="""
91 output_style = Unicode("notebook", config=True, help="""
91 Nbconvert Ipython
92 Nbconvert Ipython
92 notebook input/output formatting style.
93 notebook input/output formatting style.
93 You may choose one of the following:
94 You may choose one of the following:
94 "simple (recommended for long code segments)"
95 "simple (recommended for long code segments)"
95 "notebook" (default)
96 "notebook" (default)
96 """)
97 """)
97
98
98 center_output = Bool(False, config=True, help="""
99 center_output = Bool(False, config=True, help="""
99 Optional attempt to center all output. If this is false, no additional
100 Optional attempt to center all output. If this is false, no additional
100 formatting is applied.
101 formatting is applied.
101 """)
102 """)
102
103
103 use_headers = Bool(True, config=True, help="""
104 use_headers = Bool(True, config=True, help="""
104 Whether not a header should be added to the document.
105 Whether not a header should be added to the document.
105 """)
106 """)
106
107
107 #Allow the user to override the title of the notebook (useful for
108 #Allow the user to override the title of the notebook (useful for
108 #fancy document titles that the file system doesn't support.)
109 #fancy document titles that the file system doesn't support.)
109 overridetitle = Unicode("", config=True, help="")
110 overridetitle = Unicode("", config=True, help="")
110
111
111
112
112 def preprocess(self, nb, resources):
113 def preprocess(self, nb, resources):
113 """
114 """
114 Sphinx preprocessing to apply on each notebook.
115 Sphinx preprocessing to apply on each notebook.
115
116
116 Parameters
117 Parameters
117 ----------
118 ----------
118 nb : NotebookNode
119 nb : NotebookNode
119 Notebook being converted
120 Notebook being converted
120 resources : dictionary
121 resources : dictionary
121 Additional resources used in the conversion process. Allows
122 Additional resources used in the conversion process. Allows
122 preprocessors to pass variables into the Jinja engine.
123 preprocessors to pass variables into the Jinja engine.
123 """
124 """
124 # import sphinx here, so that sphinx is not a dependency when it's not used
125 # import sphinx here, so that sphinx is not a dependency when it's not used
125 import sphinx
126 import sphinx
126
127
127 # TODO: Add versatile method of additional notebook metadata. Include
128 # TODO: Add versatile method of additional notebook metadata. Include
128 # handling of multiple files. For now use a temporay namespace,
129 # handling of multiple files. For now use a temporay namespace,
129 # '_draft' to signify that this needs to change.
130 # '_draft' to signify that this needs to change.
130 if not isinstance(resources["sphinx"], dict):
131 if not isinstance(resources["sphinx"], dict):
131 resources["sphinx"] = {}
132 resources["sphinx"] = {}
132
133
133 if self.interactive:
134 if self.interactive:
134
135
135 # Prompt the user for additional meta data that doesn't exist currently
136 # Prompt the user for additional meta data that doesn't exist currently
136 # but would be usefull for Sphinx.
137 # but would be usefull for Sphinx.
137 resources["sphinx"]["author"] = self._prompt_author()
138 resources["sphinx"]["author"] = self._prompt_author()
138 resources["sphinx"]["version"] = self._prompt_version()
139 resources["sphinx"]["version"] = self._prompt_version()
139 resources["sphinx"]["release"] = self._prompt_release()
140 resources["sphinx"]["release"] = self._prompt_release()
140 resources["sphinx"]["date"] = self._prompt_date()
141 resources["sphinx"]["date"] = self._prompt_date()
141
142
142 # Prompt the user for the document style.
143 # Prompt the user for the document style.
143 resources["sphinx"]["chapterstyle"] = self._prompt_chapter_title_style()
144 resources["sphinx"]["chapterstyle"] = self._prompt_chapter_title_style()
144 resources["sphinx"]["outputstyle"] = self._prompt_output_style()
145 resources["sphinx"]["outputstyle"] = self._prompt_output_style()
145
146
146 # Small options
147 # Small options
147 resources["sphinx"]["centeroutput"] = console.prompt_boolean("Do you want to center the output? (false)", False)
148 resources["sphinx"]["centeroutput"] = console.prompt_boolean("Do you want to center the output? (false)", False)
148 resources["sphinx"]["header"] = console.prompt_boolean("Should a Sphinx document header be used? (true)", True)
149 resources["sphinx"]["header"] = console.prompt_boolean("Should a Sphinx document header be used? (true)", True)
149 else:
150 else:
150
151
151 # Try to use the traitlets.
152 # Try to use the traitlets.
152 resources["sphinx"]["author"] = self.author
153 resources["sphinx"]["author"] = self.author
153 resources["sphinx"]["version"] = self.version
154 resources["sphinx"]["version"] = self.version
154 resources["sphinx"]["release"] = self.release
155 resources["sphinx"]["release"] = self.release
155
156
156 # Use todays date if none is provided.
157 # Use todays date if none is provided.
157 if self.publish_date:
158 if self.publish_date:
158 resources["sphinx"]["date"] = self.publish_date
159 resources["sphinx"]["date"] = self.publish_date
159 elif len(resources['metadata']['modified_date'].strip()) == 0:
160 elif len(resources['metadata']['modified_date'].strip()) == 0:
160 resources["sphinx"]["date"] = date.today().strftime("%B %-d, %Y")
161 resources["sphinx"]["date"] = date.today().strftime(text.date_format)
161 else:
162 else:
162 resources["sphinx"]["date"] = resources['metadata']['modified_date']
163 resources["sphinx"]["date"] = resources['metadata']['modified_date']
163
164
164 # Sphinx traitlets.
165 # Sphinx traitlets.
165 resources["sphinx"]["chapterstyle"] = self.chapter_style
166 resources["sphinx"]["chapterstyle"] = self.chapter_style
166 resources["sphinx"]["outputstyle"] = self.output_style
167 resources["sphinx"]["outputstyle"] = self.output_style
167 resources["sphinx"]["centeroutput"] = self.center_output
168 resources["sphinx"]["centeroutput"] = self.center_output
168 resources["sphinx"]["header"] = self.use_headers
169 resources["sphinx"]["header"] = self.use_headers
169
170
170 # Find and pass in the path to the Sphinx dependencies.
171 # Find and pass in the path to the Sphinx dependencies.
171 resources["sphinx"]["texinputs"] = os.path.realpath(os.path.join(sphinx.package_dir, "texinputs"))
172 resources["sphinx"]["texinputs"] = os.path.realpath(os.path.join(sphinx.package_dir, "texinputs"))
172
173
173 # Generate Pygments definitions for Latex
174 # Generate Pygments definitions for Latex
174 resources["sphinx"]["pygment_definitions"] = self._generate_pygments_latex_def()
175 resources["sphinx"]["pygment_definitions"] = self._generate_pygments_latex_def()
175
176
176 if not (self.overridetitle == None or len(self.overridetitle.strip()) == 0):
177 if not (self.overridetitle == None or len(self.overridetitle.strip()) == 0):
177 resources['metadata']['name'] = self.overridetitle
178 resources['metadata']['name'] = self.overridetitle
178
179
179 # End
180 # End
180 return nb, resources
181 return nb, resources
181
182
182
183
183 def _generate_pygments_latex_def(self):
184 def _generate_pygments_latex_def(self):
184 """
185 """
185 Generate the pygments latex definitions that allows pygments
186 Generate the pygments latex definitions that allows pygments
186 to work in latex.
187 to work in latex.
187 """
188 """
188
189
189 return LatexFormatter().get_style_defs()
190 return LatexFormatter().get_style_defs()
190
191
191
192
192 def _prompt_author(self):
193 def _prompt_author(self):
193 """
194 """
194 Prompt the user to input an Author name
195 Prompt the user to input an Author name
195 """
196 """
196 return console.input("Author name: ")
197 return console.input("Author name: ")
197
198
198
199
199 def _prompt_version(self):
200 def _prompt_version(self):
200 """
201 """
201 prompt the user to enter a version number
202 prompt the user to enter a version number
202 """
203 """
203 return console.input("Version (ie ""1.0.0""): ")
204 return console.input("Version (ie ""1.0.0""): ")
204
205
205
206
206 def _prompt_release(self):
207 def _prompt_release(self):
207 """
208 """
208 Prompt the user to input a release name
209 Prompt the user to input a release name
209 """
210 """
210
211
211 return console.input("Release Name (ie ""Rough draft""): ")
212 return console.input("Release Name (ie ""Rough draft""): ")
212
213
213
214
214 def _prompt_date(self, resources):
215 def _prompt_date(self, resources):
215 """
216 """
216 Prompt the user to enter a date
217 Prompt the user to enter a date
217 """
218 """
218
219
219 if resources['metadata']['modified_date']:
220 if resources['metadata']['modified_date']:
220 default_date = resources['metadata']['modified_date']
221 default_date = resources['metadata']['modified_date']
221 else:
222 else:
222 default_date = date.today().strftime("%B %-d, %Y")
223 default_date = date.today().strftime(text.date_format)
223
224
224 user_date = console.input("Date (deafults to \"" + default_date + "\"): ")
225 user_date = console.input("Date (deafults to \"" + default_date + "\"): ")
225 if len(user_date.strip()) == 0:
226 if len(user_date.strip()) == 0:
226 user_date = default_date
227 user_date = default_date
227 return user_date
228 return user_date
228
229
229
230
230 def _prompt_output_style(self):
231 def _prompt_output_style(self):
231 """
232 """
232 Prompts the user to pick an IPython output style.
233 Prompts the user to pick an IPython output style.
233 """
234 """
234
235
235 # Dictionary of available output styles
236 # Dictionary of available output styles
236 styles = {1: "simple",
237 styles = {1: "simple",
237 2: "notebook"}
238 2: "notebook"}
238
239
239 #Append comments to the menu when displaying it to the user.
240 #Append comments to the menu when displaying it to the user.
240 comments = {1: "(recommended for long code segments)",
241 comments = {1: "(recommended for long code segments)",
241 2: "(default)"}
242 2: "(default)"}
242
243
243 return console.prompt_dictionary(styles, default_style=2, menu_comments=comments)
244 return console.prompt_dictionary(styles, default_style=2, menu_comments=comments)
244
245
245
246
246 def _prompt_chapter_title_style(self):
247 def _prompt_chapter_title_style(self):
247 """
248 """
248 Prompts the user to pick a Sphinx chapter style
249 Prompts the user to pick a Sphinx chapter style
249 """
250 """
250
251
251 # Dictionary of available Sphinx styles
252 # Dictionary of available Sphinx styles
252 styles = {1: "Bjarne",
253 styles = {1: "Bjarne",
253 2: "Lenny",
254 2: "Lenny",
254 3: "Glenn",
255 3: "Glenn",
255 4: "Conny",
256 4: "Conny",
256 5: "Rejne",
257 5: "Rejne",
257 6: "Sonny"}
258 6: "Sonny"}
258
259
259 #Append comments to the menu when displaying it to the user.
260 #Append comments to the menu when displaying it to the user.
260 comments = {1: "(default)",
261 comments = {1: "(default)",
261 6: "(for international documents)"}
262 6: "(for international documents)"}
262
263
263 return console.prompt_dictionary(styles, menu_comments=comments)
264 return console.prompt_dictionary(styles, menu_comments=comments)
264
@@ -1,713 +1,726
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import re
23 import re
24 import sys
24 import textwrap
25 import textwrap
25 from string import Formatter
26 from string import Formatter
26
27
27 from IPython.external.path import path
28 from IPython.external.path import path
28 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
29 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
29 from IPython.utils import py3compat
30 from IPython.utils import py3compat
30
31
32
33 #-----------------------------------------------------------------------------
34 # Declarations
35 #-----------------------------------------------------------------------------
36
37 # datetime.strftime date format for ipython
38 if sys.platform == 'win32':
39 date_format = "%B %d, %Y"
40 else:
41 date_format = "%B %-d, %Y"
42
43
31 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
32 # Code
45 # Code
33 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
34
47
35 class LSString(str):
48 class LSString(str):
36 """String derivative with a special access attributes.
49 """String derivative with a special access attributes.
37
50
38 These are normal strings, but with the special attributes:
51 These are normal strings, but with the special attributes:
39
52
40 .l (or .list) : value as list (split on newlines).
53 .l (or .list) : value as list (split on newlines).
41 .n (or .nlstr): original value (the string itself).
54 .n (or .nlstr): original value (the string itself).
42 .s (or .spstr): value as whitespace-separated string.
55 .s (or .spstr): value as whitespace-separated string.
43 .p (or .paths): list of path objects
56 .p (or .paths): list of path objects
44
57
45 Any values which require transformations are computed only once and
58 Any values which require transformations are computed only once and
46 cached.
59 cached.
47
60
48 Such strings are very useful to efficiently interact with the shell, which
61 Such strings are very useful to efficiently interact with the shell, which
49 typically only understands whitespace-separated options for commands."""
62 typically only understands whitespace-separated options for commands."""
50
63
51 def get_list(self):
64 def get_list(self):
52 try:
65 try:
53 return self.__list
66 return self.__list
54 except AttributeError:
67 except AttributeError:
55 self.__list = self.split('\n')
68 self.__list = self.split('\n')
56 return self.__list
69 return self.__list
57
70
58 l = list = property(get_list)
71 l = list = property(get_list)
59
72
60 def get_spstr(self):
73 def get_spstr(self):
61 try:
74 try:
62 return self.__spstr
75 return self.__spstr
63 except AttributeError:
76 except AttributeError:
64 self.__spstr = self.replace('\n',' ')
77 self.__spstr = self.replace('\n',' ')
65 return self.__spstr
78 return self.__spstr
66
79
67 s = spstr = property(get_spstr)
80 s = spstr = property(get_spstr)
68
81
69 def get_nlstr(self):
82 def get_nlstr(self):
70 return self
83 return self
71
84
72 n = nlstr = property(get_nlstr)
85 n = nlstr = property(get_nlstr)
73
86
74 def get_paths(self):
87 def get_paths(self):
75 try:
88 try:
76 return self.__paths
89 return self.__paths
77 except AttributeError:
90 except AttributeError:
78 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
91 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
79 return self.__paths
92 return self.__paths
80
93
81 p = paths = property(get_paths)
94 p = paths = property(get_paths)
82
95
83 # FIXME: We need to reimplement type specific displayhook and then add this
96 # FIXME: We need to reimplement type specific displayhook and then add this
84 # back as a custom printer. This should also be moved outside utils into the
97 # back as a custom printer. This should also be moved outside utils into the
85 # core.
98 # core.
86
99
87 # def print_lsstring(arg):
100 # def print_lsstring(arg):
88 # """ Prettier (non-repr-like) and more informative printer for LSString """
101 # """ Prettier (non-repr-like) and more informative printer for LSString """
89 # print "LSString (.p, .n, .l, .s available). Value:"
102 # print "LSString (.p, .n, .l, .s available). Value:"
90 # print arg
103 # print arg
91 #
104 #
92 #
105 #
93 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
106 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
94
107
95
108
96 class SList(list):
109 class SList(list):
97 """List derivative with a special access attributes.
110 """List derivative with a special access attributes.
98
111
99 These are normal lists, but with the special attributes:
112 These are normal lists, but with the special attributes:
100
113
101 .l (or .list) : value as list (the list itself).
114 .l (or .list) : value as list (the list itself).
102 .n (or .nlstr): value as a string, joined on newlines.
115 .n (or .nlstr): value as a string, joined on newlines.
103 .s (or .spstr): value as a string, joined on spaces.
116 .s (or .spstr): value as a string, joined on spaces.
104 .p (or .paths): list of path objects
117 .p (or .paths): list of path objects
105
118
106 Any values which require transformations are computed only once and
119 Any values which require transformations are computed only once and
107 cached."""
120 cached."""
108
121
109 def get_list(self):
122 def get_list(self):
110 return self
123 return self
111
124
112 l = list = property(get_list)
125 l = list = property(get_list)
113
126
114 def get_spstr(self):
127 def get_spstr(self):
115 try:
128 try:
116 return self.__spstr
129 return self.__spstr
117 except AttributeError:
130 except AttributeError:
118 self.__spstr = ' '.join(self)
131 self.__spstr = ' '.join(self)
119 return self.__spstr
132 return self.__spstr
120
133
121 s = spstr = property(get_spstr)
134 s = spstr = property(get_spstr)
122
135
123 def get_nlstr(self):
136 def get_nlstr(self):
124 try:
137 try:
125 return self.__nlstr
138 return self.__nlstr
126 except AttributeError:
139 except AttributeError:
127 self.__nlstr = '\n'.join(self)
140 self.__nlstr = '\n'.join(self)
128 return self.__nlstr
141 return self.__nlstr
129
142
130 n = nlstr = property(get_nlstr)
143 n = nlstr = property(get_nlstr)
131
144
132 def get_paths(self):
145 def get_paths(self):
133 try:
146 try:
134 return self.__paths
147 return self.__paths
135 except AttributeError:
148 except AttributeError:
136 self.__paths = [path(p) for p in self if os.path.exists(p)]
149 self.__paths = [path(p) for p in self if os.path.exists(p)]
137 return self.__paths
150 return self.__paths
138
151
139 p = paths = property(get_paths)
152 p = paths = property(get_paths)
140
153
141 def grep(self, pattern, prune = False, field = None):
154 def grep(self, pattern, prune = False, field = None):
142 """ Return all strings matching 'pattern' (a regex or callable)
155 """ Return all strings matching 'pattern' (a regex or callable)
143
156
144 This is case-insensitive. If prune is true, return all items
157 This is case-insensitive. If prune is true, return all items
145 NOT matching the pattern.
158 NOT matching the pattern.
146
159
147 If field is specified, the match must occur in the specified
160 If field is specified, the match must occur in the specified
148 whitespace-separated field.
161 whitespace-separated field.
149
162
150 Examples::
163 Examples::
151
164
152 a.grep( lambda x: x.startswith('C') )
165 a.grep( lambda x: x.startswith('C') )
153 a.grep('Cha.*log', prune=1)
166 a.grep('Cha.*log', prune=1)
154 a.grep('chm', field=-1)
167 a.grep('chm', field=-1)
155 """
168 """
156
169
157 def match_target(s):
170 def match_target(s):
158 if field is None:
171 if field is None:
159 return s
172 return s
160 parts = s.split()
173 parts = s.split()
161 try:
174 try:
162 tgt = parts[field]
175 tgt = parts[field]
163 return tgt
176 return tgt
164 except IndexError:
177 except IndexError:
165 return ""
178 return ""
166
179
167 if isinstance(pattern, basestring):
180 if isinstance(pattern, basestring):
168 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
181 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
169 else:
182 else:
170 pred = pattern
183 pred = pattern
171 if not prune:
184 if not prune:
172 return SList([el for el in self if pred(match_target(el))])
185 return SList([el for el in self if pred(match_target(el))])
173 else:
186 else:
174 return SList([el for el in self if not pred(match_target(el))])
187 return SList([el for el in self if not pred(match_target(el))])
175
188
176 def fields(self, *fields):
189 def fields(self, *fields):
177 """ Collect whitespace-separated fields from string list
190 """ Collect whitespace-separated fields from string list
178
191
179 Allows quick awk-like usage of string lists.
192 Allows quick awk-like usage of string lists.
180
193
181 Example data (in var a, created by 'a = !ls -l')::
194 Example data (in var a, created by 'a = !ls -l')::
182 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
195 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
183 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
196 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
184
197
185 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
198 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
186 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
199 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
187 (note the joining by space).
200 (note the joining by space).
188 a.fields(-1) is ['ChangeLog', 'IPython']
201 a.fields(-1) is ['ChangeLog', 'IPython']
189
202
190 IndexErrors are ignored.
203 IndexErrors are ignored.
191
204
192 Without args, fields() just split()'s the strings.
205 Without args, fields() just split()'s the strings.
193 """
206 """
194 if len(fields) == 0:
207 if len(fields) == 0:
195 return [el.split() for el in self]
208 return [el.split() for el in self]
196
209
197 res = SList()
210 res = SList()
198 for el in [f.split() for f in self]:
211 for el in [f.split() for f in self]:
199 lineparts = []
212 lineparts = []
200
213
201 for fd in fields:
214 for fd in fields:
202 try:
215 try:
203 lineparts.append(el[fd])
216 lineparts.append(el[fd])
204 except IndexError:
217 except IndexError:
205 pass
218 pass
206 if lineparts:
219 if lineparts:
207 res.append(" ".join(lineparts))
220 res.append(" ".join(lineparts))
208
221
209 return res
222 return res
210
223
211 def sort(self,field= None, nums = False):
224 def sort(self,field= None, nums = False):
212 """ sort by specified fields (see fields())
225 """ sort by specified fields (see fields())
213
226
214 Example::
227 Example::
215 a.sort(1, nums = True)
228 a.sort(1, nums = True)
216
229
217 Sorts a by second field, in numerical order (so that 21 > 3)
230 Sorts a by second field, in numerical order (so that 21 > 3)
218
231
219 """
232 """
220
233
221 #decorate, sort, undecorate
234 #decorate, sort, undecorate
222 if field is not None:
235 if field is not None:
223 dsu = [[SList([line]).fields(field), line] for line in self]
236 dsu = [[SList([line]).fields(field), line] for line in self]
224 else:
237 else:
225 dsu = [[line, line] for line in self]
238 dsu = [[line, line] for line in self]
226 if nums:
239 if nums:
227 for i in range(len(dsu)):
240 for i in range(len(dsu)):
228 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
241 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
229 try:
242 try:
230 n = int(numstr)
243 n = int(numstr)
231 except ValueError:
244 except ValueError:
232 n = 0;
245 n = 0;
233 dsu[i][0] = n
246 dsu[i][0] = n
234
247
235
248
236 dsu.sort()
249 dsu.sort()
237 return SList([t[1] for t in dsu])
250 return SList([t[1] for t in dsu])
238
251
239
252
240 # FIXME: We need to reimplement type specific displayhook and then add this
253 # FIXME: We need to reimplement type specific displayhook and then add this
241 # back as a custom printer. This should also be moved outside utils into the
254 # back as a custom printer. This should also be moved outside utils into the
242 # core.
255 # core.
243
256
244 # def print_slist(arg):
257 # def print_slist(arg):
245 # """ Prettier (non-repr-like) and more informative printer for SList """
258 # """ Prettier (non-repr-like) and more informative printer for SList """
246 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
259 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
247 # if hasattr(arg, 'hideonce') and arg.hideonce:
260 # if hasattr(arg, 'hideonce') and arg.hideonce:
248 # arg.hideonce = False
261 # arg.hideonce = False
249 # return
262 # return
250 #
263 #
251 # nlprint(arg) # This was a nested list printer, now removed.
264 # nlprint(arg) # This was a nested list printer, now removed.
252 #
265 #
253 # print_slist = result_display.when_type(SList)(print_slist)
266 # print_slist = result_display.when_type(SList)(print_slist)
254
267
255
268
256 def indent(instr,nspaces=4, ntabs=0, flatten=False):
269 def indent(instr,nspaces=4, ntabs=0, flatten=False):
257 """Indent a string a given number of spaces or tabstops.
270 """Indent a string a given number of spaces or tabstops.
258
271
259 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
272 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
260
273
261 Parameters
274 Parameters
262 ----------
275 ----------
263
276
264 instr : basestring
277 instr : basestring
265 The string to be indented.
278 The string to be indented.
266 nspaces : int (default: 4)
279 nspaces : int (default: 4)
267 The number of spaces to be indented.
280 The number of spaces to be indented.
268 ntabs : int (default: 0)
281 ntabs : int (default: 0)
269 The number of tabs to be indented.
282 The number of tabs to be indented.
270 flatten : bool (default: False)
283 flatten : bool (default: False)
271 Whether to scrub existing indentation. If True, all lines will be
284 Whether to scrub existing indentation. If True, all lines will be
272 aligned to the same indentation. If False, existing indentation will
285 aligned to the same indentation. If False, existing indentation will
273 be strictly increased.
286 be strictly increased.
274
287
275 Returns
288 Returns
276 -------
289 -------
277
290
278 str|unicode : string indented by ntabs and nspaces.
291 str|unicode : string indented by ntabs and nspaces.
279
292
280 """
293 """
281 if instr is None:
294 if instr is None:
282 return
295 return
283 ind = '\t'*ntabs+' '*nspaces
296 ind = '\t'*ntabs+' '*nspaces
284 if flatten:
297 if flatten:
285 pat = re.compile(r'^\s*', re.MULTILINE)
298 pat = re.compile(r'^\s*', re.MULTILINE)
286 else:
299 else:
287 pat = re.compile(r'^', re.MULTILINE)
300 pat = re.compile(r'^', re.MULTILINE)
288 outstr = re.sub(pat, ind, instr)
301 outstr = re.sub(pat, ind, instr)
289 if outstr.endswith(os.linesep+ind):
302 if outstr.endswith(os.linesep+ind):
290 return outstr[:-len(ind)]
303 return outstr[:-len(ind)]
291 else:
304 else:
292 return outstr
305 return outstr
293
306
294
307
295 def list_strings(arg):
308 def list_strings(arg):
296 """Always return a list of strings, given a string or list of strings
309 """Always return a list of strings, given a string or list of strings
297 as input.
310 as input.
298
311
299 :Examples:
312 :Examples:
300
313
301 In [7]: list_strings('A single string')
314 In [7]: list_strings('A single string')
302 Out[7]: ['A single string']
315 Out[7]: ['A single string']
303
316
304 In [8]: list_strings(['A single string in a list'])
317 In [8]: list_strings(['A single string in a list'])
305 Out[8]: ['A single string in a list']
318 Out[8]: ['A single string in a list']
306
319
307 In [9]: list_strings(['A','list','of','strings'])
320 In [9]: list_strings(['A','list','of','strings'])
308 Out[9]: ['A', 'list', 'of', 'strings']
321 Out[9]: ['A', 'list', 'of', 'strings']
309 """
322 """
310
323
311 if isinstance(arg,basestring): return [arg]
324 if isinstance(arg,basestring): return [arg]
312 else: return arg
325 else: return arg
313
326
314
327
315 def marquee(txt='',width=78,mark='*'):
328 def marquee(txt='',width=78,mark='*'):
316 """Return the input string centered in a 'marquee'.
329 """Return the input string centered in a 'marquee'.
317
330
318 :Examples:
331 :Examples:
319
332
320 In [16]: marquee('A test',40)
333 In [16]: marquee('A test',40)
321 Out[16]: '**************** A test ****************'
334 Out[16]: '**************** A test ****************'
322
335
323 In [17]: marquee('A test',40,'-')
336 In [17]: marquee('A test',40,'-')
324 Out[17]: '---------------- A test ----------------'
337 Out[17]: '---------------- A test ----------------'
325
338
326 In [18]: marquee('A test',40,' ')
339 In [18]: marquee('A test',40,' ')
327 Out[18]: ' A test '
340 Out[18]: ' A test '
328
341
329 """
342 """
330 if not txt:
343 if not txt:
331 return (mark*width)[:width]
344 return (mark*width)[:width]
332 nmark = (width-len(txt)-2)//len(mark)//2
345 nmark = (width-len(txt)-2)//len(mark)//2
333 if nmark < 0: nmark =0
346 if nmark < 0: nmark =0
334 marks = mark*nmark
347 marks = mark*nmark
335 return '%s %s %s' % (marks,txt,marks)
348 return '%s %s %s' % (marks,txt,marks)
336
349
337
350
338 ini_spaces_re = re.compile(r'^(\s+)')
351 ini_spaces_re = re.compile(r'^(\s+)')
339
352
340 def num_ini_spaces(strng):
353 def num_ini_spaces(strng):
341 """Return the number of initial spaces in a string"""
354 """Return the number of initial spaces in a string"""
342
355
343 ini_spaces = ini_spaces_re.match(strng)
356 ini_spaces = ini_spaces_re.match(strng)
344 if ini_spaces:
357 if ini_spaces:
345 return ini_spaces.end()
358 return ini_spaces.end()
346 else:
359 else:
347 return 0
360 return 0
348
361
349
362
350 def format_screen(strng):
363 def format_screen(strng):
351 """Format a string for screen printing.
364 """Format a string for screen printing.
352
365
353 This removes some latex-type format codes."""
366 This removes some latex-type format codes."""
354 # Paragraph continue
367 # Paragraph continue
355 par_re = re.compile(r'\\$',re.MULTILINE)
368 par_re = re.compile(r'\\$',re.MULTILINE)
356 strng = par_re.sub('',strng)
369 strng = par_re.sub('',strng)
357 return strng
370 return strng
358
371
359
372
360 def dedent(text):
373 def dedent(text):
361 """Equivalent of textwrap.dedent that ignores unindented first line.
374 """Equivalent of textwrap.dedent that ignores unindented first line.
362
375
363 This means it will still dedent strings like:
376 This means it will still dedent strings like:
364 '''foo
377 '''foo
365 is a bar
378 is a bar
366 '''
379 '''
367
380
368 For use in wrap_paragraphs.
381 For use in wrap_paragraphs.
369 """
382 """
370
383
371 if text.startswith('\n'):
384 if text.startswith('\n'):
372 # text starts with blank line, don't ignore the first line
385 # text starts with blank line, don't ignore the first line
373 return textwrap.dedent(text)
386 return textwrap.dedent(text)
374
387
375 # split first line
388 # split first line
376 splits = text.split('\n',1)
389 splits = text.split('\n',1)
377 if len(splits) == 1:
390 if len(splits) == 1:
378 # only one line
391 # only one line
379 return textwrap.dedent(text)
392 return textwrap.dedent(text)
380
393
381 first, rest = splits
394 first, rest = splits
382 # dedent everything but the first line
395 # dedent everything but the first line
383 rest = textwrap.dedent(rest)
396 rest = textwrap.dedent(rest)
384 return '\n'.join([first, rest])
397 return '\n'.join([first, rest])
385
398
386
399
387 def wrap_paragraphs(text, ncols=80):
400 def wrap_paragraphs(text, ncols=80):
388 """Wrap multiple paragraphs to fit a specified width.
401 """Wrap multiple paragraphs to fit a specified width.
389
402
390 This is equivalent to textwrap.wrap, but with support for multiple
403 This is equivalent to textwrap.wrap, but with support for multiple
391 paragraphs, as separated by empty lines.
404 paragraphs, as separated by empty lines.
392
405
393 Returns
406 Returns
394 -------
407 -------
395
408
396 list of complete paragraphs, wrapped to fill `ncols` columns.
409 list of complete paragraphs, wrapped to fill `ncols` columns.
397 """
410 """
398 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
411 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
399 text = dedent(text).strip()
412 text = dedent(text).strip()
400 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
413 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
401 out_ps = []
414 out_ps = []
402 indent_re = re.compile(r'\n\s+', re.MULTILINE)
415 indent_re = re.compile(r'\n\s+', re.MULTILINE)
403 for p in paragraphs:
416 for p in paragraphs:
404 # presume indentation that survives dedent is meaningful formatting,
417 # presume indentation that survives dedent is meaningful formatting,
405 # so don't fill unless text is flush.
418 # so don't fill unless text is flush.
406 if indent_re.search(p) is None:
419 if indent_re.search(p) is None:
407 # wrap paragraph
420 # wrap paragraph
408 p = textwrap.fill(p, ncols)
421 p = textwrap.fill(p, ncols)
409 out_ps.append(p)
422 out_ps.append(p)
410 return out_ps
423 return out_ps
411
424
412
425
413 def long_substr(data):
426 def long_substr(data):
414 """Return the longest common substring in a list of strings.
427 """Return the longest common substring in a list of strings.
415
428
416 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
429 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
417 """
430 """
418 substr = ''
431 substr = ''
419 if len(data) > 1 and len(data[0]) > 0:
432 if len(data) > 1 and len(data[0]) > 0:
420 for i in range(len(data[0])):
433 for i in range(len(data[0])):
421 for j in range(len(data[0])-i+1):
434 for j in range(len(data[0])-i+1):
422 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
435 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
423 substr = data[0][i:i+j]
436 substr = data[0][i:i+j]
424 elif len(data) == 1:
437 elif len(data) == 1:
425 substr = data[0]
438 substr = data[0]
426 return substr
439 return substr
427
440
428
441
429 def strip_email_quotes(text):
442 def strip_email_quotes(text):
430 """Strip leading email quotation characters ('>').
443 """Strip leading email quotation characters ('>').
431
444
432 Removes any combination of leading '>' interspersed with whitespace that
445 Removes any combination of leading '>' interspersed with whitespace that
433 appears *identically* in all lines of the input text.
446 appears *identically* in all lines of the input text.
434
447
435 Parameters
448 Parameters
436 ----------
449 ----------
437 text : str
450 text : str
438
451
439 Examples
452 Examples
440 --------
453 --------
441
454
442 Simple uses::
455 Simple uses::
443
456
444 In [2]: strip_email_quotes('> > text')
457 In [2]: strip_email_quotes('> > text')
445 Out[2]: 'text'
458 Out[2]: 'text'
446
459
447 In [3]: strip_email_quotes('> > text\\n> > more')
460 In [3]: strip_email_quotes('> > text\\n> > more')
448 Out[3]: 'text\\nmore'
461 Out[3]: 'text\\nmore'
449
462
450 Note how only the common prefix that appears in all lines is stripped::
463 Note how only the common prefix that appears in all lines is stripped::
451
464
452 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
465 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
453 Out[4]: '> text\\n> more\\nmore...'
466 Out[4]: '> text\\n> more\\nmore...'
454
467
455 So if any line has no quote marks ('>') , then none are stripped from any
468 So if any line has no quote marks ('>') , then none are stripped from any
456 of them ::
469 of them ::
457
470
458 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
471 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
459 Out[5]: '> > text\\n> > more\\nlast different'
472 Out[5]: '> > text\\n> > more\\nlast different'
460 """
473 """
461 lines = text.splitlines()
474 lines = text.splitlines()
462 matches = set()
475 matches = set()
463 for line in lines:
476 for line in lines:
464 prefix = re.match(r'^(\s*>[ >]*)', line)
477 prefix = re.match(r'^(\s*>[ >]*)', line)
465 if prefix:
478 if prefix:
466 matches.add(prefix.group(1))
479 matches.add(prefix.group(1))
467 else:
480 else:
468 break
481 break
469 else:
482 else:
470 prefix = long_substr(list(matches))
483 prefix = long_substr(list(matches))
471 if prefix:
484 if prefix:
472 strip = len(prefix)
485 strip = len(prefix)
473 text = '\n'.join([ ln[strip:] for ln in lines])
486 text = '\n'.join([ ln[strip:] for ln in lines])
474 return text
487 return text
475
488
476
489
477 class EvalFormatter(Formatter):
490 class EvalFormatter(Formatter):
478 """A String Formatter that allows evaluation of simple expressions.
491 """A String Formatter that allows evaluation of simple expressions.
479
492
480 Note that this version interprets a : as specifying a format string (as per
493 Note that this version interprets a : as specifying a format string (as per
481 standard string formatting), so if slicing is required, you must explicitly
494 standard string formatting), so if slicing is required, you must explicitly
482 create a slice.
495 create a slice.
483
496
484 This is to be used in templating cases, such as the parallel batch
497 This is to be used in templating cases, such as the parallel batch
485 script templates, where simple arithmetic on arguments is useful.
498 script templates, where simple arithmetic on arguments is useful.
486
499
487 Examples
500 Examples
488 --------
501 --------
489
502
490 In [1]: f = EvalFormatter()
503 In [1]: f = EvalFormatter()
491 In [2]: f.format('{n//4}', n=8)
504 In [2]: f.format('{n//4}', n=8)
492 Out [2]: '2'
505 Out [2]: '2'
493
506
494 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
507 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
495 Out [3]: 'll'
508 Out [3]: 'll'
496 """
509 """
497 def get_field(self, name, args, kwargs):
510 def get_field(self, name, args, kwargs):
498 v = eval(name, kwargs)
511 v = eval(name, kwargs)
499 return v, name
512 return v, name
500
513
501
514
502 @skip_doctest_py3
515 @skip_doctest_py3
503 class FullEvalFormatter(Formatter):
516 class FullEvalFormatter(Formatter):
504 """A String Formatter that allows evaluation of simple expressions.
517 """A String Formatter that allows evaluation of simple expressions.
505
518
506 Any time a format key is not found in the kwargs,
519 Any time a format key is not found in the kwargs,
507 it will be tried as an expression in the kwargs namespace.
520 it will be tried as an expression in the kwargs namespace.
508
521
509 Note that this version allows slicing using [1:2], so you cannot specify
522 Note that this version allows slicing using [1:2], so you cannot specify
510 a format string. Use :class:`EvalFormatter` to permit format strings.
523 a format string. Use :class:`EvalFormatter` to permit format strings.
511
524
512 Examples
525 Examples
513 --------
526 --------
514
527
515 In [1]: f = FullEvalFormatter()
528 In [1]: f = FullEvalFormatter()
516 In [2]: f.format('{n//4}', n=8)
529 In [2]: f.format('{n//4}', n=8)
517 Out[2]: u'2'
530 Out[2]: u'2'
518
531
519 In [3]: f.format('{list(range(5))[2:4]}')
532 In [3]: f.format('{list(range(5))[2:4]}')
520 Out[3]: u'[2, 3]'
533 Out[3]: u'[2, 3]'
521
534
522 In [4]: f.format('{3*2}')
535 In [4]: f.format('{3*2}')
523 Out[4]: u'6'
536 Out[4]: u'6'
524 """
537 """
525 # copied from Formatter._vformat with minor changes to allow eval
538 # copied from Formatter._vformat with minor changes to allow eval
526 # and replace the format_spec code with slicing
539 # and replace the format_spec code with slicing
527 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
540 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
528 if recursion_depth < 0:
541 if recursion_depth < 0:
529 raise ValueError('Max string recursion exceeded')
542 raise ValueError('Max string recursion exceeded')
530 result = []
543 result = []
531 for literal_text, field_name, format_spec, conversion in \
544 for literal_text, field_name, format_spec, conversion in \
532 self.parse(format_string):
545 self.parse(format_string):
533
546
534 # output the literal text
547 # output the literal text
535 if literal_text:
548 if literal_text:
536 result.append(literal_text)
549 result.append(literal_text)
537
550
538 # if there's a field, output it
551 # if there's a field, output it
539 if field_name is not None:
552 if field_name is not None:
540 # this is some markup, find the object and do
553 # this is some markup, find the object and do
541 # the formatting
554 # the formatting
542
555
543 if format_spec:
556 if format_spec:
544 # override format spec, to allow slicing:
557 # override format spec, to allow slicing:
545 field_name = ':'.join([field_name, format_spec])
558 field_name = ':'.join([field_name, format_spec])
546
559
547 # eval the contents of the field for the object
560 # eval the contents of the field for the object
548 # to be formatted
561 # to be formatted
549 obj = eval(field_name, kwargs)
562 obj = eval(field_name, kwargs)
550
563
551 # do any conversion on the resulting object
564 # do any conversion on the resulting object
552 obj = self.convert_field(obj, conversion)
565 obj = self.convert_field(obj, conversion)
553
566
554 # format the object and append to the result
567 # format the object and append to the result
555 result.append(self.format_field(obj, ''))
568 result.append(self.format_field(obj, ''))
556
569
557 return u''.join(py3compat.cast_unicode(s) for s in result)
570 return u''.join(py3compat.cast_unicode(s) for s in result)
558
571
559
572
560 @skip_doctest_py3
573 @skip_doctest_py3
561 class DollarFormatter(FullEvalFormatter):
574 class DollarFormatter(FullEvalFormatter):
562 """Formatter allowing Itpl style $foo replacement, for names and attribute
575 """Formatter allowing Itpl style $foo replacement, for names and attribute
563 access only. Standard {foo} replacement also works, and allows full
576 access only. Standard {foo} replacement also works, and allows full
564 evaluation of its arguments.
577 evaluation of its arguments.
565
578
566 Examples
579 Examples
567 --------
580 --------
568 In [1]: f = DollarFormatter()
581 In [1]: f = DollarFormatter()
569 In [2]: f.format('{n//4}', n=8)
582 In [2]: f.format('{n//4}', n=8)
570 Out[2]: u'2'
583 Out[2]: u'2'
571
584
572 In [3]: f.format('23 * 76 is $result', result=23*76)
585 In [3]: f.format('23 * 76 is $result', result=23*76)
573 Out[3]: u'23 * 76 is 1748'
586 Out[3]: u'23 * 76 is 1748'
574
587
575 In [4]: f.format('$a or {b}', a=1, b=2)
588 In [4]: f.format('$a or {b}', a=1, b=2)
576 Out[4]: u'1 or 2'
589 Out[4]: u'1 or 2'
577 """
590 """
578 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
591 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
579 def parse(self, fmt_string):
592 def parse(self, fmt_string):
580 for literal_txt, field_name, format_spec, conversion \
593 for literal_txt, field_name, format_spec, conversion \
581 in Formatter.parse(self, fmt_string):
594 in Formatter.parse(self, fmt_string):
582
595
583 # Find $foo patterns in the literal text.
596 # Find $foo patterns in the literal text.
584 continue_from = 0
597 continue_from = 0
585 txt = ""
598 txt = ""
586 for m in self._dollar_pattern.finditer(literal_txt):
599 for m in self._dollar_pattern.finditer(literal_txt):
587 new_txt, new_field = m.group(1,2)
600 new_txt, new_field = m.group(1,2)
588 # $$foo --> $foo
601 # $$foo --> $foo
589 if new_field.startswith("$"):
602 if new_field.startswith("$"):
590 txt += new_txt + new_field
603 txt += new_txt + new_field
591 else:
604 else:
592 yield (txt + new_txt, new_field, "", None)
605 yield (txt + new_txt, new_field, "", None)
593 txt = ""
606 txt = ""
594 continue_from = m.end()
607 continue_from = m.end()
595
608
596 # Re-yield the {foo} style pattern
609 # Re-yield the {foo} style pattern
597 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
598
611
599 #-----------------------------------------------------------------------------
612 #-----------------------------------------------------------------------------
600 # Utils to columnize a list of string
613 # Utils to columnize a list of string
601 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
602
615
603 def _chunks(l, n):
616 def _chunks(l, n):
604 """Yield successive n-sized chunks from l."""
617 """Yield successive n-sized chunks from l."""
605 for i in xrange(0, len(l), n):
618 for i in xrange(0, len(l), n):
606 yield l[i:i+n]
619 yield l[i:i+n]
607
620
608
621
609 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
622 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
610 """Calculate optimal info to columnize a list of string"""
623 """Calculate optimal info to columnize a list of string"""
611 for nrow in range(1, len(rlist)+1) :
624 for nrow in range(1, len(rlist)+1) :
612 chk = map(max,_chunks(rlist, nrow))
625 chk = map(max,_chunks(rlist, nrow))
613 sumlength = sum(chk)
626 sumlength = sum(chk)
614 ncols = len(chk)
627 ncols = len(chk)
615 if sumlength+separator_size*(ncols-1) <= displaywidth :
628 if sumlength+separator_size*(ncols-1) <= displaywidth :
616 break;
629 break;
617 return {'columns_numbers' : ncols,
630 return {'columns_numbers' : ncols,
618 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
631 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
619 'rows_numbers' : nrow,
632 'rows_numbers' : nrow,
620 'columns_width' : chk
633 'columns_width' : chk
621 }
634 }
622
635
623
636
624 def _get_or_default(mylist, i, default=None):
637 def _get_or_default(mylist, i, default=None):
625 """return list item number, or default if don't exist"""
638 """return list item number, or default if don't exist"""
626 if i >= len(mylist):
639 if i >= len(mylist):
627 return default
640 return default
628 else :
641 else :
629 return mylist[i]
642 return mylist[i]
630
643
631
644
632 @skip_doctest
645 @skip_doctest
633 def compute_item_matrix(items, empty=None, *args, **kwargs) :
646 def compute_item_matrix(items, empty=None, *args, **kwargs) :
634 """Returns a nested list, and info to columnize items
647 """Returns a nested list, and info to columnize items
635
648
636 Parameters
649 Parameters
637 ----------
650 ----------
638
651
639 items :
652 items :
640 list of strings to columize
653 list of strings to columize
641 empty : (default None)
654 empty : (default None)
642 default value to fill list if needed
655 default value to fill list if needed
643 separator_size : int (default=2)
656 separator_size : int (default=2)
644 How much caracters will be used as a separation between each columns.
657 How much caracters will be used as a separation between each columns.
645 displaywidth : int (default=80)
658 displaywidth : int (default=80)
646 The width of the area onto wich the columns should enter
659 The width of the area onto wich the columns should enter
647
660
648 Returns
661 Returns
649 -------
662 -------
650
663
651 Returns a tuple of (strings_matrix, dict_info)
664 Returns a tuple of (strings_matrix, dict_info)
652
665
653 strings_matrix :
666 strings_matrix :
654
667
655 nested list of string, the outer most list contains as many list as
668 nested list of string, the outer most list contains as many list as
656 rows, the innermost lists have each as many element as colums. If the
669 rows, the innermost lists have each as many element as colums. If the
657 total number of elements in `items` does not equal the product of
670 total number of elements in `items` does not equal the product of
658 rows*columns, the last element of some lists are filled with `None`.
671 rows*columns, the last element of some lists are filled with `None`.
659
672
660 dict_info :
673 dict_info :
661 some info to make columnize easier:
674 some info to make columnize easier:
662
675
663 columns_numbers : number of columns
676 columns_numbers : number of columns
664 rows_numbers : number of rows
677 rows_numbers : number of rows
665 columns_width : list of with of each columns
678 columns_width : list of with of each columns
666 optimal_separator_width : best separator width between columns
679 optimal_separator_width : best separator width between columns
667
680
668 Examples
681 Examples
669 --------
682 --------
670
683
671 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
684 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
672 ...: compute_item_matrix(l,displaywidth=12)
685 ...: compute_item_matrix(l,displaywidth=12)
673 Out[1]:
686 Out[1]:
674 ([['aaa', 'f', 'k'],
687 ([['aaa', 'f', 'k'],
675 ['b', 'g', 'l'],
688 ['b', 'g', 'l'],
676 ['cc', 'h', None],
689 ['cc', 'h', None],
677 ['d', 'i', None],
690 ['d', 'i', None],
678 ['eeeee', 'j', None]],
691 ['eeeee', 'j', None]],
679 {'columns_numbers': 3,
692 {'columns_numbers': 3,
680 'columns_width': [5, 1, 1],
693 'columns_width': [5, 1, 1],
681 'optimal_separator_width': 2,
694 'optimal_separator_width': 2,
682 'rows_numbers': 5})
695 'rows_numbers': 5})
683
696
684 """
697 """
685 info = _find_optimal(map(len, items), *args, **kwargs)
698 info = _find_optimal(map(len, items), *args, **kwargs)
686 nrow, ncol = info['rows_numbers'], info['columns_numbers']
699 nrow, ncol = info['rows_numbers'], info['columns_numbers']
687 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
700 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
688
701
689
702
690 def columnize(items, separator=' ', displaywidth=80):
703 def columnize(items, separator=' ', displaywidth=80):
691 """ Transform a list of strings into a single string with columns.
704 """ Transform a list of strings into a single string with columns.
692
705
693 Parameters
706 Parameters
694 ----------
707 ----------
695 items : sequence of strings
708 items : sequence of strings
696 The strings to process.
709 The strings to process.
697
710
698 separator : str, optional [default is two spaces]
711 separator : str, optional [default is two spaces]
699 The string that separates columns.
712 The string that separates columns.
700
713
701 displaywidth : int, optional [default is 80]
714 displaywidth : int, optional [default is 80]
702 Width of the display in number of characters.
715 Width of the display in number of characters.
703
716
704 Returns
717 Returns
705 -------
718 -------
706 The formatted string.
719 The formatted string.
707 """
720 """
708 if not items :
721 if not items :
709 return '\n'
722 return '\n'
710 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
723 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
711 fmatrix = [filter(None, x) for x in matrix]
724 fmatrix = [filter(None, x) for x in matrix]
712 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
725 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
713 return '\n'.join(map(sjoin, fmatrix))+'\n'
726 return '\n'.join(map(sjoin, fmatrix))+'\n'
General Comments 0
You need to be logged in to leave comments. Login now