##// END OF EJS Templates
Only set name if it doesn't exist!
Jonathan Frederic -
Show More
@@ -1,405 +1,405 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 types
23 import types
24 import copy
24 import copy
25 import collections
25 import collections
26 import datetime
26 import datetime
27
27
28 # other libs/dependencies
28 # other libs/dependencies
29 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
29 from jinja2 import Environment, FileSystemLoader, ChoiceLoader
30
30
31 # IPython imports
31 # IPython imports
32 from IPython.config.configurable import Configurable
32 from IPython.config.configurable import Configurable
33 from IPython.config import Config
33 from IPython.config import Config
34 from IPython.nbformat import current as nbformat
34 from IPython.nbformat import current as nbformat
35 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
35 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
36 from IPython.utils.importstring import import_item
36 from IPython.utils.importstring import import_item
37 from IPython.utils.text import indent
37 from IPython.utils.text import indent
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 'markdown': filters.markdown2html,
51 'markdown': 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 'highlight': filters.highlight,
55 'highlight': filters.highlight,
56 'highlight2html': filters.highlight,
56 'highlight2html': filters.highlight,
57 'highlight2latex': filters.highlight2latex,
57 'highlight2latex': filters.highlight2latex,
58 'markdown2latex': filters.markdown2latex,
58 'markdown2latex': filters.markdown2latex,
59 'markdown2rst': filters.markdown2rst,
59 'markdown2rst': filters.markdown2rst,
60 'pycomment': filters.python_comment,
60 'pycomment': filters.python_comment,
61 'rm_ansi': filters.remove_ansi,
61 'rm_ansi': filters.remove_ansi,
62 'rm_dollars': filters.strip_dollars,
62 'rm_dollars': filters.strip_dollars,
63 'rm_fake': filters.rm_fake,
63 'rm_fake': filters.rm_fake,
64 'ansi2latex': filters.ansi2latex,
64 'ansi2latex': filters.ansi2latex,
65 'rm_math_space': filters.rm_math_space,
65 'rm_math_space': filters.rm_math_space,
66 'wrap': filters.wrap
66 'wrap': filters.wrap
67 }
67 }
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Class
70 # Class
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73 class ResourcesDict(collections.defaultdict):
73 class ResourcesDict(collections.defaultdict):
74 def __missing__(self, key):
74 def __missing__(self, key):
75 return ''
75 return ''
76
76
77
77
78 class Exporter(Configurable):
78 class Exporter(Configurable):
79 """
79 """
80 Exports notebooks into other file formats. Uses Jinja 2 templating engine
80 Exports notebooks into other file formats. Uses Jinja 2 templating engine
81 to output new formats. Inherit from this class if you are creating a new
81 to output new formats. Inherit from this class if you are creating a new
82 template type along with new filters/transformers. If the filters/
82 template type along with new filters/transformers. If the filters/
83 transformers provided by default suffice, there is no need to inherit from
83 transformers provided by default suffice, there is no need to inherit from
84 this class. Instead, override the template_file and file_extension
84 this class. Instead, override the template_file and file_extension
85 traits via a config file.
85 traits via a config file.
86
86
87 {filters}
87 {filters}
88 """
88 """
89
89
90 # finish the docstring
90 # finish the docstring
91 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
91 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
92
92
93
93
94 template_file = Unicode(
94 template_file = Unicode(
95 '', config=True,
95 '', config=True,
96 help="Name of the template file to use")
96 help="Name of the template file to use")
97
97
98 file_extension = Unicode(
98 file_extension = Unicode(
99 'txt', config=True,
99 'txt', config=True,
100 help="Extension of the file that should be written to disk"
100 help="Extension of the file that should be written to disk"
101 )
101 )
102
102
103 template_path = Unicode(
103 template_path = Unicode(
104 os.path.join("..", "templates"), config=True,
104 os.path.join("..", "templates"), config=True,
105 help="Path where the template files are located.")
105 help="Path where the template files are located.")
106
106
107 template_skeleton_path = Unicode(
107 template_skeleton_path = Unicode(
108 os.path.join("..", "templates", "skeleton"), config=True,
108 os.path.join("..", "templates", "skeleton"), config=True,
109 help="Path where the template skeleton files are located.")
109 help="Path where the template skeleton files are located.")
110
110
111 #Jinja block definitions
111 #Jinja block definitions
112 jinja_comment_block_start = Unicode("", config=True)
112 jinja_comment_block_start = Unicode("", config=True)
113 jinja_comment_block_end = Unicode("", config=True)
113 jinja_comment_block_end = Unicode("", config=True)
114 jinja_variable_block_start = Unicode("", config=True)
114 jinja_variable_block_start = Unicode("", config=True)
115 jinja_variable_block_end = Unicode("", config=True)
115 jinja_variable_block_end = Unicode("", config=True)
116 jinja_logic_block_start = Unicode("", config=True)
116 jinja_logic_block_start = Unicode("", config=True)
117 jinja_logic_block_end = Unicode("", config=True)
117 jinja_logic_block_end = Unicode("", config=True)
118
118
119 #Extension that the template files use.
119 #Extension that the template files use.
120 template_extension = Unicode(".tpl", config=True)
120 template_extension = Unicode(".tpl", config=True)
121
121
122 #Configurability, allows the user to easily add filters and transformers.
122 #Configurability, allows the user to easily add filters and transformers.
123 transformers = List(config=True,
123 transformers = List(config=True,
124 help="""List of transformers, by name or namespace, to enable.""")
124 help="""List of transformers, by name or namespace, to enable.""")
125
125
126 filters = Dict(config=True,
126 filters = Dict(config=True,
127 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 help="""Dictionary of filters, by name and namespace, to add to the Jinja
128 environment.""")
128 environment.""")
129
129
130 default_transformers = List([nbtransformers.coalesce_streams,
130 default_transformers = List([nbtransformers.coalesce_streams,
131 nbtransformers.ExtractFigureTransformer],
131 nbtransformers.ExtractFigureTransformer],
132 config=True,
132 config=True,
133 help="""List of transformers available by default, by name, namespace,
133 help="""List of transformers available by default, by name, namespace,
134 instance, or type.""")
134 instance, or type.""")
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 loder to find templates. Will be tried in order
145 ordered list of Jinja loder to find templates. Will be tried in order
146 before the default FileSysteme ones.
146 before the default FileSysteme ones.
147 """
147 """
148
148
149 #Call the base class constructor
149 #Call the base class constructor
150 c = self.default_config
150 c = self.default_config
151 if config:
151 if config:
152 c.merge(config)
152 c.merge(config)
153
153
154 super(Exporter, self).__init__(config=c, **kw)
154 super(Exporter, self).__init__(config=c, **kw)
155
155
156 #Init
156 #Init
157 self._init_environment(extra_loaders=extra_loaders)
157 self._init_environment(extra_loaders=extra_loaders)
158 self._init_transformers()
158 self._init_transformers()
159 self._init_filters()
159 self._init_filters()
160
160
161
161
162 @property
162 @property
163 def default_config(self):
163 def default_config(self):
164 return Config()
164 return Config()
165
165
166
166
167 def from_notebook_node(self, nb, resources=None, **kw):
167 def from_notebook_node(self, nb, resources=None, **kw):
168 """
168 """
169 Convert a notebook from a notebook node instance.
169 Convert a notebook from a notebook node instance.
170
170
171 Parameters
171 Parameters
172 ----------
172 ----------
173 nb : Notebook node
173 nb : Notebook node
174 resources : dict (**kw)
174 resources : dict (**kw)
175 of additional resources that can be accessed read/write by
175 of additional resources that can be accessed read/write by
176 transformers and filters.
176 transformers and filters.
177 """
177 """
178 nb_copy = copy.deepcopy(nb)
178 nb_copy = copy.deepcopy(nb)
179 resources = self._init_resources(resources)
179 resources = self._init_resources(resources)
180
180
181 #Preprocess
181 #Preprocess
182 nb_copy, resources = self._transform(nb_copy, resources)
182 nb_copy, resources = self._transform(nb_copy, resources)
183
183
184 #Convert
184 #Convert
185 self.template = self.environment.get_template(self.template_file + self.template_extension)
185 self.template = self.environment.get_template(self.template_file + self.template_extension)
186 output = self.template.render(nb=nb_copy, resources=resources)
186 output = self.template.render(nb=nb_copy, resources=resources)
187 return output, resources
187 return output, resources
188
188
189
189
190 def from_filename(self, filename, resources=None, **kw):
190 def from_filename(self, filename, resources=None, **kw):
191 """
191 """
192 Convert a notebook from a notebook file.
192 Convert a notebook from a notebook file.
193
193
194 Parameters
194 Parameters
195 ----------
195 ----------
196 filename : str
196 filename : str
197 Full filename of the notebook file to open and convert.
197 Full filename of the notebook file to open and convert.
198 """
198 """
199
199
200 #Pull the metadata from the filesystem.
200 #Pull the metadata from the filesystem.
201 if not 'metadata' in resources:
201 if not 'metadata' in resources:
202 resources['metadata'] = ResourcesDict()
202 resources['metadata'] = ResourcesDict()
203 basename = os.path.basename(filename)
203 basename = os.path.basename(filename)
204 notebook_name = basename[:basename.rfind('.')]
204 notebook_name = basename[:basename.rfind('.')]
205 resources['metadata']['name'] = notebook_name
205 resources['metadata']['name'] = notebook_name
206
206
207 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
207 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
208 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
208 resources['metadata']['modified_date'] = modified_date.strftime("%B %-d, %Y")
209
209
210 with io.open(filename) as f:
210 with io.open(filename) as f:
211 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
211 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources,**kw)
212
212
213
213
214 def from_file(self, file_stream, resources=None, **kw):
214 def from_file(self, file_stream, resources=None, **kw):
215 """
215 """
216 Convert a notebook from a notebook file.
216 Convert a notebook from a notebook file.
217
217
218 Parameters
218 Parameters
219 ----------
219 ----------
220 file_stream : file-like object
220 file_stream : file-like object
221 Notebook file-like object to convert.
221 Notebook file-like object to convert.
222 """
222 """
223 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
223 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
224
224
225
225
226 def register_transformer(self, transformer, enabled=None):
226 def register_transformer(self, transformer, enabled=None):
227 """
227 """
228 Register a transformer.
228 Register a transformer.
229 Transformers are classes that act upon the notebook before it is
229 Transformers are classes that act upon the notebook before it is
230 passed into the Jinja templating engine. Transformers are also
230 passed into the Jinja templating engine. Transformers are also
231 capable of passing additional information to the Jinja
231 capable of passing additional information to the Jinja
232 templating engine.
232 templating engine.
233
233
234 Parameters
234 Parameters
235 ----------
235 ----------
236 transformer : transformer
236 transformer : transformer
237 """
237 """
238
238
239 #Handle transformer's registration based on it's type
239 #Handle transformer's registration based on it's type
240 if inspect.isfunction(transformer):
240 if inspect.isfunction(transformer):
241 #Transformer is a function, no need to construct it.
241 #Transformer is a function, no need to construct it.
242 self._transformers.append(transformer)
242 self._transformers.append(transformer)
243 return transformer
243 return transformer
244
244
245 elif isinstance(transformer, types.StringTypes):
245 elif isinstance(transformer, types.StringTypes):
246 #Transformer is a string, import the namespace and recursively call
246 #Transformer is a string, import the namespace and recursively call
247 #this register_transformer method
247 #this register_transformer method
248 transformer_cls = import_item(DottedObjectName(transformer))
248 transformer_cls = import_item(DottedObjectName(transformer))
249 return self.register_transformer(transformer_cls, enabled=None)
249 return self.register_transformer(transformer_cls, enabled=None)
250
250
251 elif isinstance(transformer, MetaHasTraits):
251 elif isinstance(transformer, MetaHasTraits):
252 #Transformer is configurable. Make sure to pass in new default for
252 #Transformer is configurable. Make sure to pass in new default for
253 #the enabled flag if one was specified.
253 #the enabled flag if one was specified.
254 transformer_instance = transformer(parent=self)
254 transformer_instance = transformer(parent=self)
255 if enabled is not None:
255 if enabled is not None:
256 transformer_instance.enabled = True
256 transformer_instance.enabled = True
257
257
258 else:
258 else:
259 #Transformer is not configurable, construct it
259 #Transformer is not configurable, construct it
260 transformer_instance = transformer()
260 transformer_instance = transformer()
261
261
262 #Register and return the transformer.
262 #Register and return the transformer.
263 self._transformers.append(transformer_instance)
263 self._transformers.append(transformer_instance)
264 return transformer_instance
264 return transformer_instance
265
265
266
266
267 def register_filter(self, name, filter):
267 def register_filter(self, name, filter):
268 """
268 """
269 Register a filter.
269 Register a filter.
270 A filter is a function that accepts and acts on one string.
270 A filter is a function that accepts and acts on one string.
271 The filters are accesible within the Jinja templating engine.
271 The filters are accesible within the Jinja templating engine.
272
272
273 Parameters
273 Parameters
274 ----------
274 ----------
275 name : str
275 name : str
276 name to give the filter in the Jinja engine
276 name to give the filter in the Jinja engine
277 filter : filter
277 filter : filter
278 """
278 """
279 if inspect.isfunction(filter):
279 if inspect.isfunction(filter):
280 self.environment.filters[name] = filter
280 self.environment.filters[name] = filter
281 elif isinstance(filter, types.StringTypes):
281 elif isinstance(filter, types.StringTypes):
282 filter_cls = import_item(DottedObjectName(filter))
282 filter_cls = import_item(DottedObjectName(filter))
283 self.register_filter(name, filter_cls)
283 self.register_filter(name, filter_cls)
284 elif isinstance(filter, MetaHasTraits):
284 elif isinstance(filter, MetaHasTraits):
285 self.environment.filters[name] = filter(config=self.config)
285 self.environment.filters[name] = filter(config=self.config)
286 else:
286 else:
287 self.environment.filters[name] = filter()
287 self.environment.filters[name] = filter()
288 return self.environment.filters[name]
288 return self.environment.filters[name]
289
289
290
290
291 def _init_environment(self, extra_loaders=None):
291 def _init_environment(self, extra_loaders=None):
292 """
292 """
293 Create the Jinja templating environment.
293 Create the Jinja templating environment.
294 """
294 """
295 here = os.path.dirname(os.path.realpath(__file__))
295 here = os.path.dirname(os.path.realpath(__file__))
296 loaders = []
296 loaders = []
297 if extra_loaders:
297 if extra_loaders:
298 loaders.extend(extra_loaders)
298 loaders.extend(extra_loaders)
299
299
300 loaders.append(FileSystemLoader([
300 loaders.append(FileSystemLoader([
301 os.path.join(here, self.template_path),
301 os.path.join(here, self.template_path),
302 os.path.join(here, self.template_skeleton_path),
302 os.path.join(here, self.template_skeleton_path),
303 ]))
303 ]))
304
304
305 self.environment = Environment(
305 self.environment = Environment(
306 loader= ChoiceLoader(loaders),
306 loader= ChoiceLoader(loaders),
307 extensions=JINJA_EXTENSIONS
307 extensions=JINJA_EXTENSIONS
308 )
308 )
309
309
310 #Set special Jinja2 syntax that will not conflict with latex.
310 #Set special Jinja2 syntax that will not conflict with latex.
311 if self.jinja_logic_block_start:
311 if self.jinja_logic_block_start:
312 self.environment.block_start_string = self.jinja_logic_block_start
312 self.environment.block_start_string = self.jinja_logic_block_start
313 if self.jinja_logic_block_end:
313 if self.jinja_logic_block_end:
314 self.environment.block_end_string = self.jinja_logic_block_end
314 self.environment.block_end_string = self.jinja_logic_block_end
315 if self.jinja_variable_block_start:
315 if self.jinja_variable_block_start:
316 self.environment.variable_start_string = self.jinja_variable_block_start
316 self.environment.variable_start_string = self.jinja_variable_block_start
317 if self.jinja_variable_block_end:
317 if self.jinja_variable_block_end:
318 self.environment.variable_end_string = self.jinja_variable_block_end
318 self.environment.variable_end_string = self.jinja_variable_block_end
319 if self.jinja_comment_block_start:
319 if self.jinja_comment_block_start:
320 self.environment.comment_start_string = self.jinja_comment_block_start
320 self.environment.comment_start_string = self.jinja_comment_block_start
321 if self.jinja_comment_block_end:
321 if self.jinja_comment_block_end:
322 self.environment.comment_end_string = self.jinja_comment_block_end
322 self.environment.comment_end_string = self.jinja_comment_block_end
323
323
324
324
325 def _init_transformers(self):
325 def _init_transformers(self):
326 """
326 """
327 Register all of the transformers needed for this exporter, disabled
327 Register all of the transformers needed for this exporter, disabled
328 unless specified explicitly.
328 unless specified explicitly.
329 """
329 """
330 self._transformers = []
330 self._transformers = []
331
331
332 #Load default transformers (not necessarly enabled by default).
332 #Load default transformers (not necessarly enabled by default).
333 if self.default_transformers:
333 if self.default_transformers:
334 for transformer in self.default_transformers:
334 for transformer in self.default_transformers:
335 self.register_transformer(transformer)
335 self.register_transformer(transformer)
336
336
337 #Load user transformers. Enable by default.
337 #Load user transformers. Enable by default.
338 if self.transformers:
338 if self.transformers:
339 for transformer in self.transformers:
339 for transformer in self.transformers:
340 self.register_transformer(transformer, enabled=True)
340 self.register_transformer(transformer, enabled=True)
341
341
342
342
343 def _init_filters(self):
343 def _init_filters(self):
344 """
344 """
345 Register all of the filters required for the exporter.
345 Register all of the filters required for the exporter.
346 """
346 """
347
347
348 #Add default filters to the Jinja2 environment
348 #Add default filters to the Jinja2 environment
349 for key, value in default_filters.iteritems():
349 for key, value in default_filters.iteritems():
350 self.register_filter(key, value)
350 self.register_filter(key, value)
351
351
352 #Load user filters. Overwrite existing filters if need be.
352 #Load user filters. Overwrite existing filters if need be.
353 if self.filters:
353 if self.filters:
354 for key, user_filter in self.filters.iteritems():
354 for key, user_filter in self.filters.iteritems():
355 self.register_filter(key, user_filter)
355 self.register_filter(key, user_filter)
356
356
357
357
358 def _init_resources(self, resources):
358 def _init_resources(self, resources):
359
359
360 #Make sure the resources dict is of ResourcesDict type.
360 #Make sure the resources dict is of ResourcesDict type.
361 if resources is None:
361 if resources is None:
362 resources = ResourcesDict()
362 resources = ResourcesDict()
363 if not isinstance(resources, ResourcesDict):
363 if not isinstance(resources, ResourcesDict):
364 new_resources = ResourcesDict()
364 new_resources = ResourcesDict()
365 new_resources.update(resources)
365 new_resources.update(resources)
366 resources = new_resources
366 resources = new_resources
367
367
368 #Make sure the metadata extension exists in resources
368 #Make sure the metadata extension exists in resources
369 if 'metadata' in resources:
369 if 'metadata' in resources:
370 if not isinstance(resources['metadata'], ResourcesDict):
370 if not isinstance(resources['metadata'], ResourcesDict):
371 resources['metadata'] = ResourcesDict(resources['metadata'])
371 resources['metadata'] = ResourcesDict(resources['metadata'])
372 else:
372 else:
373 resources['metadata'] = ResourcesDict()
373 resources['metadata'] = ResourcesDict()
374 resources['metadata']['name'] = 'Notebook'
374 if not resources['metadata']['name']:
375 resources['metadata']['modified_date'] = ''
375 resources['metadata']['name']: = 'Notebook'
376
376
377 #Set the output extension
377 #Set the output extension
378 resources['output_extension'] = self.file_extension
378 resources['output_extension'] = self.file_extension
379 return resources
379 return resources
380
380
381
381
382 def _transform(self, nb, resources):
382 def _transform(self, nb, resources):
383 """
383 """
384 Preprocess the notebook before passing it into the Jinja engine.
384 Preprocess the notebook before passing it into the Jinja engine.
385 To preprocess the notebook is to apply all of the
385 To preprocess the notebook is to apply all of the
386
386
387 Parameters
387 Parameters
388 ----------
388 ----------
389 nb : notebook node
389 nb : notebook node
390 notebook that is being exported.
390 notebook that is being exported.
391 resources : a dict of additional resources that
391 resources : a dict of additional resources that
392 can be accessed read/write by transformers
392 can be accessed read/write by transformers
393 and filters.
393 and filters.
394 """
394 """
395
395
396 # Do a copy.deepcopy first,
396 # Do a copy.deepcopy first,
397 # we are never safe enough with what the transformers could do.
397 # we are never safe enough with what the transformers could do.
398 nbc = copy.deepcopy(nb)
398 nbc = copy.deepcopy(nb)
399 resc = copy.deepcopy(resources)
399 resc = copy.deepcopy(resources)
400
400
401 #Run each transformer on the notebook. Carry the output along
401 #Run each transformer on the notebook. Carry the output along
402 #to each transformer
402 #to each transformer
403 for transformer in self._transformers:
403 for transformer in self._transformers:
404 nbc, resc = transformer(nbc, resc)
404 nbc, resc = transformer(nbc, resc)
405 return nbc, resc
405 return nbc, resc
General Comments 0
You need to be logged in to leave comments. Login now