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