##// END OF EJS Templates
@MinRK fix suggestions
Jonathan Frederic -
Show More
@@ -1,281 +1,279 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 copy
22 import copy
23 import collections
23 import collections
24 import datetime
24 import datetime
25
25
26
26
27 # IPython imports
27 # IPython imports
28 from IPython.config.configurable import LoggingConfigurable
28 from IPython.config.configurable import LoggingConfigurable
29 from IPython.config import Config
29 from IPython.config import Config
30 from IPython.nbformat import current as nbformat
30 from IPython.nbformat import current as nbformat
31 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
31 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
32 from IPython.utils.importstring import import_item
32 from IPython.utils.importstring import import_item
33 from IPython.utils import text, py3compat
33 from IPython.utils import text, py3compat
34
34
35 from IPython.nbconvert import preprocessors as nbpreprocessors
35 from IPython.nbconvert import preprocessors as nbpreprocessors
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Class
39 # Class
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class ResourcesDict(collections.defaultdict):
42 class ResourcesDict(collections.defaultdict):
43 def __missing__(self, key):
43 def __missing__(self, key):
44 return ''
44 return ''
45
45
46
46
47 class Exporter(LoggingConfigurable):
47 class Exporter(LoggingConfigurable):
48 """
48 """
49 Exporter class that only converts from notebook to notebook
49 Class containing methods that sequentially run a list of preprocessors on a
50 by applying the preprocessors and providing basic methods for
50 NotebookNode object and then return the modified NotebookNode object and
51 reading a notebook from different sources.
51 accompanying resources dict.
52 """
52 """
53
53
54 # finish the docstring
55
56 file_extension = Unicode(
54 file_extension = Unicode(
57 'txt', config=True,
55 'txt', config=True,
58 help="Extension of the file that should be written to disk"
56 help="Extension of the file that should be written to disk"
59 )
57 )
60
58
61 #Configurability, allows the user to easily add filters and preprocessors.
59 #Configurability, allows the user to easily add filters and preprocessors.
62 preprocessors = List(config=True,
60 preprocessors = List(config=True,
63 help="""List of preprocessors, by name or namespace, to enable.""")
61 help="""List of preprocessors, by name or namespace, to enable.""")
64
62
65 _preprocessors = None
63 _preprocessors = None
66
64
67 default_preprocessors = List([nbpreprocessors.coalesce_streams,
65 default_preprocessors = List([nbpreprocessors.coalesce_streams,
68 nbpreprocessors.SVG2PDFPreprocessor,
66 nbpreprocessors.SVG2PDFPreprocessor,
69 nbpreprocessors.ExtractOutputPreprocessor,
67 nbpreprocessors.ExtractOutputPreprocessor,
70 nbpreprocessors.CSSHTMLHeaderPreprocessor,
68 nbpreprocessors.CSSHTMLHeaderPreprocessor,
71 nbpreprocessors.RevealHelpPreprocessor,
69 nbpreprocessors.RevealHelpPreprocessor,
72 nbpreprocessors.LatexPreprocessor,
70 nbpreprocessors.LatexPreprocessor,
73 nbpreprocessors.SphinxPreprocessor],
71 nbpreprocessors.SphinxPreprocessor],
74 config=True,
72 config=True,
75 help="""List of preprocessors available by default, by name, namespace,
73 help="""List of preprocessors available by default, by name, namespace,
76 instance, or type.""")
74 instance, or type.""")
77
75
78
76
79 def __init__(self, config=None, **kw):
77 def __init__(self, config=None, **kw):
80 """
78 """
81 Public constructor
79 Public constructor
82
80
83 Parameters
81 Parameters
84 ----------
82 ----------
85 config : config
83 config : config
86 User configuration instance.
84 User configuration instance.
87 """
85 """
88 if not config:
86 if not config:
89 config = self.default_config
87 config = self.default_config
90
88
91 super(Exporter, self).__init__(config=config, **kw)
89 super(Exporter, self).__init__(config=config, **kw)
92
90
93 #Init
91 #Init
94 self._init_preprocessors()
92 self._init_preprocessors()
95
93
96
94
97 @property
95 @property
98 def default_config(self):
96 def default_config(self):
99 return Config()
97 return Config()
100
98
101 def _config_changed(self, name, old, new):
99 def _config_changed(self, name, old, new):
102 """When setting config, make sure to start with our default_config"""
100 """When setting config, make sure to start with our default_config"""
103 c = self.default_config
101 c = self.default_config
104 if new:
102 if new:
105 c.merge(new)
103 c.merge(new)
106 if c != old:
104 if c != old:
107 self.config = c
105 self.config = c
108 super(Exporter, self)._config_changed(name, old, c)
106 super(Exporter, self)._config_changed(name, old, c)
109
107
110
108
111 def from_notebook_node(self, nb, resources=None):
109 def from_notebook_node(self, nb, resources=None, **kw):
112 """
110 """
113 Convert a notebook from a notebook node instance.
111 Convert a notebook from a notebook node instance.
114
112
115 Parameters
113 Parameters
116 ----------
114 ----------
117 nb : Notebook node
115 nb : Notebook node
118 resources : dict (**kw)
116 resources : dict (**kw)
119 of additional resources that can be accessed read/write by
117 of additional resources that can be accessed read/write by
120 preprocessors.
118 preprocessors.
121 """
119 """
122 nb_copy = copy.deepcopy(nb)
120 nb_copy = copy.deepcopy(nb)
123 resources = self._init_resources(resources)
121 resources = self._init_resources(resources)
124
122
125 # Preprocess
123 # Preprocess
126 nb_copy, resources = self._transform(nb_copy, resources)
124 nb_copy, resources = self._preprocess(nb_copy, resources)
127
125
128 return nb_copy, resources
126 return nb_copy, resources
129
127
130
128
131 def from_filename(self, filename, resources=None, **kw):
129 def from_filename(self, filename, resources=None, **kw):
132 """
130 """
133 Convert a notebook from a notebook file.
131 Convert a notebook from a notebook file.
134
132
135 Parameters
133 Parameters
136 ----------
134 ----------
137 filename : str
135 filename : str
138 Full filename of the notebook file to open and convert.
136 Full filename of the notebook file to open and convert.
139 """
137 """
140
138
141 # Pull the metadata from the filesystem.
139 # Pull the metadata from the filesystem.
142 if resources is None:
140 if resources is None:
143 resources = ResourcesDict()
141 resources = ResourcesDict()
144 if not 'metadata' in resources or resources['metadata'] == '':
142 if not 'metadata' in resources or resources['metadata'] == '':
145 resources['metadata'] = ResourcesDict()
143 resources['metadata'] = ResourcesDict()
146 basename = os.path.basename(filename)
144 basename = os.path.basename(filename)
147 notebook_name = basename[:basename.rfind('.')]
145 notebook_name = basename[:basename.rfind('.')]
148 resources['metadata']['name'] = notebook_name
146 resources['metadata']['name'] = notebook_name
149
147
150 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
148 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
151 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
149 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
152
150
153 with io.open(filename) as f:
151 with io.open(filename) as f:
154 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw)
152 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw)
155
153
156
154
157 def from_file(self, file_stream, resources=None, **kw):
155 def from_file(self, file_stream, resources=None, **kw):
158 """
156 """
159 Convert a notebook from a notebook file.
157 Convert a notebook from a notebook file.
160
158
161 Parameters
159 Parameters
162 ----------
160 ----------
163 file_stream : file-like object
161 file_stream : file-like object
164 Notebook file-like object to convert.
162 Notebook file-like object to convert.
165 """
163 """
166 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
164 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
167
165
168
166
169 def register_preprocessor(self, preprocessor, enabled=False):
167 def register_preprocessor(self, preprocessor, enabled=False):
170 """
168 """
171 Register a preprocessor.
169 Register a preprocessor.
172 preprocessors are classes that act upon the notebook before it is
170 Preprocessors are classes that act upon the notebook before it is
173 passed into the Jinja templating engine. preprocessors are also
171 passed into the Jinja templating engine. preprocessors are also
174 capable of passing additional information to the Jinja
172 capable of passing additional information to the Jinja
175 templating engine.
173 templating engine.
176
174
177 Parameters
175 Parameters
178 ----------
176 ----------
179 preprocessor : preprocessor
177 preprocessor : preprocessor
180 """
178 """
181 if preprocessor is None:
179 if preprocessor is None:
182 raise TypeError('preprocessor')
180 raise TypeError('preprocessor')
183 isclass = isinstance(preprocessor, type)
181 isclass = isinstance(preprocessor, type)
184 constructed = not isclass
182 constructed = not isclass
185
183
186 #Handle preprocessor's registration based on it's type
184 #Handle preprocessor's registration based on it's type
187 if constructed and isinstance(preprocessor, py3compat.string_types):
185 if constructed and isinstance(preprocessor, py3compat.string_types):
188 #preprocessor is a string, import the namespace and recursively call
186 #preprocessor is a string, import the namespace and recursively call
189 #this register_preprocessor method
187 #this register_preprocessor method
190 preprocessor_cls = import_item(preprocessor)
188 preprocessor_cls = import_item(preprocessor)
191 return self.register_preprocessor(preprocessor_cls, enabled)
189 return self.register_preprocessor(preprocessor_cls, enabled)
192
190
193 if constructed and hasattr(preprocessor, '__call__'):
191 if constructed and hasattr(preprocessor, '__call__'):
194 #preprocessor is a function, no need to construct it.
192 #preprocessor is a function, no need to construct it.
195 #Register and return the preprocessor.
193 #Register and return the preprocessor.
196 if enabled:
194 if enabled:
197 preprocessor.enabled = True
195 preprocessor.enabled = True
198 self._preprocessors.append(preprocessor)
196 self._preprocessors.append(preprocessor)
199 return preprocessor
197 return preprocessor
200
198
201 elif isclass and isinstance(preprocessor, MetaHasTraits):
199 elif isclass and isinstance(preprocessor, MetaHasTraits):
202 #preprocessor is configurable. Make sure to pass in new default for
200 #preprocessor is configurable. Make sure to pass in new default for
203 #the enabled flag if one was specified.
201 #the enabled flag if one was specified.
204 self.register_preprocessor(preprocessor(parent=self), enabled)
202 self.register_preprocessor(preprocessor(parent=self), enabled)
205
203
206 elif isclass:
204 elif isclass:
207 #preprocessor is not configurable, construct it
205 #preprocessor is not configurable, construct it
208 self.register_preprocessor(preprocessor(), enabled)
206 self.register_preprocessor(preprocessor(), enabled)
209
207
210 else:
208 else:
211 #preprocessor is an instance of something without a __call__
209 #preprocessor is an instance of something without a __call__
212 #attribute.
210 #attribute.
213 raise TypeError('preprocessor')
211 raise TypeError('preprocessor')
214
212
215
213
216 def _init_preprocessors(self):
214 def _init_preprocessors(self):
217 """
215 """
218 Register all of the preprocessors needed for this exporter, disabled
216 Register all of the preprocessors needed for this exporter, disabled
219 unless specified explicitly.
217 unless specified explicitly.
220 """
218 """
221 if self._preprocessors is None:
219 if self._preprocessors is None:
222 self._preprocessors = []
220 self._preprocessors = []
223
221
224 #Load default preprocessors (not necessarly enabled by default).
222 #Load default preprocessors (not necessarly enabled by default).
225 if self.default_preprocessors:
223 if self.default_preprocessors:
226 for preprocessor in self.default_preprocessors:
224 for preprocessor in self.default_preprocessors:
227 self.register_preprocessor(preprocessor)
225 self.register_preprocessor(preprocessor)
228
226
229 #Load user preprocessors. Enable by default.
227 #Load user preprocessors. Enable by default.
230 if self.preprocessors:
228 if self.preprocessors:
231 for preprocessor in self.preprocessors:
229 for preprocessor in self.preprocessors:
232 self.register_preprocessor(preprocessor, enabled=True)
230 self.register_preprocessor(preprocessor, enabled=True)
233
231
234
232
235 def _init_resources(self, resources):
233 def _init_resources(self, resources):
236
234
237 #Make sure the resources dict is of ResourcesDict type.
235 #Make sure the resources dict is of ResourcesDict type.
238 if resources is None:
236 if resources is None:
239 resources = ResourcesDict()
237 resources = ResourcesDict()
240 if not isinstance(resources, ResourcesDict):
238 if not isinstance(resources, ResourcesDict):
241 new_resources = ResourcesDict()
239 new_resources = ResourcesDict()
242 new_resources.update(resources)
240 new_resources.update(resources)
243 resources = new_resources
241 resources = new_resources
244
242
245 #Make sure the metadata extension exists in resources
243 #Make sure the metadata extension exists in resources
246 if 'metadata' in resources:
244 if 'metadata' in resources:
247 if not isinstance(resources['metadata'], ResourcesDict):
245 if not isinstance(resources['metadata'], ResourcesDict):
248 resources['metadata'] = ResourcesDict(resources['metadata'])
246 resources['metadata'] = ResourcesDict(resources['metadata'])
249 else:
247 else:
250 resources['metadata'] = ResourcesDict()
248 resources['metadata'] = ResourcesDict()
251 if not resources['metadata']['name']:
249 if not resources['metadata']['name']:
252 resources['metadata']['name'] = 'Notebook'
250 resources['metadata']['name'] = 'Notebook'
253
251
254 #Set the output extension
252 #Set the output extension
255 resources['output_extension'] = self.file_extension
253 resources['output_extension'] = self.file_extension
256 return resources
254 return resources
257
255
258
256
259 def _transform(self, nb, resources):
257 def _preprocess(self, nb, resources):
260 """
258 """
261 Preprocess the notebook before passing it into the Jinja engine.
259 Preprocess the notebook before passing it into the Jinja engine.
262 To preprocess the notebook is to apply all of the
260 To preprocess the notebook is to apply all of the
263
261
264 Parameters
262 Parameters
265 ----------
263 ----------
266 nb : notebook node
264 nb : notebook node
267 notebook that is being exported.
265 notebook that is being exported.
268 resources : a dict of additional resources that
266 resources : a dict of additional resources that
269 can be accessed read/write by preprocessors
267 can be accessed read/write by preprocessors
270 """
268 """
271
269
272 # Do a copy.deepcopy first,
270 # Do a copy.deepcopy first,
273 # we are never safe enough with what the preprocessors could do.
271 # we are never safe enough with what the preprocessors could do.
274 nbc = copy.deepcopy(nb)
272 nbc = copy.deepcopy(nb)
275 resc = copy.deepcopy(resources)
273 resc = copy.deepcopy(resources)
276
274
277 #Run each preprocessor on the notebook. Carry the output along
275 #Run each preprocessor on the notebook. Carry the output along
278 #to each preprocessor
276 #to each preprocessor
279 for preprocessor in self._preprocessors:
277 for preprocessor in self._preprocessors:
280 nbc, resc = preprocessor(nbc, resc)
278 nbc, resc = preprocessor(nbc, resc)
281 return nbc, resc
279 return nbc, resc
General Comments 0
You need to be logged in to leave comments. Login now