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