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