##// END OF EJS Templates
Merge pull request #3720 from jstenar/nbconvert-datefmt...
Min RK -
r11762:8d0a6b70 merge
parent child Browse files
Show More
@@ -1,446 +1,446 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
29 29
30 30 # IPython imports
31 31 from IPython.config.configurable import Configurable
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
35 35 from IPython.utils.importstring import import_item
36 36 from IPython.utils.text import indent
37 37 from IPython.utils import py3compat
38 38
39 39 from IPython.nbconvert import transformers as nbtransformers
40 40 from IPython.nbconvert import filters
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Globals and constants
44 44 #-----------------------------------------------------------------------------
45 45
46 46 #Jinja2 extensions to load.
47 47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
48 48
49 49 default_filters = {
50 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 'markdown2latex': filters.markdown2latex,
59 59 'markdown2rst': filters.markdown2rst,
60 60 'comment_lines': filters.comment_lines,
61 61 'strip_ansi': filters.strip_ansi,
62 62 'strip_dollars': filters.strip_dollars,
63 63 'strip_files_prefix': filters.strip_files_prefix,
64 64 'html2text' : filters.html2text,
65 65 'add_anchor': filters.add_anchor,
66 66 'ansi2latex': filters.ansi2latex,
67 67 'strip_math_space': filters.strip_math_space,
68 68 'wrap_text': filters.wrap_text,
69 69 'escape_latex': filters.escape_latex
70 70 }
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # Class
74 74 #-----------------------------------------------------------------------------
75 75
76 76 class ResourcesDict(collections.defaultdict):
77 77 def __missing__(self, key):
78 78 return ''
79 79
80 80
81 81 class Exporter(Configurable):
82 82 """
83 83 Exports notebooks into other file formats. Uses Jinja 2 templating engine
84 84 to output new formats. Inherit from this class if you are creating a new
85 85 template type along with new filters/transformers. If the filters/
86 86 transformers provided by default suffice, there is no need to inherit from
87 87 this class. Instead, override the template_file and file_extension
88 88 traits via a config file.
89 89
90 90 {filters}
91 91 """
92 92
93 93 # finish the docstring
94 94 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
95 95
96 96
97 97 template_file = Unicode(
98 98 '', config=True,
99 99 help="Name of the template file to use")
100 100
101 101 file_extension = Unicode(
102 102 'txt', config=True,
103 103 help="Extension of the file that should be written to disk"
104 104 )
105 105
106 106 template_path = List(['.'], config=True)
107 107
108 108 default_template_path = Unicode(
109 109 os.path.join("..", "templates"),
110 110 help="Path where the template files are located.")
111 111
112 112 template_skeleton_path = Unicode(
113 113 os.path.join("..", "templates", "skeleton"),
114 114 help="Path where the template skeleton files are located.")
115 115
116 116 #Jinja block definitions
117 117 jinja_comment_block_start = Unicode("", config=True)
118 118 jinja_comment_block_end = Unicode("", config=True)
119 119 jinja_variable_block_start = Unicode("", config=True)
120 120 jinja_variable_block_end = Unicode("", config=True)
121 121 jinja_logic_block_start = Unicode("", config=True)
122 122 jinja_logic_block_end = Unicode("", config=True)
123 123
124 124 #Extension that the template files use.
125 125 template_extension = Unicode(".tpl", config=True)
126 126
127 127 #Configurability, allows the user to easily add filters and transformers.
128 128 transformers = List(config=True,
129 129 help="""List of transformers, by name or namespace, to enable.""")
130 130
131 131 filters = Dict(config=True,
132 132 help="""Dictionary of filters, by name and namespace, to add to the Jinja
133 133 environment.""")
134 134
135 135 default_transformers = List([nbtransformers.coalesce_streams,
136 136 nbtransformers.SVG2PDFTransformer,
137 137 nbtransformers.ExtractOutputTransformer,
138 138 nbtransformers.CSSHTMLHeaderTransformer,
139 139 nbtransformers.RevealHelpTransformer,
140 140 nbtransformers.LatexTransformer,
141 141 nbtransformers.SphinxTransformer],
142 142 config=True,
143 143 help="""List of transformers available by default, by name, namespace,
144 144 instance, or type.""")
145 145
146 146
147 147 def __init__(self, config=None, extra_loaders=None, **kw):
148 148 """
149 149 Public constructor
150 150
151 151 Parameters
152 152 ----------
153 153 config : config
154 154 User configuration instance.
155 155 extra_loaders : list[of Jinja Loaders]
156 156 ordered list of Jinja loder to find templates. Will be tried in order
157 157 before the default FileSysteme ones.
158 158 """
159 159
160 160 #Call the base class constructor
161 161 c = self.default_config
162 162 if config:
163 163 c.merge(config)
164 164
165 165 super(Exporter, self).__init__(config=c, **kw)
166 166
167 167 #Init
168 168 self._init_environment(extra_loaders=extra_loaders)
169 169 self._init_transformers()
170 170 self._init_filters()
171 171
172 172
173 173 @property
174 174 def default_config(self):
175 175 return Config()
176 176
177 177
178 178 def from_notebook_node(self, nb, resources=None, **kw):
179 179 """
180 180 Convert a notebook from a notebook node instance.
181 181
182 182 Parameters
183 183 ----------
184 184 nb : Notebook node
185 185 resources : dict (**kw)
186 186 of additional resources that can be accessed read/write by
187 187 transformers and filters.
188 188 """
189 189 nb_copy = copy.deepcopy(nb)
190 190 resources = self._init_resources(resources)
191 191
192 192 #Preprocess
193 193 nb_copy, resources = self._transform(nb_copy, resources)
194 194
195 195 #Convert
196 196 self.template = self.environment.get_template(self.template_file + self.template_extension)
197 197 output = self.template.render(nb=nb_copy, resources=resources)
198 198 return output, resources
199 199
200 200
201 201 def from_filename(self, filename, resources=None, **kw):
202 202 """
203 203 Convert a notebook from a notebook file.
204 204
205 205 Parameters
206 206 ----------
207 207 filename : str
208 208 Full filename of the notebook file to open and convert.
209 209 """
210 210
211 211 #Pull the metadata from the filesystem.
212 212 if resources is None:
213 213 resources = ResourcesDict()
214 214 if not 'metadata' in resources or resources['metadata'] == '':
215 215 resources['metadata'] = ResourcesDict()
216 216 basename = os.path.basename(filename)
217 217 notebook_name = basename[:basename.rfind('.')]
218 218 resources['metadata']['name'] = notebook_name
219 219
220 220 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
221 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
221 resources['metadata']['modified_date'] = modified_date.strftime("%B %d, %Y")
222 222
223 223 with io.open(filename) as f:
224 224 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
225 225
226 226
227 227 def from_file(self, file_stream, resources=None, **kw):
228 228 """
229 229 Convert a notebook from a notebook file.
230 230
231 231 Parameters
232 232 ----------
233 233 file_stream : file-like object
234 234 Notebook file-like object to convert.
235 235 """
236 236 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
237 237
238 238
239 239 def register_transformer(self, transformer, enabled=False):
240 240 """
241 241 Register a transformer.
242 242 Transformers are classes that act upon the notebook before it is
243 243 passed into the Jinja templating engine. Transformers are also
244 244 capable of passing additional information to the Jinja
245 245 templating engine.
246 246
247 247 Parameters
248 248 ----------
249 249 transformer : transformer
250 250 """
251 251 if transformer is None:
252 252 raise TypeError('transformer')
253 253 isclass = isinstance(transformer, type)
254 254 constructed = not isclass
255 255
256 256 #Handle transformer's registration based on it's type
257 257 if constructed and isinstance(transformer, py3compat.string_types):
258 258 #Transformer is a string, import the namespace and recursively call
259 259 #this register_transformer method
260 260 transformer_cls = import_item(transformer)
261 261 return self.register_transformer(transformer_cls, enabled)
262 262
263 263 if constructed and hasattr(transformer, '__call__'):
264 264 #Transformer is a function, no need to construct it.
265 265 #Register and return the transformer.
266 266 if enabled:
267 267 transformer.enabled = True
268 268 self._transformers.append(transformer)
269 269 return transformer
270 270
271 271 elif isclass and isinstance(transformer, MetaHasTraits):
272 272 #Transformer is configurable. Make sure to pass in new default for
273 273 #the enabled flag if one was specified.
274 274 self.register_transformer(transformer(parent=self), enabled)
275 275
276 276 elif isclass:
277 277 #Transformer is not configurable, construct it
278 278 self.register_transformer(transformer(), enabled)
279 279
280 280 else:
281 281 #Transformer is an instance of something without a __call__
282 282 #attribute.
283 283 raise TypeError('transformer')
284 284
285 285
286 286 def register_filter(self, name, jinja_filter):
287 287 """
288 288 Register a filter.
289 289 A filter is a function that accepts and acts on one string.
290 290 The filters are accesible within the Jinja templating engine.
291 291
292 292 Parameters
293 293 ----------
294 294 name : str
295 295 name to give the filter in the Jinja engine
296 296 filter : filter
297 297 """
298 298 if jinja_filter is None:
299 299 raise TypeError('filter')
300 300 isclass = isinstance(jinja_filter, type)
301 301 constructed = not isclass
302 302
303 303 #Handle filter's registration based on it's type
304 304 if constructed and isinstance(jinja_filter, py3compat.string_types):
305 305 #filter is a string, import the namespace and recursively call
306 306 #this register_filter method
307 307 filter_cls = import_item(jinja_filter)
308 308 return self.register_filter(name, filter_cls)
309 309
310 310 if constructed and hasattr(jinja_filter, '__call__'):
311 311 #filter is a function, no need to construct it.
312 312 self.environment.filters[name] = jinja_filter
313 313 return jinja_filter
314 314
315 315 elif isclass and isinstance(jinja_filter, MetaHasTraits):
316 316 #filter is configurable. Make sure to pass in new default for
317 317 #the enabled flag if one was specified.
318 318 filter_instance = jinja_filter(parent=self)
319 319 self.register_filter(name, filter_instance )
320 320
321 321 elif isclass:
322 322 #filter is not configurable, construct it
323 323 filter_instance = jinja_filter()
324 324 self.register_filter(name, filter_instance)
325 325
326 326 else:
327 327 #filter is an instance of something without a __call__
328 328 #attribute.
329 329 raise TypeError('filter')
330 330
331 331
332 332 def _init_environment(self, extra_loaders=None):
333 333 """
334 334 Create the Jinja templating environment.
335 335 """
336 336 here = os.path.dirname(os.path.realpath(__file__))
337 337 loaders = []
338 338 if extra_loaders:
339 339 loaders.extend(extra_loaders)
340 340
341 341 paths = self.template_path
342 342 paths.extend([os.path.join(here, self.default_template_path),
343 343 os.path.join(here, self.template_skeleton_path)])
344 344 loaders.append(FileSystemLoader(paths))
345 345
346 346 self.environment = Environment(
347 347 loader= ChoiceLoader(loaders),
348 348 extensions=JINJA_EXTENSIONS
349 349 )
350 350
351 351 #Set special Jinja2 syntax that will not conflict with latex.
352 352 if self.jinja_logic_block_start:
353 353 self.environment.block_start_string = self.jinja_logic_block_start
354 354 if self.jinja_logic_block_end:
355 355 self.environment.block_end_string = self.jinja_logic_block_end
356 356 if self.jinja_variable_block_start:
357 357 self.environment.variable_start_string = self.jinja_variable_block_start
358 358 if self.jinja_variable_block_end:
359 359 self.environment.variable_end_string = self.jinja_variable_block_end
360 360 if self.jinja_comment_block_start:
361 361 self.environment.comment_start_string = self.jinja_comment_block_start
362 362 if self.jinja_comment_block_end:
363 363 self.environment.comment_end_string = self.jinja_comment_block_end
364 364
365 365
366 366 def _init_transformers(self):
367 367 """
368 368 Register all of the transformers needed for this exporter, disabled
369 369 unless specified explicitly.
370 370 """
371 371 self._transformers = []
372 372
373 373 #Load default transformers (not necessarly enabled by default).
374 374 if self.default_transformers:
375 375 for transformer in self.default_transformers:
376 376 self.register_transformer(transformer)
377 377
378 378 #Load user transformers. Enable by default.
379 379 if self.transformers:
380 380 for transformer in self.transformers:
381 381 self.register_transformer(transformer, enabled=True)
382 382
383 383
384 384 def _init_filters(self):
385 385 """
386 386 Register all of the filters required for the exporter.
387 387 """
388 388
389 389 #Add default filters to the Jinja2 environment
390 390 for key, value in default_filters.items():
391 391 self.register_filter(key, value)
392 392
393 393 #Load user filters. Overwrite existing filters if need be.
394 394 if self.filters:
395 395 for key, user_filter in self.filters.items():
396 396 self.register_filter(key, user_filter)
397 397
398 398
399 399 def _init_resources(self, resources):
400 400
401 401 #Make sure the resources dict is of ResourcesDict type.
402 402 if resources is None:
403 403 resources = ResourcesDict()
404 404 if not isinstance(resources, ResourcesDict):
405 405 new_resources = ResourcesDict()
406 406 new_resources.update(resources)
407 407 resources = new_resources
408 408
409 409 #Make sure the metadata extension exists in resources
410 410 if 'metadata' in resources:
411 411 if not isinstance(resources['metadata'], ResourcesDict):
412 412 resources['metadata'] = ResourcesDict(resources['metadata'])
413 413 else:
414 414 resources['metadata'] = ResourcesDict()
415 415 if not resources['metadata']['name']:
416 416 resources['metadata']['name'] = 'Notebook'
417 417
418 418 #Set the output extension
419 419 resources['output_extension'] = self.file_extension
420 420 return resources
421 421
422 422
423 423 def _transform(self, nb, resources):
424 424 """
425 425 Preprocess the notebook before passing it into the Jinja engine.
426 426 To preprocess the notebook is to apply all of the
427 427
428 428 Parameters
429 429 ----------
430 430 nb : notebook node
431 431 notebook that is being exported.
432 432 resources : a dict of additional resources that
433 433 can be accessed read/write by transformers
434 434 and filters.
435 435 """
436 436
437 437 # Do a copy.deepcopy first,
438 438 # we are never safe enough with what the transformers could do.
439 439 nbc = copy.deepcopy(nb)
440 440 resc = copy.deepcopy(resources)
441 441
442 442 #Run each transformer on the notebook. Carry the output along
443 443 #to each transformer
444 444 for transformer in self._transformers:
445 445 nbc, resc = transformer(nbc, resc)
446 446 return nbc, resc
General Comments 0
You need to be logged in to leave comments. Login now