##// END OF EJS Templates
delay jinja2 imports to runtime in nbconvert...
MinRK -
Show More
@@ -1,322 +1,325 b''
1 """This module defines TemplateExporter, a highly configurable converter
1 """This module defines TemplateExporter, 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 os
20 import os
21
21
22 # other libs/dependencies
22 # other libs/dependencies are imported at runtime
23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
23 # to move ImportErrors to runtime when the requirement is actually needed
24
24
25 # IPython imports
25 # IPython imports
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils import py3compat, text
28 from IPython.utils import py3compat, text
29
29
30 from IPython.nbformat.current import docstring_nbformat_mod
30 from IPython.nbformat.current import docstring_nbformat_mod
31 from IPython.nbconvert import filters
31 from IPython.nbconvert import filters
32 from .exporter import Exporter
32 from .exporter import Exporter
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Globals and constants
35 # Globals and constants
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 #Jinja2 extensions to load.
38 #Jinja2 extensions to load.
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
40
40
41 default_filters = {
41 default_filters = {
42 'indent': text.indent,
42 'indent': text.indent,
43 'markdown2html': filters.markdown2html,
43 'markdown2html': filters.markdown2html,
44 'ansi2html': filters.ansi2html,
44 'ansi2html': filters.ansi2html,
45 'filter_data_type': filters.DataTypeFilter,
45 'filter_data_type': filters.DataTypeFilter,
46 'get_lines': filters.get_lines,
46 'get_lines': filters.get_lines,
47 'highlight2html': filters.Highlight2Html,
47 'highlight2html': filters.Highlight2Html,
48 'highlight2latex': filters.Highlight2Latex,
48 'highlight2latex': filters.Highlight2Latex,
49 'ipython2python': filters.ipython2python,
49 'ipython2python': filters.ipython2python,
50 'posix_path': filters.posix_path,
50 'posix_path': filters.posix_path,
51 'markdown2latex': filters.markdown2latex,
51 'markdown2latex': filters.markdown2latex,
52 'markdown2rst': filters.markdown2rst,
52 'markdown2rst': filters.markdown2rst,
53 'comment_lines': filters.comment_lines,
53 'comment_lines': filters.comment_lines,
54 'strip_ansi': filters.strip_ansi,
54 'strip_ansi': filters.strip_ansi,
55 'strip_dollars': filters.strip_dollars,
55 'strip_dollars': filters.strip_dollars,
56 'strip_files_prefix': filters.strip_files_prefix,
56 'strip_files_prefix': filters.strip_files_prefix,
57 'html2text' : filters.html2text,
57 'html2text' : filters.html2text,
58 'add_anchor': filters.add_anchor,
58 'add_anchor': filters.add_anchor,
59 'ansi2latex': filters.ansi2latex,
59 'ansi2latex': filters.ansi2latex,
60 'wrap_text': filters.wrap_text,
60 'wrap_text': filters.wrap_text,
61 'escape_latex': filters.escape_latex,
61 'escape_latex': filters.escape_latex,
62 'citation2latex': filters.citation2latex,
62 'citation2latex': filters.citation2latex,
63 'path2url': filters.path2url,
63 'path2url': filters.path2url,
64 'add_prompts': filters.add_prompts,
64 'add_prompts': filters.add_prompts,
65 }
65 }
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Class
68 # Class
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 class TemplateExporter(Exporter):
71 class TemplateExporter(Exporter):
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/preprocessors. If the filters/
75 template type along with new filters/preprocessors. If the filters/
76 preprocessors provided by default suffice, there is no need to inherit from
76 preprocessors 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(u'default',
87 template_file = Unicode(u'default',
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 def _template_file_changed(self, name, old, new):
90 def _template_file_changed(self, name, old, new):
91 if new == 'default':
91 if new == 'default':
92 self.template_file = self.default_template
92 self.template_file = self.default_template
93 else:
93 else:
94 self.template_file = new
94 self.template_file = new
95 self.template = None
95 self.template = None
96 self._load_template()
96 self._load_template()
97
97
98 default_template = Unicode(u'')
98 default_template = Unicode(u'')
99 template = Any()
99 template = Any()
100 environment = Any()
100 environment = Any()
101
101
102 template_path = List(['.'], config=True)
102 template_path = List(['.'], config=True)
103 def _template_path_changed(self, name, old, new):
103 def _template_path_changed(self, name, old, new):
104 self._load_template()
104 self._load_template()
105
105
106 default_template_path = Unicode(
106 default_template_path = Unicode(
107 os.path.join("..", "templates"),
107 os.path.join("..", "templates"),
108 help="Path where the template files are located.")
108 help="Path where the template files are located.")
109
109
110 template_skeleton_path = Unicode(
110 template_skeleton_path = Unicode(
111 os.path.join("..", "templates", "skeleton"),
111 os.path.join("..", "templates", "skeleton"),
112 help="Path where the template skeleton files are located.")
112 help="Path where the template skeleton files are located.")
113
113
114 #Jinja block definitions
114 #Jinja block definitions
115 jinja_comment_block_start = Unicode("", config=True)
115 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
121
121
122 #Extension that the template files use.
122 #Extension that the template files use.
123 template_extension = Unicode(".tpl", config=True)
123 template_extension = Unicode(".tpl", config=True)
124
124
125 filters = Dict(config=True,
125 filters = Dict(config=True,
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 environment.""")
127 environment.""")
128
128
129 raw_mimetypes = List(config=True,
129 raw_mimetypes = List(config=True,
130 help="""formats of raw cells to be included in this Exporter's output."""
130 help="""formats of raw cells to be included in this Exporter's output."""
131 )
131 )
132 def _raw_mimetypes_default(self):
132 def _raw_mimetypes_default(self):
133 return [self.output_mimetype, '']
133 return [self.output_mimetype, '']
134
134
135
135
136 def __init__(self, config=None, extra_loaders=None, **kw):
136 def __init__(self, config=None, extra_loaders=None, **kw):
137 """
137 """
138 Public constructor
138 Public constructor
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 config : config
142 config : config
143 User configuration instance.
143 User configuration instance.
144 extra_loaders : list[of Jinja Loaders]
144 extra_loaders : list[of Jinja Loaders]
145 ordered list of Jinja loader to find templates. Will be tried in order
145 ordered list of Jinja loader to find templates. Will be tried in order
146 before the default FileSystem ones.
146 before the default FileSystem ones.
147 template : str (optional, kw arg)
147 template : str (optional, kw arg)
148 Template to use when exporting.
148 Template to use when exporting.
149 """
149 """
150 super(TemplateExporter, self).__init__(config=config, **kw)
150 super(TemplateExporter, self).__init__(config=config, **kw)
151
151
152 #Init
152 #Init
153 self._init_template()
153 self._init_template()
154 self._init_environment(extra_loaders=extra_loaders)
154 self._init_environment(extra_loaders=extra_loaders)
155 self._init_preprocessors()
155 self._init_preprocessors()
156 self._init_filters()
156 self._init_filters()
157
157
158
158
159 def _load_template(self):
159 def _load_template(self):
160 """Load the Jinja template object from the template file
160 """Load the Jinja template object from the template file
161
161
162 This is a no-op if the template attribute is already defined,
162 This is a no-op if the template attribute is already defined,
163 or the Jinja environment is not setup yet.
163 or the Jinja environment is not setup yet.
164
164
165 This is triggered by various trait changes that would change the template.
165 This is triggered by various trait changes that would change the template.
166 """
166 """
167 from jinja2 import TemplateNotFound
168
167 if self.template is not None:
169 if self.template is not None:
168 return
170 return
169 # called too early, do nothing
171 # called too early, do nothing
170 if self.environment is None:
172 if self.environment is None:
171 return
173 return
172 # Try different template names during conversion. First try to load the
174 # Try different template names during conversion. First try to load the
173 # template by name with extension added, then try loading the template
175 # template by name with extension added, then try loading the template
174 # as if the name is explicitly specified, then try the name as a
176 # as if the name is explicitly specified, then try the name as a
175 # 'flavor', and lastly just try to load the template by module name.
177 # 'flavor', and lastly just try to load the template by module name.
176 module_name = self.__module__.rsplit('.', 1)[-1]
178 module_name = self.__module__.rsplit('.', 1)[-1]
177 try_names = []
179 try_names = []
178 if self.template_file:
180 if self.template_file:
179 try_names.extend([
181 try_names.extend([
180 self.template_file + self.template_extension,
182 self.template_file + self.template_extension,
181 self.template_file,
183 self.template_file,
182 module_name + '_' + self.template_file + self.template_extension,
184 module_name + '_' + self.template_file + self.template_extension,
183 ])
185 ])
184 try_names.append(module_name + self.template_extension)
186 try_names.append(module_name + self.template_extension)
185 for try_name in try_names:
187 for try_name in try_names:
186 self.log.debug("Attempting to load template %s", try_name)
188 self.log.debug("Attempting to load template %s", try_name)
187 try:
189 try:
188 self.template = self.environment.get_template(try_name)
190 self.template = self.environment.get_template(try_name)
189 except (TemplateNotFound, IOError):
191 except (TemplateNotFound, IOError):
190 pass
192 pass
191 except Exception as e:
193 except Exception as e:
192 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
194 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
193 else:
195 else:
194 self.log.info("Loaded template %s", try_name)
196 self.log.info("Loaded template %s", try_name)
195 break
197 break
196
198
197 @docstring_nbformat_mod
199 @docstring_nbformat_mod
198 def from_notebook_node(self, nb, resources=None, **kw):
200 def from_notebook_node(self, nb, resources=None, **kw):
199 """
201 """
200 Convert a notebook from a notebook node instance.
202 Convert a notebook from a notebook node instance.
201
203
202 Parameters
204 Parameters
203 ----------
205 ----------
204 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
206 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
205 Notebook node
207 Notebook node
206 resources : dict
208 resources : dict
207 Additional resources that can be accessed read/write by
209 Additional resources that can be accessed read/write by
208 preprocessors and filters.
210 preprocessors and filters.
209 """
211 """
210 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
212 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
211 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
213 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
212
214
213 self._load_template()
215 self._load_template()
214
216
215 if self.template is not None:
217 if self.template is not None:
216 output = self.template.render(nb=nb_copy, resources=resources)
218 output = self.template.render(nb=nb_copy, resources=resources)
217 else:
219 else:
218 raise IOError('template file "%s" could not be found' % self.template_file)
220 raise IOError('template file "%s" could not be found' % self.template_file)
219 return output, resources
221 return output, resources
220
222
221
223
222 def register_filter(self, name, jinja_filter):
224 def register_filter(self, name, jinja_filter):
223 """
225 """
224 Register a filter.
226 Register a filter.
225 A filter is a function that accepts and acts on one string.
227 A filter is a function that accepts and acts on one string.
226 The filters are accesible within the Jinja templating engine.
228 The filters are accesible within the Jinja templating engine.
227
229
228 Parameters
230 Parameters
229 ----------
231 ----------
230 name : str
232 name : str
231 name to give the filter in the Jinja engine
233 name to give the filter in the Jinja engine
232 filter : filter
234 filter : filter
233 """
235 """
234 if jinja_filter is None:
236 if jinja_filter is None:
235 raise TypeError('filter')
237 raise TypeError('filter')
236 isclass = isinstance(jinja_filter, type)
238 isclass = isinstance(jinja_filter, type)
237 constructed = not isclass
239 constructed = not isclass
238
240
239 #Handle filter's registration based on it's type
241 #Handle filter's registration based on it's type
240 if constructed and isinstance(jinja_filter, py3compat.string_types):
242 if constructed and isinstance(jinja_filter, py3compat.string_types):
241 #filter is a string, import the namespace and recursively call
243 #filter is a string, import the namespace and recursively call
242 #this register_filter method
244 #this register_filter method
243 filter_cls = import_item(jinja_filter)
245 filter_cls = import_item(jinja_filter)
244 return self.register_filter(name, filter_cls)
246 return self.register_filter(name, filter_cls)
245
247
246 if constructed and hasattr(jinja_filter, '__call__'):
248 if constructed and hasattr(jinja_filter, '__call__'):
247 #filter is a function, no need to construct it.
249 #filter is a function, no need to construct it.
248 self.environment.filters[name] = jinja_filter
250 self.environment.filters[name] = jinja_filter
249 return jinja_filter
251 return jinja_filter
250
252
251 elif isclass and isinstance(jinja_filter, MetaHasTraits):
253 elif isclass and isinstance(jinja_filter, MetaHasTraits):
252 #filter is configurable. Make sure to pass in new default for
254 #filter is configurable. Make sure to pass in new default for
253 #the enabled flag if one was specified.
255 #the enabled flag if one was specified.
254 filter_instance = jinja_filter(parent=self)
256 filter_instance = jinja_filter(parent=self)
255 self.register_filter(name, filter_instance )
257 self.register_filter(name, filter_instance )
256
258
257 elif isclass:
259 elif isclass:
258 #filter is not configurable, construct it
260 #filter is not configurable, construct it
259 filter_instance = jinja_filter()
261 filter_instance = jinja_filter()
260 self.register_filter(name, filter_instance)
262 self.register_filter(name, filter_instance)
261
263
262 else:
264 else:
263 #filter is an instance of something without a __call__
265 #filter is an instance of something without a __call__
264 #attribute.
266 #attribute.
265 raise TypeError('filter')
267 raise TypeError('filter')
266
268
267
269
268 def _init_template(self):
270 def _init_template(self):
269 """
271 """
270 Make sure a template name is specified. If one isn't specified, try to
272 Make sure a template name is specified. If one isn't specified, try to
271 build one from the information we know.
273 build one from the information we know.
272 """
274 """
273 self._template_file_changed('template_file', self.template_file, self.template_file)
275 self._template_file_changed('template_file', self.template_file, self.template_file)
274
276
275
277
276 def _init_environment(self, extra_loaders=None):
278 def _init_environment(self, extra_loaders=None):
277 """
279 """
278 Create the Jinja templating environment.
280 Create the Jinja templating environment.
279 """
281 """
282 from jinja2 import Environment, ChoiceLoader, FileSystemLoader
280 here = os.path.dirname(os.path.realpath(__file__))
283 here = os.path.dirname(os.path.realpath(__file__))
281 loaders = []
284 loaders = []
282 if extra_loaders:
285 if extra_loaders:
283 loaders.extend(extra_loaders)
286 loaders.extend(extra_loaders)
284
287
285 paths = self.template_path
288 paths = self.template_path
286 paths.extend([os.path.join(here, self.default_template_path),
289 paths.extend([os.path.join(here, self.default_template_path),
287 os.path.join(here, self.template_skeleton_path)])
290 os.path.join(here, self.template_skeleton_path)])
288 loaders.append(FileSystemLoader(paths))
291 loaders.append(FileSystemLoader(paths))
289
292
290 self.environment = Environment(
293 self.environment = Environment(
291 loader= ChoiceLoader(loaders),
294 loader= ChoiceLoader(loaders),
292 extensions=JINJA_EXTENSIONS
295 extensions=JINJA_EXTENSIONS
293 )
296 )
294
297
295 #Set special Jinja2 syntax that will not conflict with latex.
298 #Set special Jinja2 syntax that will not conflict with latex.
296 if self.jinja_logic_block_start:
299 if self.jinja_logic_block_start:
297 self.environment.block_start_string = self.jinja_logic_block_start
300 self.environment.block_start_string = self.jinja_logic_block_start
298 if self.jinja_logic_block_end:
301 if self.jinja_logic_block_end:
299 self.environment.block_end_string = self.jinja_logic_block_end
302 self.environment.block_end_string = self.jinja_logic_block_end
300 if self.jinja_variable_block_start:
303 if self.jinja_variable_block_start:
301 self.environment.variable_start_string = self.jinja_variable_block_start
304 self.environment.variable_start_string = self.jinja_variable_block_start
302 if self.jinja_variable_block_end:
305 if self.jinja_variable_block_end:
303 self.environment.variable_end_string = self.jinja_variable_block_end
306 self.environment.variable_end_string = self.jinja_variable_block_end
304 if self.jinja_comment_block_start:
307 if self.jinja_comment_block_start:
305 self.environment.comment_start_string = self.jinja_comment_block_start
308 self.environment.comment_start_string = self.jinja_comment_block_start
306 if self.jinja_comment_block_end:
309 if self.jinja_comment_block_end:
307 self.environment.comment_end_string = self.jinja_comment_block_end
310 self.environment.comment_end_string = self.jinja_comment_block_end
308
311
309
312
310 def _init_filters(self):
313 def _init_filters(self):
311 """
314 """
312 Register all of the filters required for the exporter.
315 Register all of the filters required for the exporter.
313 """
316 """
314
317
315 #Add default filters to the Jinja2 environment
318 #Add default filters to the Jinja2 environment
316 for key, value in default_filters.items():
319 for key, value in default_filters.items():
317 self.register_filter(key, value)
320 self.register_filter(key, value)
318
321
319 #Load user filters. Overwrite existing filters if need be.
322 #Load user filters. Overwrite existing filters if need be.
320 if self.filters:
323 if self.filters:
321 for key, user_filter in self.filters.items():
324 for key, user_filter in self.filters.items():
322 self.register_filter(key, user_filter)
325 self.register_filter(key, user_filter)
General Comments 0
You need to be logged in to leave comments. Login now