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