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