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