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