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