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