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