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