##// END OF EJS Templates
Cast to unicode, not bytes
Jessica B. Hamrick -
Show More
@@ -1,279 +1,279 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, TraitType, 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(TraitType):
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 try:
35 value = py3compat.cast_bytes_py2(value)
35 value = py3compat.cast_unicode_py2(value)
36 36 except UnicodeDecodeError:
37 37 msg = "Could not decode {!r} for FileExtension trait '{}'."
38 38 raise TraitError(msg.format(value, self.name))
39 39
40 40 if not value.startswith('.'):
41 41 msg = "FileExtension trait '{}' does not begin with a dot: {!r}"
42 42 raise TraitError(msg.format(self.name, value))
43 43
44 44 return value
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 = FilenameExtension(
55 55 '.txt', config=True,
56 56 help="Extension of the file that should be written to disk"
57 57 )
58 58
59 59 # MIME type of the result file, for HTTP response headers.
60 60 # This is *not* a traitlet, because we want to be able to access it from
61 61 # the class, not just on instances.
62 62 output_mimetype = ''
63 63
64 64 #Configurability, allows the user to easily add filters and preprocessors.
65 65 preprocessors = List(config=True,
66 66 help="""List of preprocessors, by name or namespace, to enable.""")
67 67
68 68 _preprocessors = List()
69 69
70 70 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
71 71 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
72 72 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
73 73 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
74 74 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
75 75 'IPython.nbconvert.preprocessors.LatexPreprocessor',
76 76 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
77 77 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
78 78 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
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 basename = os.path.basename(filename)
148 148 notebook_name = basename[:basename.rfind('.')]
149 149 resources['metadata']['name'] = notebook_name
150 150
151 151 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
152 152 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
153 153
154 154 with io.open(filename, encoding='utf-8') as f:
155 155 return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw)
156 156
157 157
158 158 def from_file(self, file_stream, resources=None, **kw):
159 159 """
160 160 Convert a notebook from a notebook file.
161 161
162 162 Parameters
163 163 ----------
164 164 file_stream : file-like object
165 165 Notebook file-like object to convert.
166 166 """
167 167 return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
168 168
169 169
170 170 def register_preprocessor(self, preprocessor, enabled=False):
171 171 """
172 172 Register a preprocessor.
173 173 Preprocessors are classes that act upon the notebook before it is
174 174 passed into the Jinja templating engine. preprocessors are also
175 175 capable of passing additional information to the Jinja
176 176 templating engine.
177 177
178 178 Parameters
179 179 ----------
180 180 preprocessor : preprocessor
181 181 """
182 182 if preprocessor is None:
183 183 raise TypeError('preprocessor')
184 184 isclass = isinstance(preprocessor, type)
185 185 constructed = not isclass
186 186
187 187 # Handle preprocessor's registration based on it's type
188 188 if constructed and isinstance(preprocessor, py3compat.string_types):
189 189 # Preprocessor is a string, import the namespace and recursively call
190 190 # this register_preprocessor method
191 191 preprocessor_cls = import_item(preprocessor)
192 192 return self.register_preprocessor(preprocessor_cls, enabled)
193 193
194 194 if constructed and hasattr(preprocessor, '__call__'):
195 195 # Preprocessor is a function, no need to construct it.
196 196 # Register and return the preprocessor.
197 197 if enabled:
198 198 preprocessor.enabled = True
199 199 self._preprocessors.append(preprocessor)
200 200 return preprocessor
201 201
202 202 elif isclass and isinstance(preprocessor, MetaHasTraits):
203 203 # Preprocessor is configurable. Make sure to pass in new default for
204 204 # the enabled flag if one was specified.
205 205 self.register_preprocessor(preprocessor(parent=self), enabled)
206 206
207 207 elif isclass:
208 208 # Preprocessor is not configurable, construct it
209 209 self.register_preprocessor(preprocessor(), enabled)
210 210
211 211 else:
212 212 # Preprocessor is an instance of something without a __call__
213 213 # attribute.
214 214 raise TypeError('preprocessor')
215 215
216 216
217 217 def _init_preprocessors(self):
218 218 """
219 219 Register all of the preprocessors needed for this exporter, disabled
220 220 unless specified explicitly.
221 221 """
222 222 self._preprocessors = []
223 223
224 224 # Load default preprocessors (not necessarly enabled by default).
225 225 for preprocessor in self.default_preprocessors:
226 226 self.register_preprocessor(preprocessor)
227 227
228 228 # Load user-specified preprocessors. Enable by default.
229 229 for preprocessor in self.preprocessors:
230 230 self.register_preprocessor(preprocessor, enabled=True)
231 231
232 232
233 233 def _init_resources(self, resources):
234 234
235 235 #Make sure the resources dict is of ResourcesDict type.
236 236 if resources is None:
237 237 resources = ResourcesDict()
238 238 if not isinstance(resources, ResourcesDict):
239 239 new_resources = ResourcesDict()
240 240 new_resources.update(resources)
241 241 resources = new_resources
242 242
243 243 #Make sure the metadata extension exists in resources
244 244 if 'metadata' in resources:
245 245 if not isinstance(resources['metadata'], ResourcesDict):
246 246 resources['metadata'] = ResourcesDict(resources['metadata'])
247 247 else:
248 248 resources['metadata'] = ResourcesDict()
249 249 if not resources['metadata']['name']:
250 250 resources['metadata']['name'] = 'Notebook'
251 251
252 252 #Set the output extension
253 253 resources['output_extension'] = self.file_extension
254 254 return resources
255 255
256 256
257 257 def _preprocess(self, nb, resources):
258 258 """
259 259 Preprocess the notebook before passing it into the Jinja engine.
260 260 To preprocess the notebook is to apply all of the
261 261
262 262 Parameters
263 263 ----------
264 264 nb : notebook node
265 265 notebook that is being exported.
266 266 resources : a dict of additional resources that
267 267 can be accessed read/write by preprocessors
268 268 """
269 269
270 270 # Do a copy.deepcopy first,
271 271 # we are never safe enough with what the preprocessors could do.
272 272 nbc = copy.deepcopy(nb)
273 273 resc = copy.deepcopy(resources)
274 274
275 275 #Run each preprocessor on the notebook. Carry the output along
276 276 #to each preprocessor
277 277 for preprocessor in self._preprocessors:
278 278 nbc, resc = preprocessor(nbc, resc)
279 279 return nbc, resc
General Comments 0
You need to be logged in to leave comments. Login now