##// END OF EJS Templates
Fixes "bugs with mutable defaults args", thanks @Carreau
Jonathan Frederic -
Show More
@@ -1,361 +1,361 b''
1 """This module defines Exporter, a highly configurable converter
1 """This module defines Exporter, a highly configurable converter
2 that uses Jinja2 to export notebook files into different formats.
2 that uses Jinja2 to export notebook files into different formats.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import io
20 import io
21 import os
21 import os
22 import inspect
22 import inspect
23 import types
23 import types
24 from copy import deepcopy
24 from copy import deepcopy
25
25
26 # other libs/dependencies
26 # other libs/dependencies
27 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
27 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
28
28
29 # IPython imports
29 # IPython imports
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.config import Config
31 from IPython.config import Config
32 from IPython.nbformat import current as nbformat
32 from IPython.nbformat import current as nbformat
33 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
33 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
34 from IPython.utils.importstring import import_item
34 from IPython.utils.importstring import import_item
35 from IPython.utils.text import indent
35 from IPython.utils.text import indent
36
36
37 from IPython.nbconvert import filters
37 from IPython.nbconvert import filters
38 from IPython.nbconvert import transformers
38 from IPython.nbconvert import transformers
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Globals and constants
41 # Globals and constants
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 #Jinja2 extensions to load.
44 #Jinja2 extensions to load.
45 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
45 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
46
46
47 default_filters = {
47 default_filters = {
48 'indent': indent,
48 'indent': indent,
49 'markdown': filters.markdown2html,
49 'markdown': filters.markdown2html,
50 'ansi2html': filters.ansi2html,
50 'ansi2html': filters.ansi2html,
51 'filter_data_type': filters.DataTypeFilter,
51 'filter_data_type': filters.DataTypeFilter,
52 'get_lines': filters.get_lines,
52 'get_lines': filters.get_lines,
53 'highlight': filters.highlight,
53 'highlight': filters.highlight,
54 'highlight2html': filters.highlight,
54 'highlight2html': filters.highlight,
55 'highlight2latex': filters.highlight2latex,
55 'highlight2latex': filters.highlight2latex,
56 'markdown2latex': filters.markdown2latex,
56 'markdown2latex': filters.markdown2latex,
57 'markdown2rst': filters.markdown2rst,
57 'markdown2rst': filters.markdown2rst,
58 'pycomment': filters.python_comment,
58 'pycomment': filters.python_comment,
59 'rm_ansi': filters.remove_ansi,
59 'rm_ansi': filters.remove_ansi,
60 'rm_dollars': filters.strip_dollars,
60 'rm_dollars': filters.strip_dollars,
61 'rm_fake': filters.rm_fake,
61 'rm_fake': filters.rm_fake,
62 'ansi2latex': filters.ansi2latex,
62 'ansi2latex': filters.ansi2latex,
63 'rm_math_space': filters.rm_math_space,
63 'rm_math_space': filters.rm_math_space,
64 'wrap': filters.wrap
64 'wrap': filters.wrap
65 }
65 }
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Class
68 # Class
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 class Exporter(Configurable):
71 class Exporter(Configurable):
72 """
72 """
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 to output new formats. Inherit from this class if you are creating a new
74 to output new formats. Inherit from this class if you are creating a new
75 template type along with new filters/transformers. If the filters/
75 template type along with new filters/transformers. If the filters/
76 transformers provided by default suffice, there is no need to inherit from
76 transformers provided by default suffice, there is no need to inherit from
77 this class. Instead, override the template_file and file_extension
77 this class. Instead, override the template_file and file_extension
78 traits via a config file.
78 traits via a config file.
79
79
80 {filters}
80 {filters}
81 """
81 """
82
82
83 # finish the docstring
83 # finish the docstring
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85
85
86
86
87 template_file = Unicode(
87 template_file = Unicode(
88 '', config=True,
88 '', config=True,
89 help="Name of the template file to use")
89 help="Name of the template file to use")
90
90
91 file_extension = Unicode(
91 file_extension = Unicode(
92 'txt', config=True,
92 'txt', config=True,
93 help="Extension of the file that should be written to disk"
93 help="Extension of the file that should be written to disk"
94 )
94 )
95
95
96 template_path = Unicode(
96 template_path = Unicode(
97 os.path.join("..", "templates"), config=True,
97 os.path.join("..", "templates"), config=True,
98 help="Path where the template files are located.")
98 help="Path where the template files are located.")
99
99
100 template_skeleton_path = Unicode(
100 template_skeleton_path = Unicode(
101 os.path.join("..", "templates", "skeleton"), config=True,
101 os.path.join("..", "templates", "skeleton"), config=True,
102 help="Path where the template skeleton files are located.")
102 help="Path where the template skeleton files are located.")
103
103
104 #Jinja block definitions
104 #Jinja block definitions
105 jinja_comment_block_start = Unicode("", config=True)
105 jinja_comment_block_start = Unicode("", config=True)
106 jinja_comment_block_end = Unicode("", config=True)
106 jinja_comment_block_end = Unicode("", config=True)
107 jinja_variable_block_start = Unicode("", config=True)
107 jinja_variable_block_start = Unicode("", config=True)
108 jinja_variable_block_end = Unicode("", config=True)
108 jinja_variable_block_end = Unicode("", config=True)
109 jinja_logic_block_start = Unicode("", config=True)
109 jinja_logic_block_start = Unicode("", config=True)
110 jinja_logic_block_end = Unicode("", config=True)
110 jinja_logic_block_end = Unicode("", config=True)
111
111
112 #Extension that the template files use.
112 #Extension that the template files use.
113 template_extension = Unicode(".tpl", config=True)
113 template_extension = Unicode(".tpl", config=True)
114
114
115 #Configurability, allows the user to easily add filters and transformers.
115 #Configurability, allows the user to easily add filters and transformers.
116 transformers = List(config=True,
116 transformers = List(config=True,
117 help="""List of transformers, by name or namespace, to enable.""")
117 help="""List of transformers, by name or namespace, to enable.""")
118 filters = Dict(config=True,
118 filters = Dict(config=True,
119 help="""Dictionary of filters, by name and namespace, to add to the Jinja
119 help="""Dictionary of filters, by name and namespace, to add to the Jinja
120 environment.""")
120 environment.""")
121
121
122 def __init__(self, config=None, extra_loaders=None, **kw):
122 def __init__(self, config=None, extra_loaders=None, **kw):
123 """
123 """
124 Public constructor
124 Public constructor
125
125
126 Parameters
126 Parameters
127 ----------
127 ----------
128 config : config
128 config : config
129 User configuration instance.
129 User configuration instance.
130 extra_loaders : list[of Jinja Loaders]
130 extra_loaders : list[of Jinja Loaders]
131 ordered list of Jinja loder to find templates. Will be tried in order
131 ordered list of Jinja loder to find templates. Will be tried in order
132 before the default FileSysteme ones.
132 before the default FileSysteme ones.
133 """
133 """
134
134
135 #Call the base class constructor
135 #Call the base class constructor
136 c = self.default_config
136 c = self.default_config
137 if config:
137 if config:
138 c.merge(config)
138 c.merge(config)
139
139
140 super(Exporter, self).__init__(config=c, **kw)
140 super(Exporter, self).__init__(config=c, **kw)
141
141
142 #Standard environment
142 #Standard environment
143 self._init_environment(extra_loaders=extra_loaders)
143 self._init_environment(extra_loaders=extra_loaders)
144
144
145 #Add transformers
145 #Add transformers
146 self._transformers = []
146 self._transformers = []
147 self._register_transformers()
147 self._register_transformers()
148
148
149 #Add filters to the Jinja2 environment
149 #Add filters to the Jinja2 environment
150 self._register_filters()
150 self._register_filters()
151
151
152 #Load user transformers. Enabled by default.
152 #Load user transformers. Enabled by default.
153 if self.transformers:
153 if self.transformers:
154 for transformer in self.transformers:
154 for transformer in self.transformers:
155 self.register_transformer(transformer, True)
155 self.register_transformer(transformer, True)
156
156
157 #Load user filters. Overwrite existing filters if need be.
157 #Load user filters. Overwrite existing filters if need be.
158 if self.filters:
158 if self.filters:
159 for key, user_filter in self.filters.iteritems():
159 for key, user_filter in self.filters.iteritems():
160 self.register_filter(key, user_filter)
160 self.register_filter(key, user_filter)
161
161
162
162
163 @property
163 @property
164 def default_config(self):
164 def default_config(self):
165 return Config()
165 return Config()
166
166
167
167
168 def from_notebook_node(self, nb, resources={}, **kw):
168 def from_notebook_node(self, nb, resources=None, **kw):
169 """
169 """
170 Convert a notebook from a notebook node instance.
170 Convert a notebook from a notebook node instance.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 nb : Notebook node
174 nb : Notebook node
175 resources : dict (**kw)
175 resources : dict (**kw)
176 of additional resources that can be accessed read/write by
176 of additional resources that can be accessed read/write by
177 transformers and filters.
177 transformers and filters.
178 """
178 """
179 nb_copy = deepcopy(nb)
179 nb_copy = deepcopy(nb)
180
180
181 #Preprocess
181 #Preprocess
182 nb_copy, resources = self._preprocess(nb_copy, resources)
182 nb_copy, resources = self._preprocess(nb_copy, resources)
183
183
184 #Convert
184 #Convert
185 self.template = self.environment.get_template(self.template_file + self.template_extension)
185 self.template = self.environment.get_template(self.template_file + self.template_extension)
186 output = self.template.render(nb=nb_copy, resources=resources)
186 output = self.template.render(nb=nb_copy, resources=resources)
187
187
188 #Set output extension in resources dict
188 #Set output extension in resources dict
189 resources['output_extension'] = self.file_extension
189 resources['output_extension'] = self.file_extension
190 return output, resources
190 return output, resources
191
191
192
192
193 def from_filename(self, filename, resources={}, **kw):
193 def from_filename(self, filename, resources=None **kw):
194 """
194 """
195 Convert a notebook from a notebook file.
195 Convert a notebook from a notebook file.
196
196
197 Parameters
197 Parameters
198 ----------
198 ----------
199 filename : str
199 filename : str
200 Full filename of the notebook file to open and convert.
200 Full filename of the notebook file to open and convert.
201 """
201 """
202
202
203 with io.open(filename) as f:
203 with io.open(filename) as f:
204 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
204 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
205
205
206
206
207 def from_file(self, file_stream, resources={}, **kw):
207 def from_file(self, file_stream, resources=None, **kw):
208 """
208 """
209 Convert a notebook from a notebook file.
209 Convert a notebook from a notebook file.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 file_stream : file-like object
213 file_stream : file-like object
214 Notebook file-like object to convert.
214 Notebook file-like object to convert.
215 """
215 """
216 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
216 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
217
217
218
218
219 def register_transformer(self, transformer, enabled=None):
219 def register_transformer(self, transformer, enabled=None):
220 """
220 """
221 Register a transformer.
221 Register a transformer.
222 Transformers are classes that act upon the notebook before it is
222 Transformers are classes that act upon the notebook before it is
223 passed into the Jinja templating engine. Transformers are also
223 passed into the Jinja templating engine. Transformers are also
224 capable of passing additional information to the Jinja
224 capable of passing additional information to the Jinja
225 templating engine.
225 templating engine.
226
226
227 Parameters
227 Parameters
228 ----------
228 ----------
229 transformer : transformer
229 transformer : transformer
230 """
230 """
231
231
232 #Handle transformer's registration based on it's type
232 #Handle transformer's registration based on it's type
233 if inspect.isfunction(transformer):
233 if inspect.isfunction(transformer):
234 #Transformer is a function, no need to construct it.
234 #Transformer is a function, no need to construct it.
235 self._transformers.append(transformer)
235 self._transformers.append(transformer)
236 return transformer
236 return transformer
237
237
238 elif isinstance(transformer, types.StringTypes):
238 elif isinstance(transformer, types.StringTypes):
239 #Transformer is a string, import the namespace and recursively call
239 #Transformer is a string, import the namespace and recursively call
240 #this register_transformer method
240 #this register_transformer method
241 transformer_cls = import_item(DottedObjectName(transformer))
241 transformer_cls = import_item(DottedObjectName(transformer))
242 return self.register_transformer(transformer_cls, enabled=None)
242 return self.register_transformer(transformer_cls, enabled=None)
243
243
244 elif isinstance(transformer, MetaHasTraits):
244 elif isinstance(transformer, MetaHasTraits):
245 #Transformer is configurable. Make sure to pass in new default for
245 #Transformer is configurable. Make sure to pass in new default for
246 #the enabled flag if one was specified.
246 #the enabled flag if one was specified.
247 c = Config()
247 c = Config()
248 if not enabled is None:
248 if not enabled is None:
249 c = Config({transformer.__name__: {'enabled': enabled}})
249 c = Config({transformer.__name__: {'enabled': enabled}})
250 c.merge(self.config)
250 c.merge(self.config)
251 transformer_instance = transformer(config=c)
251 transformer_instance = transformer(config=c)
252
252
253 else:
253 else:
254 #Transformer is not configurable, construct it
254 #Transformer is not configurable, construct it
255 transformer_instance = transformer()
255 transformer_instance = transformer()
256
256
257 #Register and return the transformer.
257 #Register and return the transformer.
258 self._transformers.append(transformer_instance)
258 self._transformers.append(transformer_instance)
259 return transformer_instance
259 return transformer_instance
260
260
261
261
262 def register_filter(self, name, filter):
262 def register_filter(self, name, filter):
263 """
263 """
264 Register a filter.
264 Register a filter.
265 A filter is a function that accepts and acts on one string.
265 A filter is a function that accepts and acts on one string.
266 The filters are accesible within the Jinja templating engine.
266 The filters are accesible within the Jinja templating engine.
267
267
268 Parameters
268 Parameters
269 ----------
269 ----------
270 name : str
270 name : str
271 name to give the filter in the Jinja engine
271 name to give the filter in the Jinja engine
272 filter : filter
272 filter : filter
273 """
273 """
274 if inspect.isfunction(filter):
274 if inspect.isfunction(filter):
275 self.environment.filters[name] = filter
275 self.environment.filters[name] = filter
276 elif isinstance(filter, types.StringTypes):
276 elif isinstance(filter, types.StringTypes):
277 filter_cls = import_item(DottedObjectName(filter))
277 filter_cls = import_item(DottedObjectName(filter))
278 self.register_filter(name, filter_cls)
278 self.register_filter(name, filter_cls)
279 elif isinstance(filter, MetaHasTraits):
279 elif isinstance(filter, MetaHasTraits):
280 self.environment.filters[name] = filter(config=self.config)
280 self.environment.filters[name] = filter(config=self.config)
281 else:
281 else:
282 self.environment.filters[name] = filter()
282 self.environment.filters[name] = filter()
283 return self.environment.filters[name]
283 return self.environment.filters[name]
284
284
285
285
286 def _register_transformers(self):
286 def _register_transformers(self):
287 """
287 """
288 Register all of the transformers needed for this exporter, disabled
288 Register all of the transformers needed for this exporter, disabled
289 unless specified explicitly.
289 unless specified explicitly.
290 """
290 """
291
291
292 self.register_transformer(transformers.coalesce_streams)
292 self.register_transformer(transformers.coalesce_streams)
293 self.register_transformer(transformers.ExtractFigureTransformer)
293 self.register_transformer(transformers.ExtractFigureTransformer)
294
294
295
295
296 def _register_filters(self):
296 def _register_filters(self):
297 """
297 """
298 Register all of the filters required for the exporter.
298 Register all of the filters required for the exporter.
299 """
299 """
300 for key, value in default_filters.iteritems():
300 for key, value in default_filters.iteritems():
301 self.register_filter(key, value)
301 self.register_filter(key, value)
302
302
303
303
304 def _init_environment(self, extra_loaders=None):
304 def _init_environment(self, extra_loaders=None):
305 """
305 """
306 Create the Jinja templating environment.
306 Create the Jinja templating environment.
307 """
307 """
308 here = os.path.dirname(os.path.realpath(__file__))
308 here = os.path.dirname(os.path.realpath(__file__))
309 loaders = []
309 loaders = []
310 if extra_loaders:
310 if extra_loaders:
311 loaders.extend(extra_loaders)
311 loaders.extend(extra_loaders)
312
312
313 loaders.append(FileSystemLoader([
313 loaders.append(FileSystemLoader([
314 os.path.join(here, self.template_path),
314 os.path.join(here, self.template_path),
315 os.path.join(here, self.template_skeleton_path),
315 os.path.join(here, self.template_skeleton_path),
316 ]))
316 ]))
317
317
318 self.environment = Environment(
318 self.environment = Environment(
319 loader= ChoiceLoader(loaders),
319 loader= ChoiceLoader(loaders),
320 extensions=JINJA_EXTENSIONS
320 extensions=JINJA_EXTENSIONS
321 )
321 )
322
322
323 #Set special Jinja2 syntax that will not conflict with latex.
323 #Set special Jinja2 syntax that will not conflict with latex.
324 if self.jinja_logic_block_start:
324 if self.jinja_logic_block_start:
325 self.environment.block_start_string = self.jinja_logic_block_start
325 self.environment.block_start_string = self.jinja_logic_block_start
326 if self.jinja_logic_block_end:
326 if self.jinja_logic_block_end:
327 self.environment.block_end_string = self.jinja_logic_block_end
327 self.environment.block_end_string = self.jinja_logic_block_end
328 if self.jinja_variable_block_start:
328 if self.jinja_variable_block_start:
329 self.environment.variable_start_string = self.jinja_variable_block_start
329 self.environment.variable_start_string = self.jinja_variable_block_start
330 if self.jinja_variable_block_end:
330 if self.jinja_variable_block_end:
331 self.environment.variable_end_string = self.jinja_variable_block_end
331 self.environment.variable_end_string = self.jinja_variable_block_end
332 if self.jinja_comment_block_start:
332 if self.jinja_comment_block_start:
333 self.environment.comment_start_string = self.jinja_comment_block_start
333 self.environment.comment_start_string = self.jinja_comment_block_start
334 if self.jinja_comment_block_end:
334 if self.jinja_comment_block_end:
335 self.environment.comment_end_string = self.jinja_comment_block_end
335 self.environment.comment_end_string = self.jinja_comment_block_end
336
336
337
337
338 def _preprocess(self, nb, resources):
338 def _preprocess(self, nb, resources):
339 """
339 """
340 Preprocess the notebook before passing it into the Jinja engine.
340 Preprocess the notebook before passing it into the Jinja engine.
341 To preprocess the notebook is to apply all of the
341 To preprocess the notebook is to apply all of the
342
342
343 Parameters
343 Parameters
344 ----------
344 ----------
345 nb : notebook node
345 nb : notebook node
346 notebook that is being exported.
346 notebook that is being exported.
347 resources : a dict of additional resources that
347 resources : a dict of additional resources that
348 can be accessed read/write by transformers
348 can be accessed read/write by transformers
349 and filters.
349 and filters.
350 """
350 """
351
351
352 # Do a deepcopy first,
352 # Do a deepcopy first,
353 # we are never safe enough with what the transformers could do.
353 # we are never safe enough with what the transformers could do.
354 nbc = deepcopy(nb)
354 nbc = deepcopy(nb)
355 resc = deepcopy(resources)
355 resc = deepcopy(resources)
356
356
357 #Run each transformer on the notebook. Carry the output along
357 #Run each transformer on the notebook. Carry the output along
358 #to each transformer
358 #to each transformer
359 for transformer in self._transformers:
359 for transformer in self._transformers:
360 nbc, resc = transformer(nbc, resc)
360 nbc, resc = transformer(nbc, resc)
361 return nbc, resc
361 return nbc, resc
General Comments 0
You need to be logged in to leave comments. Login now