##// END OF EJS Templates
convert IPython syntax to Python syntax in nbconvert python template...
MinRK -
Show More
@@ -1,445 +1,446 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 import copy
23 import copy
24 import collections
24 import collections
25 import datetime
25 import datetime
26
26
27 # other libs/dependencies
27 # other libs/dependencies
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
29
29
30 # IPython imports
30 # IPython imports
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
32 from IPython.config import Config
32 from IPython.config import Config
33 from IPython.nbformat import current as nbformat
33 from IPython.nbformat import current as nbformat
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
35 from IPython.utils.importstring import import_item
35 from IPython.utils.importstring import import_item
36 from IPython.utils.text import indent
36 from IPython.utils.text import indent
37 from IPython.utils import py3compat
37 from IPython.utils import py3compat
38
38
39 from IPython.nbconvert import transformers as nbtransformers
39 from IPython.nbconvert import transformers as nbtransformers
40 from IPython.nbconvert import filters
40 from IPython.nbconvert import filters
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Globals and constants
43 # Globals and constants
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 #Jinja2 extensions to load.
46 #Jinja2 extensions to load.
47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
47 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
48
48
49 default_filters = {
49 default_filters = {
50 'indent': indent,
50 'indent': indent,
51 'markdown2html': filters.markdown2html,
51 'markdown2html': filters.markdown2html,
52 'ansi2html': filters.ansi2html,
52 'ansi2html': filters.ansi2html,
53 'filter_data_type': filters.DataTypeFilter,
53 'filter_data_type': filters.DataTypeFilter,
54 'get_lines': filters.get_lines,
54 'get_lines': filters.get_lines,
55 'highlight2html': filters.highlight2html,
55 'highlight2html': filters.highlight2html,
56 'highlight2latex': filters.highlight2latex,
56 'highlight2latex': filters.highlight2latex,
57 'ipython2python': filters.ipython2python,
57 'markdown2latex': filters.markdown2latex,
58 'markdown2latex': filters.markdown2latex,
58 'markdown2rst': filters.markdown2rst,
59 'markdown2rst': filters.markdown2rst,
59 'comment_lines': filters.comment_lines,
60 'comment_lines': filters.comment_lines,
60 'strip_ansi': filters.strip_ansi,
61 'strip_ansi': filters.strip_ansi,
61 'strip_dollars': filters.strip_dollars,
62 'strip_dollars': filters.strip_dollars,
62 'strip_files_prefix': filters.strip_files_prefix,
63 'strip_files_prefix': filters.strip_files_prefix,
63 'html2text' : filters.html2text,
64 'html2text' : filters.html2text,
64 'add_anchor': filters.add_anchor,
65 'add_anchor': filters.add_anchor,
65 'ansi2latex': filters.ansi2latex,
66 'ansi2latex': filters.ansi2latex,
66 'strip_math_space': filters.strip_math_space,
67 'strip_math_space': filters.strip_math_space,
67 'wrap_text': filters.wrap_text,
68 'wrap_text': filters.wrap_text,
68 'escape_latex': filters.escape_latex
69 'escape_latex': filters.escape_latex
69 }
70 }
70
71
71 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
72 # Class
73 # Class
73 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
74
75
75 class ResourcesDict(collections.defaultdict):
76 class ResourcesDict(collections.defaultdict):
76 def __missing__(self, key):
77 def __missing__(self, key):
77 return ''
78 return ''
78
79
79
80
80 class Exporter(Configurable):
81 class Exporter(Configurable):
81 """
82 """
82 Exports notebooks into other file formats. Uses Jinja 2 templating engine
83 Exports notebooks into other file formats. Uses Jinja 2 templating engine
83 to output new formats. Inherit from this class if you are creating a new
84 to output new formats. Inherit from this class if you are creating a new
84 template type along with new filters/transformers. If the filters/
85 template type along with new filters/transformers. If the filters/
85 transformers provided by default suffice, there is no need to inherit from
86 transformers provided by default suffice, there is no need to inherit from
86 this class. Instead, override the template_file and file_extension
87 this class. Instead, override the template_file and file_extension
87 traits via a config file.
88 traits via a config file.
88
89
89 {filters}
90 {filters}
90 """
91 """
91
92
92 # finish the docstring
93 # finish the docstring
93 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
94 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
94
95
95
96
96 template_file = Unicode(
97 template_file = Unicode(
97 '', config=True,
98 '', config=True,
98 help="Name of the template file to use")
99 help="Name of the template file to use")
99
100
100 file_extension = Unicode(
101 file_extension = Unicode(
101 'txt', config=True,
102 'txt', config=True,
102 help="Extension of the file that should be written to disk"
103 help="Extension of the file that should be written to disk"
103 )
104 )
104
105
105 template_path = List(['.'], config=True)
106 template_path = List(['.'], config=True)
106
107
107 default_template_path = Unicode(
108 default_template_path = Unicode(
108 os.path.join("..", "templates"),
109 os.path.join("..", "templates"),
109 help="Path where the template files are located.")
110 help="Path where the template files are located.")
110
111
111 template_skeleton_path = Unicode(
112 template_skeleton_path = Unicode(
112 os.path.join("..", "templates", "skeleton"),
113 os.path.join("..", "templates", "skeleton"),
113 help="Path where the template skeleton files are located.")
114 help="Path where the template skeleton files are located.")
114
115
115 #Jinja block definitions
116 #Jinja block definitions
116 jinja_comment_block_start = Unicode("", config=True)
117 jinja_comment_block_start = Unicode("", config=True)
117 jinja_comment_block_end = Unicode("", config=True)
118 jinja_comment_block_end = Unicode("", config=True)
118 jinja_variable_block_start = Unicode("", config=True)
119 jinja_variable_block_start = Unicode("", config=True)
119 jinja_variable_block_end = Unicode("", config=True)
120 jinja_variable_block_end = Unicode("", config=True)
120 jinja_logic_block_start = Unicode("", config=True)
121 jinja_logic_block_start = Unicode("", config=True)
121 jinja_logic_block_end = Unicode("", config=True)
122 jinja_logic_block_end = Unicode("", config=True)
122
123
123 #Extension that the template files use.
124 #Extension that the template files use.
124 template_extension = Unicode(".tpl", config=True)
125 template_extension = Unicode(".tpl", config=True)
125
126
126 #Configurability, allows the user to easily add filters and transformers.
127 #Configurability, allows the user to easily add filters and transformers.
127 transformers = List(config=True,
128 transformers = List(config=True,
128 help="""List of transformers, by name or namespace, to enable.""")
129 help="""List of transformers, by name or namespace, to enable.""")
129
130
130 filters = Dict(config=True,
131 filters = Dict(config=True,
131 help="""Dictionary of filters, by name and namespace, to add to the Jinja
132 help="""Dictionary of filters, by name and namespace, to add to the Jinja
132 environment.""")
133 environment.""")
133
134
134 default_transformers = List([nbtransformers.coalesce_streams,
135 default_transformers = List([nbtransformers.coalesce_streams,
135 nbtransformers.SVG2PDFTransformer,
136 nbtransformers.SVG2PDFTransformer,
136 nbtransformers.ExtractOutputTransformer,
137 nbtransformers.ExtractOutputTransformer,
137 nbtransformers.CSSHTMLHeaderTransformer,
138 nbtransformers.CSSHTMLHeaderTransformer,
138 nbtransformers.RevealHelpTransformer,
139 nbtransformers.RevealHelpTransformer,
139 nbtransformers.LatexTransformer,
140 nbtransformers.LatexTransformer,
140 nbtransformers.SphinxTransformer],
141 nbtransformers.SphinxTransformer],
141 config=True,
142 config=True,
142 help="""List of transformers available by default, by name, namespace,
143 help="""List of transformers available by default, by name, namespace,
143 instance, or type.""")
144 instance, or type.""")
144
145
145
146
146 def __init__(self, config=None, extra_loaders=None, **kw):
147 def __init__(self, config=None, extra_loaders=None, **kw):
147 """
148 """
148 Public constructor
149 Public constructor
149
150
150 Parameters
151 Parameters
151 ----------
152 ----------
152 config : config
153 config : config
153 User configuration instance.
154 User configuration instance.
154 extra_loaders : list[of Jinja Loaders]
155 extra_loaders : list[of Jinja Loaders]
155 ordered list of Jinja loder to find templates. Will be tried in order
156 ordered list of Jinja loder to find templates. Will be tried in order
156 before the default FileSysteme ones.
157 before the default FileSysteme ones.
157 """
158 """
158
159
159 #Call the base class constructor
160 #Call the base class constructor
160 c = self.default_config
161 c = self.default_config
161 if config:
162 if config:
162 c.merge(config)
163 c.merge(config)
163
164
164 super(Exporter, self).__init__(config=c, **kw)
165 super(Exporter, self).__init__(config=c, **kw)
165
166
166 #Init
167 #Init
167 self._init_environment(extra_loaders=extra_loaders)
168 self._init_environment(extra_loaders=extra_loaders)
168 self._init_transformers()
169 self._init_transformers()
169 self._init_filters()
170 self._init_filters()
170
171
171
172
172 @property
173 @property
173 def default_config(self):
174 def default_config(self):
174 return Config()
175 return Config()
175
176
176
177
177 def from_notebook_node(self, nb, resources=None, **kw):
178 def from_notebook_node(self, nb, resources=None, **kw):
178 """
179 """
179 Convert a notebook from a notebook node instance.
180 Convert a notebook from a notebook node instance.
180
181
181 Parameters
182 Parameters
182 ----------
183 ----------
183 nb : Notebook node
184 nb : Notebook node
184 resources : dict (**kw)
185 resources : dict (**kw)
185 of additional resources that can be accessed read/write by
186 of additional resources that can be accessed read/write by
186 transformers and filters.
187 transformers and filters.
187 """
188 """
188 nb_copy = copy.deepcopy(nb)
189 nb_copy = copy.deepcopy(nb)
189 resources = self._init_resources(resources)
190 resources = self._init_resources(resources)
190
191
191 #Preprocess
192 #Preprocess
192 nb_copy, resources = self._transform(nb_copy, resources)
193 nb_copy, resources = self._transform(nb_copy, resources)
193
194
194 #Convert
195 #Convert
195 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)
196 output = self.template.render(nb=nb_copy, resources=resources)
197 output = self.template.render(nb=nb_copy, resources=resources)
197 return output, resources
198 return output, resources
198
199
199
200
200 def from_filename(self, filename, resources=None, **kw):
201 def from_filename(self, filename, resources=None, **kw):
201 """
202 """
202 Convert a notebook from a notebook file.
203 Convert a notebook from a notebook file.
203
204
204 Parameters
205 Parameters
205 ----------
206 ----------
206 filename : str
207 filename : str
207 Full filename of the notebook file to open and convert.
208 Full filename of the notebook file to open and convert.
208 """
209 """
209
210
210 #Pull the metadata from the filesystem.
211 #Pull the metadata from the filesystem.
211 if resources is None:
212 if resources is None:
212 resources = ResourcesDict()
213 resources = ResourcesDict()
213 if not 'metadata' in resources or resources['metadata'] == '':
214 if not 'metadata' in resources or resources['metadata'] == '':
214 resources['metadata'] = ResourcesDict()
215 resources['metadata'] = ResourcesDict()
215 basename = os.path.basename(filename)
216 basename = os.path.basename(filename)
216 notebook_name = basename[:basename.rfind('.')]
217 notebook_name = basename[:basename.rfind('.')]
217 resources['metadata']['name'] = notebook_name
218 resources['metadata']['name'] = notebook_name
218
219
219 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
220 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
220 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
221 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
221
222
222 with io.open(filename) as f:
223 with io.open(filename) as f:
223 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
224 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
224
225
225
226
226 def from_file(self, file_stream, resources=None, **kw):
227 def from_file(self, file_stream, resources=None, **kw):
227 """
228 """
228 Convert a notebook from a notebook file.
229 Convert a notebook from a notebook file.
229
230
230 Parameters
231 Parameters
231 ----------
232 ----------
232 file_stream : file-like object
233 file_stream : file-like object
233 Notebook file-like object to convert.
234 Notebook file-like object to convert.
234 """
235 """
235 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
236 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
236
237
237
238
238 def register_transformer(self, transformer, enabled=False):
239 def register_transformer(self, transformer, enabled=False):
239 """
240 """
240 Register a transformer.
241 Register a transformer.
241 Transformers are classes that act upon the notebook before it is
242 Transformers are classes that act upon the notebook before it is
242 passed into the Jinja templating engine. Transformers are also
243 passed into the Jinja templating engine. Transformers are also
243 capable of passing additional information to the Jinja
244 capable of passing additional information to the Jinja
244 templating engine.
245 templating engine.
245
246
246 Parameters
247 Parameters
247 ----------
248 ----------
248 transformer : transformer
249 transformer : transformer
249 """
250 """
250 if transformer is None:
251 if transformer is None:
251 raise TypeError('transformer')
252 raise TypeError('transformer')
252 isclass = isinstance(transformer, type)
253 isclass = isinstance(transformer, type)
253 constructed = not isclass
254 constructed = not isclass
254
255
255 #Handle transformer's registration based on it's type
256 #Handle transformer's registration based on it's type
256 if constructed and isinstance(transformer, py3compat.string_types):
257 if constructed and isinstance(transformer, py3compat.string_types):
257 #Transformer is a string, import the namespace and recursively call
258 #Transformer is a string, import the namespace and recursively call
258 #this register_transformer method
259 #this register_transformer method
259 transformer_cls = import_item(transformer)
260 transformer_cls = import_item(transformer)
260 return self.register_transformer(transformer_cls, enabled)
261 return self.register_transformer(transformer_cls, enabled)
261
262
262 if constructed and hasattr(transformer, '__call__'):
263 if constructed and hasattr(transformer, '__call__'):
263 #Transformer is a function, no need to construct it.
264 #Transformer is a function, no need to construct it.
264 #Register and return the transformer.
265 #Register and return the transformer.
265 if enabled:
266 if enabled:
266 transformer.enabled = True
267 transformer.enabled = True
267 self._transformers.append(transformer)
268 self._transformers.append(transformer)
268 return transformer
269 return transformer
269
270
270 elif isclass and isinstance(transformer, MetaHasTraits):
271 elif isclass and isinstance(transformer, MetaHasTraits):
271 #Transformer is configurable. Make sure to pass in new default for
272 #Transformer is configurable. Make sure to pass in new default for
272 #the enabled flag if one was specified.
273 #the enabled flag if one was specified.
273 self.register_transformer(transformer(parent=self), enabled)
274 self.register_transformer(transformer(parent=self), enabled)
274
275
275 elif isclass:
276 elif isclass:
276 #Transformer is not configurable, construct it
277 #Transformer is not configurable, construct it
277 self.register_transformer(transformer(), enabled)
278 self.register_transformer(transformer(), enabled)
278
279
279 else:
280 else:
280 #Transformer is an instance of something without a __call__
281 #Transformer is an instance of something without a __call__
281 #attribute.
282 #attribute.
282 raise TypeError('transformer')
283 raise TypeError('transformer')
283
284
284
285
285 def register_filter(self, name, jinja_filter):
286 def register_filter(self, name, jinja_filter):
286 """
287 """
287 Register a filter.
288 Register a filter.
288 A filter is a function that accepts and acts on one string.
289 A filter is a function that accepts and acts on one string.
289 The filters are accesible within the Jinja templating engine.
290 The filters are accesible within the Jinja templating engine.
290
291
291 Parameters
292 Parameters
292 ----------
293 ----------
293 name : str
294 name : str
294 name to give the filter in the Jinja engine
295 name to give the filter in the Jinja engine
295 filter : filter
296 filter : filter
296 """
297 """
297 if jinja_filter is None:
298 if jinja_filter is None:
298 raise TypeError('filter')
299 raise TypeError('filter')
299 isclass = isinstance(jinja_filter, type)
300 isclass = isinstance(jinja_filter, type)
300 constructed = not isclass
301 constructed = not isclass
301
302
302 #Handle filter's registration based on it's type
303 #Handle filter's registration based on it's type
303 if constructed and isinstance(jinja_filter, py3compat.string_types):
304 if constructed and isinstance(jinja_filter, py3compat.string_types):
304 #filter is a string, import the namespace and recursively call
305 #filter is a string, import the namespace and recursively call
305 #this register_filter method
306 #this register_filter method
306 filter_cls = import_item(jinja_filter)
307 filter_cls = import_item(jinja_filter)
307 return self.register_filter(name, filter_cls)
308 return self.register_filter(name, filter_cls)
308
309
309 if constructed and hasattr(jinja_filter, '__call__'):
310 if constructed and hasattr(jinja_filter, '__call__'):
310 #filter is a function, no need to construct it.
311 #filter is a function, no need to construct it.
311 self.environment.filters[name] = jinja_filter
312 self.environment.filters[name] = jinja_filter
312 return jinja_filter
313 return jinja_filter
313
314
314 elif isclass and isinstance(jinja_filter, MetaHasTraits):
315 elif isclass and isinstance(jinja_filter, MetaHasTraits):
315 #filter is configurable. Make sure to pass in new default for
316 #filter is configurable. Make sure to pass in new default for
316 #the enabled flag if one was specified.
317 #the enabled flag if one was specified.
317 filter_instance = jinja_filter(parent=self)
318 filter_instance = jinja_filter(parent=self)
318 self.register_filter(name, filter_instance )
319 self.register_filter(name, filter_instance )
319
320
320 elif isclass:
321 elif isclass:
321 #filter is not configurable, construct it
322 #filter is not configurable, construct it
322 filter_instance = jinja_filter()
323 filter_instance = jinja_filter()
323 self.register_filter(name, filter_instance)
324 self.register_filter(name, filter_instance)
324
325
325 else:
326 else:
326 #filter is an instance of something without a __call__
327 #filter is an instance of something without a __call__
327 #attribute.
328 #attribute.
328 raise TypeError('filter')
329 raise TypeError('filter')
329
330
330
331
331 def _init_environment(self, extra_loaders=None):
332 def _init_environment(self, extra_loaders=None):
332 """
333 """
333 Create the Jinja templating environment.
334 Create the Jinja templating environment.
334 """
335 """
335 here = os.path.dirname(os.path.realpath(__file__))
336 here = os.path.dirname(os.path.realpath(__file__))
336 loaders = []
337 loaders = []
337 if extra_loaders:
338 if extra_loaders:
338 loaders.extend(extra_loaders)
339 loaders.extend(extra_loaders)
339
340
340 paths = self.template_path
341 paths = self.template_path
341 paths.extend([os.path.join(here, self.default_template_path),
342 paths.extend([os.path.join(here, self.default_template_path),
342 os.path.join(here, self.template_skeleton_path)])
343 os.path.join(here, self.template_skeleton_path)])
343 loaders.append(FileSystemLoader(paths))
344 loaders.append(FileSystemLoader(paths))
344
345
345 self.environment = Environment(
346 self.environment = Environment(
346 loader= ChoiceLoader(loaders),
347 loader= ChoiceLoader(loaders),
347 extensions=JINJA_EXTENSIONS
348 extensions=JINJA_EXTENSIONS
348 )
349 )
349
350
350 #Set special Jinja2 syntax that will not conflict with latex.
351 #Set special Jinja2 syntax that will not conflict with latex.
351 if self.jinja_logic_block_start:
352 if self.jinja_logic_block_start:
352 self.environment.block_start_string = self.jinja_logic_block_start
353 self.environment.block_start_string = self.jinja_logic_block_start
353 if self.jinja_logic_block_end:
354 if self.jinja_logic_block_end:
354 self.environment.block_end_string = self.jinja_logic_block_end
355 self.environment.block_end_string = self.jinja_logic_block_end
355 if self.jinja_variable_block_start:
356 if self.jinja_variable_block_start:
356 self.environment.variable_start_string = self.jinja_variable_block_start
357 self.environment.variable_start_string = self.jinja_variable_block_start
357 if self.jinja_variable_block_end:
358 if self.jinja_variable_block_end:
358 self.environment.variable_end_string = self.jinja_variable_block_end
359 self.environment.variable_end_string = self.jinja_variable_block_end
359 if self.jinja_comment_block_start:
360 if self.jinja_comment_block_start:
360 self.environment.comment_start_string = self.jinja_comment_block_start
361 self.environment.comment_start_string = self.jinja_comment_block_start
361 if self.jinja_comment_block_end:
362 if self.jinja_comment_block_end:
362 self.environment.comment_end_string = self.jinja_comment_block_end
363 self.environment.comment_end_string = self.jinja_comment_block_end
363
364
364
365
365 def _init_transformers(self):
366 def _init_transformers(self):
366 """
367 """
367 Register all of the transformers needed for this exporter, disabled
368 Register all of the transformers needed for this exporter, disabled
368 unless specified explicitly.
369 unless specified explicitly.
369 """
370 """
370 self._transformers = []
371 self._transformers = []
371
372
372 #Load default transformers (not necessarly enabled by default).
373 #Load default transformers (not necessarly enabled by default).
373 if self.default_transformers:
374 if self.default_transformers:
374 for transformer in self.default_transformers:
375 for transformer in self.default_transformers:
375 self.register_transformer(transformer)
376 self.register_transformer(transformer)
376
377
377 #Load user transformers. Enable by default.
378 #Load user transformers. Enable by default.
378 if self.transformers:
379 if self.transformers:
379 for transformer in self.transformers:
380 for transformer in self.transformers:
380 self.register_transformer(transformer, enabled=True)
381 self.register_transformer(transformer, enabled=True)
381
382
382
383
383 def _init_filters(self):
384 def _init_filters(self):
384 """
385 """
385 Register all of the filters required for the exporter.
386 Register all of the filters required for the exporter.
386 """
387 """
387
388
388 #Add default filters to the Jinja2 environment
389 #Add default filters to the Jinja2 environment
389 for key, value in default_filters.items():
390 for key, value in default_filters.items():
390 self.register_filter(key, value)
391 self.register_filter(key, value)
391
392
392 #Load user filters. Overwrite existing filters if need be.
393 #Load user filters. Overwrite existing filters if need be.
393 if self.filters:
394 if self.filters:
394 for key, user_filter in self.filters.items():
395 for key, user_filter in self.filters.items():
395 self.register_filter(key, user_filter)
396 self.register_filter(key, user_filter)
396
397
397
398
398 def _init_resources(self, resources):
399 def _init_resources(self, resources):
399
400
400 #Make sure the resources dict is of ResourcesDict type.
401 #Make sure the resources dict is of ResourcesDict type.
401 if resources is None:
402 if resources is None:
402 resources = ResourcesDict()
403 resources = ResourcesDict()
403 if not isinstance(resources, ResourcesDict):
404 if not isinstance(resources, ResourcesDict):
404 new_resources = ResourcesDict()
405 new_resources = ResourcesDict()
405 new_resources.update(resources)
406 new_resources.update(resources)
406 resources = new_resources
407 resources = new_resources
407
408
408 #Make sure the metadata extension exists in resources
409 #Make sure the metadata extension exists in resources
409 if 'metadata' in resources:
410 if 'metadata' in resources:
410 if not isinstance(resources['metadata'], ResourcesDict):
411 if not isinstance(resources['metadata'], ResourcesDict):
411 resources['metadata'] = ResourcesDict(resources['metadata'])
412 resources['metadata'] = ResourcesDict(resources['metadata'])
412 else:
413 else:
413 resources['metadata'] = ResourcesDict()
414 resources['metadata'] = ResourcesDict()
414 if not resources['metadata']['name']:
415 if not resources['metadata']['name']:
415 resources['metadata']['name'] = 'Notebook'
416 resources['metadata']['name'] = 'Notebook'
416
417
417 #Set the output extension
418 #Set the output extension
418 resources['output_extension'] = self.file_extension
419 resources['output_extension'] = self.file_extension
419 return resources
420 return resources
420
421
421
422
422 def _transform(self, nb, resources):
423 def _transform(self, nb, resources):
423 """
424 """
424 Preprocess the notebook before passing it into the Jinja engine.
425 Preprocess the notebook before passing it into the Jinja engine.
425 To preprocess the notebook is to apply all of the
426 To preprocess the notebook is to apply all of the
426
427
427 Parameters
428 Parameters
428 ----------
429 ----------
429 nb : notebook node
430 nb : notebook node
430 notebook that is being exported.
431 notebook that is being exported.
431 resources : a dict of additional resources that
432 resources : a dict of additional resources that
432 can be accessed read/write by transformers
433 can be accessed read/write by transformers
433 and filters.
434 and filters.
434 """
435 """
435
436
436 # Do a copy.deepcopy first,
437 # Do a copy.deepcopy first,
437 # we are never safe enough with what the transformers could do.
438 # we are never safe enough with what the transformers could do.
438 nbc = copy.deepcopy(nb)
439 nbc = copy.deepcopy(nb)
439 resc = copy.deepcopy(resources)
440 resc = copy.deepcopy(resources)
440
441
441 #Run each transformer on the notebook. Carry the output along
442 #Run each transformer on the notebook. Carry the output along
442 #to each transformer
443 #to each transformer
443 for transformer in self._transformers:
444 for transformer in self._transformers:
444 nbc, resc = transformer(nbc, resc)
445 nbc, resc = transformer(nbc, resc)
445 return nbc, resc
446 return nbc, resc
@@ -1,152 +1,167 b''
1 # coding: utf-8
1 # coding: utf-8
2 """String filters.
2 """String filters.
3
3
4 Contains a collection of useful string manipulation filters for use in Jinja
4 Contains a collection of useful string manipulation filters for use in Jinja
5 templates.
5 templates.
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
8 # Copyright (c) 2013, the IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import re
19 import re
20 import textwrap
20 import textwrap
21 from xml.etree import ElementTree
21 from xml.etree import ElementTree
22
23 from IPython.core.interactiveshell import InteractiveShell
22 from IPython.utils import py3compat
24 from IPython.utils import py3compat
23
25
24 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
25 # Functions
27 # Functions
26 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
27
29
28 __all__ = [
30 __all__ = [
29 'wrap_text',
31 'wrap_text',
30 'html2text',
32 'html2text',
31 'add_anchor',
33 'add_anchor',
32 'strip_dollars',
34 'strip_dollars',
33 'strip_files_prefix',
35 'strip_files_prefix',
34 'comment_lines',
36 'comment_lines',
35 'get_lines'
37 'get_lines',
38 'ipython2python',
36 ]
39 ]
37
40
38
41
39 def wrap_text(text, width=100):
42 def wrap_text(text, width=100):
40 """
43 """
41 Intelligently wrap text.
44 Intelligently wrap text.
42 Wrap text without breaking words if possible.
45 Wrap text without breaking words if possible.
43
46
44 Parameters
47 Parameters
45 ----------
48 ----------
46 text : str
49 text : str
47 Text to wrap.
50 Text to wrap.
48 width : int, optional
51 width : int, optional
49 Number of characters to wrap to, default 100.
52 Number of characters to wrap to, default 100.
50 """
53 """
51
54
52 split_text = text.split('\n')
55 split_text = text.split('\n')
53 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
56 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
54 wrpd = map('\n'.join, wrp)
57 wrpd = map('\n'.join, wrp)
55 return '\n'.join(wrpd)
58 return '\n'.join(wrpd)
56
59
57
60
58 def html2text(element):
61 def html2text(element):
59 """extract inner text from html
62 """extract inner text from html
60
63
61 Analog of jQuery's $(element).text()
64 Analog of jQuery's $(element).text()
62 """
65 """
63 if isinstance(element, py3compat.string_types):
66 if isinstance(element, py3compat.string_types):
64 element = ElementTree.fromstring(element)
67 element = ElementTree.fromstring(element)
65
68
66 text = element.text or ""
69 text = element.text or ""
67 for child in element:
70 for child in element:
68 text += html2text(child)
71 text += html2text(child)
69 text += (element.tail or "")
72 text += (element.tail or "")
70 return text
73 return text
71
74
72
75
73 def add_anchor(html):
76 def add_anchor(html):
74 """Add an anchor-link to an html header tag
77 """Add an anchor-link to an html header tag
75
78
76 For use in heading cells
79 For use in heading cells
77 """
80 """
78 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html))
81 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html))
79 link = html2text(h).replace(' ', '-')
82 link = html2text(h).replace(' ', '-')
80 h.set('id', link)
83 h.set('id', link)
81 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
84 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
82 a.text = u'ΒΆ'
85 a.text = u'ΒΆ'
83 h.append(a)
86 h.append(a)
84 return ElementTree.tostring(h)
87 return ElementTree.tostring(h)
85
88
86
89
87 def strip_dollars(text):
90 def strip_dollars(text):
88 """
91 """
89 Remove all dollar symbols from text
92 Remove all dollar symbols from text
90
93
91 Parameters
94 Parameters
92 ----------
95 ----------
93 text : str
96 text : str
94 Text to remove dollars from
97 Text to remove dollars from
95 """
98 """
96
99
97 return text.strip('$')
100 return text.strip('$')
98
101
99
102
100 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
103 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
101
104
102 def strip_files_prefix(text):
105 def strip_files_prefix(text):
103 """
106 """
104 Fix all fake URLs that start with `files/`,
107 Fix all fake URLs that start with `files/`,
105 stripping out the `files/` prefix.
108 stripping out the `files/` prefix.
106
109
107 Parameters
110 Parameters
108 ----------
111 ----------
109 text : str
112 text : str
110 Text in which to replace 'src="files/real...' with 'src="real...'
113 Text in which to replace 'src="files/real...' with 'src="real...'
111 """
114 """
112 return files_url_pattern.sub(r"\1=\2", text)
115 return files_url_pattern.sub(r"\1=\2", text)
113
116
114
117
115 def comment_lines(text, prefix='# '):
118 def comment_lines(text, prefix='# '):
116 """
119 """
117 Build a Python comment line from input text.
120 Build a Python comment line from input text.
118
121
119 Parameters
122 Parameters
120 ----------
123 ----------
121 text : str
124 text : str
122 Text to comment out.
125 Text to comment out.
123 prefix : str
126 prefix : str
124 Character to append to the start of each line.
127 Character to append to the start of each line.
125 """
128 """
126
129
127 #Replace line breaks with line breaks and comment symbols.
130 #Replace line breaks with line breaks and comment symbols.
128 #Also add a comment symbol at the beginning to comment out
131 #Also add a comment symbol at the beginning to comment out
129 #the first line.
132 #the first line.
130 return prefix + ('\n'+prefix).join(text.split('\n'))
133 return prefix + ('\n'+prefix).join(text.split('\n'))
131
134
132
135
133 def get_lines(text, start=None,end=None):
136 def get_lines(text, start=None,end=None):
134 """
137 """
135 Split the input text into separate lines and then return the
138 Split the input text into separate lines and then return the
136 lines that the caller is interested in.
139 lines that the caller is interested in.
137
140
138 Parameters
141 Parameters
139 ----------
142 ----------
140 text : str
143 text : str
141 Text to parse lines from.
144 Text to parse lines from.
142 start : int, optional
145 start : int, optional
143 First line to grab from.
146 First line to grab from.
144 end : int, optional
147 end : int, optional
145 Last line to grab from.
148 Last line to grab from.
146 """
149 """
147
150
148 # Split the input into lines.
151 # Split the input into lines.
149 lines = text.split("\n")
152 lines = text.split("\n")
150
153
151 # Return the right lines.
154 # Return the right lines.
152 return "\n".join(lines[start:end]) #re-join
155 return "\n".join(lines[start:end]) #re-join
156
157 def ipython2python(code):
158 """Transform IPython syntax to pure Python syntax
159
160 Parameters
161 ----------
162
163 code : str
164 IPython code, to be transformed to pure Python
165 """
166 shell = InteractiveShell.instance()
167 return shell.input_transformer_manager.transform_cell(code)
@@ -1,56 +1,56 b''
1 {%- extends 'null.tpl' -%}
1 {%- extends 'null.tpl' -%}
2
2
3 {% block in_prompt %}
3 {% block in_prompt %}
4 # In[{{cell.prompt_number if cell.prompt_number else ' '}}]:
4 # In[{{cell.prompt_number if cell.prompt_number else ' '}}]:
5 {% endblock in_prompt %}
5 {% endblock in_prompt %}
6
6
7 {% block output_prompt %}
7 {% block output_prompt %}
8 # Out[{{cell.prompt_number}}]:{% endblock output_prompt %}
8 # Out[{{cell.prompt_number}}]:{% endblock output_prompt %}
9
9
10 {% block input %}{{ cell.input }}
10 {% block input %}{{ cell.input | ipython2python }}
11 {% endblock input %}
11 {% endblock input %}
12
12
13
13
14 {# Those Two are for error displaying
14 {# Those Two are for error displaying
15 even if the first one seem to do nothing,
15 even if the first one seem to do nothing,
16 it introduces a new line
16 it introduces a new line
17
17
18 #}
18 #}
19 {% block pyerr %}{{ super() }}
19 {% block pyerr %}{{ super() }}
20 {% endblock pyerr %}
20 {% endblock pyerr %}
21
21
22 {% block traceback_line %}
22 {% block traceback_line %}
23 {{ line |indent| strip_ansi }}{% endblock traceback_line %}
23 {{ line |indent| strip_ansi }}{% endblock traceback_line %}
24 {# .... #}
24 {# .... #}
25
25
26
26
27 {% block pyout %}
27 {% block pyout %}
28 {{ output.text| indent | comment_lines }}
28 {{ output.text| indent | comment_lines }}
29 {% endblock pyout %}
29 {% endblock pyout %}
30
30
31 {% block stream %}
31 {% block stream %}
32 {{ output.text| indent | comment_lines }}
32 {{ output.text| indent | comment_lines }}
33 {% endblock stream %}
33 {% endblock stream %}
34
34
35
35
36
36
37
37
38 {% block display_data scoped %}
38 {% block display_data scoped %}
39 # image file:
39 # image file:
40 {% endblock display_data %}
40 {% endblock display_data %}
41
41
42 {% block markdowncell scoped %}
42 {% block markdowncell scoped %}
43 {{ cell.source | comment_lines }}
43 {{ cell.source | comment_lines }}
44 {% endblock markdowncell %}
44 {% endblock markdowncell %}
45
45
46 {% block headingcell scoped %}
46 {% block headingcell scoped %}
47 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
47 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
48 {% endblock headingcell %}
48 {% endblock headingcell %}
49
49
50 {% block rawcell scoped %}
50 {% block rawcell scoped %}
51 {{ cell.source | comment_lines }}
51 {{ cell.source | comment_lines }}
52 {% endblock rawcell %}
52 {% endblock rawcell %}
53
53
54 {% block unknowncell scoped %}
54 {% block unknowncell scoped %}
55 unknown type {{cell.type}}
55 unknown type {{cell.type}}
56 {% endblock unknowncell %}
56 {% endblock unknowncell %}
General Comments 0
You need to be logged in to leave comments. Login now