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