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