##// END OF EJS Templates
to clean (transformer deepcopy)
Matthias BUSSONNIER -
Show More
@@ -1,344 +1,347 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 from copy import deepcopy
23 from copy import deepcopy
24
24
25 # IPython imports
25 # IPython imports
26 from IPython.config.configurable import Configurable
26 from IPython.config.configurable import Configurable
27 from IPython.config import Config
27 from IPython.config import Config
28 from IPython.nbformat import current as nbformat
28 from IPython.nbformat import current as nbformat
29 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Bool
29 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Bool
30 from IPython.utils.text import indent
30 from IPython.utils.text import indent
31
31
32 # other libs/dependencies
32 # other libs/dependencies
33 from jinja2 import Environment, FileSystemLoader
33 from jinja2 import Environment, FileSystemLoader
34 from markdown import markdown
34 from markdown import markdown
35
35
36 # local import
36 # local import
37 import nbconvert.filters.strings
37 import nbconvert.filters.strings
38 import nbconvert.filters.markdown
38 import nbconvert.filters.markdown
39 import nbconvert.filters.latex
39 import nbconvert.filters.latex
40 import nbconvert.filters.datatypefilter
40 import nbconvert.filters.datatypefilter
41 import nbconvert.filters.highlight
41 import nbconvert.filters.highlight
42 import nbconvert.filters.ansi
42 import nbconvert.filters.ansi
43
43
44 import nbconvert.transformers.extractfigure
44 import nbconvert.transformers.extractfigure
45 import nbconvert.transformers.coalescestreams
45 import nbconvert.transformers.coalescestreams
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Globals and constants
48 # Globals and constants
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 #Jinja2 extensions to load.
51 #Jinja2 extensions to load.
52 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
52 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
53
53
54 default_filters = {
54 default_filters = {
55 'indent': indent,
55 'indent': indent,
56 'markdown': markdown,
56 'markdown': markdown,
57 'ansi2html': nbconvert.filters.ansi.ansi2html,
57 'ansi2html': nbconvert.filters.ansi.ansi2html,
58 'filter_data_type': nbconvert.filters.datatypefilter.DataTypeFilter,
58 'filter_data_type': nbconvert.filters.datatypefilter.DataTypeFilter,
59 'get_lines': nbconvert.filters.strings.get_lines,
59 'get_lines': nbconvert.filters.strings.get_lines,
60 'highlight': nbconvert.filters.highlight.highlight,
60 'highlight': nbconvert.filters.highlight.highlight,
61 'highlight2html': nbconvert.filters.highlight.highlight,
61 'highlight2html': nbconvert.filters.highlight.highlight,
62 'highlight2latex': nbconvert.filters.highlight.highlight2latex,
62 'highlight2latex': nbconvert.filters.highlight.highlight2latex,
63 'markdown2latex': nbconvert.filters.markdown.markdown2latex,
63 'markdown2latex': nbconvert.filters.markdown.markdown2latex,
64 'markdown2rst': nbconvert.filters.markdown.markdown2rst,
64 'markdown2rst': nbconvert.filters.markdown.markdown2rst,
65 'pycomment': nbconvert.filters.strings.python_comment,
65 'pycomment': nbconvert.filters.strings.python_comment,
66 'rm_ansi': nbconvert.filters.ansi.remove_ansi,
66 'rm_ansi': nbconvert.filters.ansi.remove_ansi,
67 'rm_dollars': nbconvert.filters.strings.strip_dollars,
67 'rm_dollars': nbconvert.filters.strings.strip_dollars,
68 'rm_fake': nbconvert.filters.strings.rm_fake,
68 'rm_fake': nbconvert.filters.strings.rm_fake,
69 'rm_math_space': nbconvert.filters.latex.rm_math_space,
69 'rm_math_space': nbconvert.filters.latex.rm_math_space,
70 'wrap': nbconvert.filters.strings.wrap
70 'wrap': nbconvert.filters.strings.wrap
71 }
71 }
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Class
74 # Class
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 class Exporter(Configurable):
77 class Exporter(Configurable):
78 """
78 """
79 Exports notebooks into other file formats. Uses Jinja 2 templating engine
79 Exports notebooks into other file formats. Uses Jinja 2 templating engine
80 to output new formats. Inherit from this class if you are creating a new
80 to output new formats. Inherit from this class if you are creating a new
81 template type along with new filters/transformers. If the filters/
81 template type along with new filters/transformers. If the filters/
82 transformers provided by default suffice, there is no need to inherit from
82 transformers provided by default suffice, there is no need to inherit from
83 this class. Instead, override the template_file and file_extension
83 this class. Instead, override the template_file and file_extension
84 traits via a config file.
84 traits via a config file.
85
85
86 {filters}
86 {filters}
87 """
87 """
88
88
89
89
90
90
91 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
91 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
92
92
93
93
94 template_file = Unicode(
94 template_file = Unicode(
95 '', config=True,
95 '', config=True,
96 help="Name of the template file to use")
96 help="Name of the template file to use")
97
97
98 file_extension = Unicode(
98 file_extension = Unicode(
99 'txt', config=True,
99 'txt', config=True,
100 help="Extension of the file that should be written to disk"
100 help="Extension of the file that should be written to disk"
101 )
101 )
102
102
103 template_path = Unicode(
103 template_path = Unicode(
104 "/../templates/", config=True,
104 "/../templates/", config=True,
105 help="Path where the template files are located.")
105 help="Path where the template files are located.")
106
106
107 template_skeleton_path = Unicode(
107 template_skeleton_path = Unicode(
108 "/../templates/skeleton/", config=True,
108 "/../templates/skeleton/", config=True,
109 help="Path where the template skeleton files are located.")
109 help="Path where the template skeleton files are located.")
110
110
111 #Jinja block definitions
111 #Jinja block definitions
112 jinja_comment_block_start = Unicode("", config=True)
112 jinja_comment_block_start = Unicode("", config=True)
113 jinja_comment_block_end = Unicode("", config=True)
113 jinja_comment_block_end = Unicode("", config=True)
114 jinja_variable_block_start = Unicode("", config=True)
114 jinja_variable_block_start = Unicode("", config=True)
115 jinja_variable_block_end = Unicode("", config=True)
115 jinja_variable_block_end = Unicode("", config=True)
116 jinja_logic_block_start = Unicode("", config=True)
116 jinja_logic_block_start = Unicode("", config=True)
117 jinja_logic_block_end = Unicode("", config=True)
117 jinja_logic_block_end = Unicode("", config=True)
118
118
119 #Extension that the template files use.
119 #Extension that the template files use.
120 template_extension = Unicode(".tpl", config=True)
120 template_extension = Unicode(".tpl", config=True)
121
121
122 #Processors that process the input data prior to the export, set in the
122 #Processors that process the input data prior to the export, set in the
123 #constructor for this class.
123 #constructor for this class.
124 transformers = None
124 transformers = None
125
125
126
126
127 def __init__(self, transformers=None, filters=None, config=None, **kw):
127 def __init__(self, transformers=None, filters=None, config=None, **kw):
128 """
128 """
129 Public constructor
129 Public constructor
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133 transformers : list[of transformer]
133 transformers : list[of transformer]
134 Custom transformers to apply to the notebook prior to engaging
134 Custom transformers to apply to the notebook prior to engaging
135 the Jinja template engine. Any transformers specified here
135 the Jinja template engine. Any transformers specified here
136 will override existing transformers if a naming conflict
136 will override existing transformers if a naming conflict
137 occurs.
137 occurs.
138 filters : dict[of filter]
138 filters : dict[of filter]
139 filters specified here will override existing filters if a naming
139 filters specified here will override existing filters if a naming
140 conflict occurs. Filters are availlable in jinja template through
140 conflict occurs. Filters are availlable in jinja template through
141 the name of the corresponding key. Cf class docstring for
141 the name of the corresponding key. Cf class docstring for
142 availlable default filters.
142 availlable default filters.
143 config : config
143 config : config
144 User configuration instance.
144 User configuration instance.
145 """
145 """
146
146
147 #Call the base class constructor
147 #Call the base class constructor
148 super(Exporter, self).__init__(config=config, **kw)
148 super(Exporter, self).__init__(config=config, **kw)
149
149
150 #Standard environment
150 #Standard environment
151 self._init_environment()
151 self._init_environment()
152
152
153 #Add transformers
153 #Add transformers
154 self._register_transformers()
154 self._register_transformers()
155
155
156 #Add filters to the Jinja2 environment
156 #Add filters to the Jinja2 environment
157 self._register_filters()
157 self._register_filters()
158
158
159 #Load user transformers. Overwrite existing transformers if need be.
159 #Load user transformers. Overwrite existing transformers if need be.
160 if transformers :
160 if transformers :
161 for transformer in transformers:
161 for transformer in transformers:
162 self.register_transformer(transformer)
162 self.register_transformer(transformer)
163
163
164 #Load user filters. Overwrite existing filters if need be.
164 #Load user filters. Overwrite existing filters if need be.
165 if not filters is None:
165 if not filters is None:
166 for key, user_filter in filters.iteritems():
166 for key, user_filter in filters.iteritems():
167 if issubclass(user_filter, MetaHasTraits):
167 if issubclass(user_filter, MetaHasTraits):
168 self.environment.filters[key] = user_filter(config=config)
168 self.environment.filters[key] = user_filter(config=config)
169 else:
169 else:
170 self.environment.filters[key] = user_filter
170 self.environment.filters[key] = user_filter
171
171
172 @property
172 @property
173 def default_config(self):
173 def default_config(self):
174 if self._default_config:
174 if self._default_config:
175 return deepcopy(self._default_config)
175 return deepcopy(self._default_config)
176 else :
176 else :
177 return Config({})
177 return Config({})
178
178
179
179
180 def from_notebook_node(self, nb, resources=None):
180 def from_notebook_node(self, nb, resources=None):
181 """
181 """
182 Convert a notebook from a notebook node instance.
182 Convert a notebook from a notebook node instance.
183
183
184 Parameters
184 Parameters
185 ----------
185 ----------
186 nb : Notebook node
186 nb : Notebook node
187 resources : a dict of additional resources that
187 resources : a dict of additional resources that
188 can be accessed read/write by transformers
188 can be accessed read/write by transformers
189 and filters.
189 and filters.
190 """
190 """
191 if resources is None:
191 if resources is None:
192 resources = {}
192 resources = {}
193 nb, resources = self._preprocess(nb, resources)
193 nb, resources = self._preprocess(nb, resources)
194
194
195 #Load the template file.
195 #Load the template file.
196 self.template = self.environment.get_template(self.template_file+self.template_extension)
196 self.template = self.environment.get_template(self.template_file+self.template_extension)
197
197
198 return self.template.render(nb=nb, resources=resources), resources
198 return self.template.render(nb=nb, resources=resources), resources
199
199
200
200
201 def from_filename(self, filename):
201 def from_filename(self, filename):
202 """
202 """
203 Convert a notebook from a notebook file.
203 Convert a notebook from a notebook file.
204
204
205 Parameters
205 Parameters
206 ----------
206 ----------
207 filename : str
207 filename : str
208 Full filename of the notebook file to open and convert.
208 Full filename of the notebook file to open and convert.
209 """
209 """
210
210
211 with io.open(filename) as f:
211 with io.open(filename) as f:
212 return self.from_notebook_node(nbformat.read(f, 'json'))
212 return self.from_notebook_node(nbformat.read(f, 'json'))
213
213
214
214
215 def from_file(self, file_stream):
215 def from_file(self, file_stream):
216 """
216 """
217 Convert a notebook from a notebook file.
217 Convert a notebook from a notebook file.
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 file_stream : file-like object
221 file_stream : file-like object
222 Notebook file-like object to convert.
222 Notebook file-like object to convert.
223 """
223 """
224 return self.from_notebook_node(nbformat.read(file_stream, 'json'))
224 return self.from_notebook_node(nbformat.read(file_stream, 'json'))
225
225
226
226
227 def register_transformer(self, transformer):
227 def register_transformer(self, transformer):
228 """
228 """
229 Register a transformer.
229 Register a transformer.
230 Transformers are classes that act upon the notebook before it is
230 Transformers are classes that act upon the notebook before it is
231 passed into the Jinja templating engine. Transformers are also
231 passed into the Jinja templating engine. Transformers are also
232 capable of passing additional information to the Jinja
232 capable of passing additional information to the Jinja
233 templating engine.
233 templating engine.
234
234
235 Parameters
235 Parameters
236 ----------
236 ----------
237 transformer : transformer
237 transformer : transformer
238 """
238 """
239 if self.transformers is None:
239 if self.transformers is None:
240 self.transformers = []
240 self.transformers = []
241
241
242 if inspect.isfunction(transformer):
242 if inspect.isfunction(transformer):
243 self.transformers.append(transformer)
243 self.transformers.append(transformer)
244 return transformer
244 return transformer
245 elif isinstance(transformer, MetaHasTraits):
245 elif isinstance(transformer, MetaHasTraits):
246 transformer_instance = transformer(config=self.config)
246 transformer_instance = transformer(config=self.config)
247 self.transformers.append(transformer_instance)
247 self.transformers.append(transformer_instance)
248 return transformer_instance
248 return transformer_instance
249 else:
249 else:
250 transformer_instance = transformer()
250 transformer_instance = transformer()
251 self.transformers.append(transformer_instance)
251 self.transformers.append(transformer_instance)
252 return transformer_instance
252 return transformer_instance
253
253
254
254
255 def register_filter(self, name, filter):
255 def register_filter(self, name, filter):
256 """
256 """
257 Register a filter.
257 Register a filter.
258 A filter is a function that accepts and acts on one string.
258 A filter is a function that accepts and acts on one string.
259 The filters are accesible within the Jinja templating engine.
259 The filters are accesible within the Jinja templating engine.
260
260
261 Parameters
261 Parameters
262 ----------
262 ----------
263 name : str
263 name : str
264 name to give the filter in the Jinja engine
264 name to give the filter in the Jinja engine
265 filter : filter
265 filter : filter
266 """
266 """
267 if inspect.isfunction(filter):
267 if inspect.isfunction(filter):
268 self.environment.filters[name] = filter
268 self.environment.filters[name] = filter
269 elif isinstance(filter, MetaHasTraits):
269 elif isinstance(filter, MetaHasTraits):
270 self.environment.filters[name] = filter(config=self.config)
270 self.environment.filters[name] = filter(config=self.config)
271 else:
271 else:
272 self.environment.filters[name] = filter()
272 self.environment.filters[name] = filter()
273 return self.environment.filters[name]
273 return self.environment.filters[name]
274
274
275
275
276 def _register_transformers(self):
276 def _register_transformers(self):
277 """
277 """
278 Register all of the transformers needed for this exporter.
278 Register all of the transformers needed for this exporter.
279 """
279 """
280
280
281 self.register_transformer(nbconvert.transformers.coalescestreams.coalesce_streams)
281 self.register_transformer(nbconvert.transformers.coalescestreams.coalesce_streams)
282
282
283 #Remember the figure extraction transformer so it can be enabled and
283 #Remember the figure extraction transformer so it can be enabled and
284 #disabled easily later.
284 #disabled easily later.
285 self.extract_figure_transformer = self.register_transformer(nbconvert.transformers.extractfigure.ExtractFigureTransformer)
285 self.extract_figure_transformer = self.register_transformer(nbconvert.transformers.extractfigure.ExtractFigureTransformer)
286
286
287
287
288 def _register_filters(self):
288 def _register_filters(self):
289 """
289 """
290 Register all of the filters required for the exporter.
290 Register all of the filters required for the exporter.
291 """
291 """
292 for k,v in default_filters.iteritems():
292 for k,v in default_filters.iteritems():
293 self.register_filter(k,v)
293 self.register_filter(k,v)
294
294
295
295
296 def _init_environment(self):
296 def _init_environment(self):
297 """
297 """
298 Create the Jinja templating environment.
298 Create the Jinja templating environment.
299 """
299 """
300
300
301 self.environment = Environment(
301 self.environment = Environment(
302 loader=FileSystemLoader([
302 loader=FileSystemLoader([
303 os.path.dirname(os.path.realpath(__file__)) + self.template_path,
303 os.path.dirname(os.path.realpath(__file__)) + self.template_path,
304 os.path.dirname(os.path.realpath(__file__)) + self.template_skeleton_path,
304 os.path.dirname(os.path.realpath(__file__)) + self.template_skeleton_path,
305 ]),
305 ]),
306 extensions=JINJA_EXTENSIONS
306 extensions=JINJA_EXTENSIONS
307 )
307 )
308
308
309 #Set special Jinja2 syntax that will not conflict with latex.
309 #Set special Jinja2 syntax that will not conflict with latex.
310 if self.jinja_logic_block_start:
310 if self.jinja_logic_block_start:
311 self.environment.block_start_string = self.jinja_logic_block_start
311 self.environment.block_start_string = self.jinja_logic_block_start
312 if self.jinja_logic_block_end:
312 if self.jinja_logic_block_end:
313 self.environment.block_end_string = self.jinja_logic_block_end
313 self.environment.block_end_string = self.jinja_logic_block_end
314 if self.jinja_variable_block_start:
314 if self.jinja_variable_block_start:
315 self.environment.variable_start_string = self.jinja_variable_block_start
315 self.environment.variable_start_string = self.jinja_variable_block_start
316 if self.jinja_variable_block_end:
316 if self.jinja_variable_block_end:
317 self.environment.variable_end_string = self.jinja_variable_block_end
317 self.environment.variable_end_string = self.jinja_variable_block_end
318 if self.jinja_comment_block_start:
318 if self.jinja_comment_block_start:
319 self.environment.comment_start_string = self.jinja_comment_block_start
319 self.environment.comment_start_string = self.jinja_comment_block_start
320 if self.jinja_comment_block_end:
320 if self.jinja_comment_block_end:
321 self.environment.comment_end_string = self.jinja_comment_block_end
321 self.environment.comment_end_string = self.jinja_comment_block_end
322
322
323
323
324 def _preprocess(self, nb, resources):
324 def _preprocess(self, nb, resources):
325 """
325 """
326 Preprocess the notebook before passing it into the Jinja engine.
326 Preprocess the notebook before passing it into the Jinja engine.
327 To preprocess the notebook is to apply all of the
327 To preprocess the notebook is to apply all of the
328
328
329 Parameters
329 Parameters
330 ----------
330 ----------
331 nb : notebook node
331 nb : notebook node
332 notebook that is being exported.
332 notebook that is being exported.
333 resources : a dict of additional resources that
333 resources : a dict of additional resources that
334 can be accessed read/write by transformers
334 can be accessed read/write by transformers
335 and filters.
335 and filters.
336 """
336 """
337
337
338
338 # Do a deepcopy first,
339 # we are never safe enough with what the transformers could do.
340 nbc = deepcopy(nb)
341 resc = deepcopy(resources)
339 #Run each transformer on the notebook. Carry the output along
342 #Run each transformer on the notebook. Carry the output along
340 #to each transformer
343 #to each transformer
341 for transformer in self.transformers:
344 for transformer in self.transformers:
342 nb, resources = transformer(nb, resources)
345 nb, resources = transformer(nbc, resc)
343 return nb, resources
346 return nb, resources
344
347
General Comments 0
You need to be logged in to leave comments. Login now