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