Show More
@@ -0,0 +1,282 b'' | |||||
|
1 | """This module defines Exporter, a highly configurable converter | |||
|
2 | that uses Jinja2 to export notebook files into different formats. | |||
|
3 | """ | |||
|
4 | ||||
|
5 | #----------------------------------------------------------------------------- | |||
|
6 | # Copyright (c) 2013, the IPython Development Team. | |||
|
7 | # | |||
|
8 | # Distributed under the terms of the Modified BSD License. | |||
|
9 | # | |||
|
10 | # The full license is in the file COPYING.txt, distributed with this software. | |||
|
11 | #----------------------------------------------------------------------------- | |||
|
12 | ||||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | # Imports | |||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | ||||
|
17 | from __future__ import print_function, absolute_import | |||
|
18 | ||||
|
19 | # Stdlib imports | |||
|
20 | import io | |||
|
21 | import os | |||
|
22 | import copy | |||
|
23 | import collections | |||
|
24 | import datetime | |||
|
25 | ||||
|
26 | ||||
|
27 | # IPython imports | |||
|
28 | from IPython.config.configurable import LoggingConfigurable | |||
|
29 | from IPython.config import Config | |||
|
30 | from IPython.nbformat import current as nbformat | |||
|
31 | from IPython.utils.traitlets import MetaHasTraits, Unicode, List | |||
|
32 | from IPython.utils.importstring import import_item | |||
|
33 | from IPython.utils import py3compat | |||
|
34 | ||||
|
35 | from IPython.nbconvert import transformers as nbtransformers | |||
|
36 | ||||
|
37 | ||||
|
38 | #----------------------------------------------------------------------------- | |||
|
39 | # Class | |||
|
40 | #----------------------------------------------------------------------------- | |||
|
41 | ||||
|
42 | class ResourcesDict(collections.defaultdict): | |||
|
43 | def __missing__(self, key): | |||
|
44 | return '' | |||
|
45 | ||||
|
46 | ||||
|
47 | class BaseExporter(LoggingConfigurable): | |||
|
48 | """ | |||
|
49 | Base Exporter Class that only conver notebook to notebook | |||
|
50 | and apply the transformers and provide basic methods for | |||
|
51 | reading a notebook from different sources. | |||
|
52 | ||||
|
53 | """ | |||
|
54 | ||||
|
55 | # finish the docstring | |||
|
56 | ||||
|
57 | file_extension = Unicode( | |||
|
58 | 'txt', config=True, | |||
|
59 | help="Extension of the file that should be written to disk" | |||
|
60 | ) | |||
|
61 | ||||
|
62 | #Configurability, allows the user to easily add transformers. | |||
|
63 | transformers = List(config=True, | |||
|
64 | help="""List of transformers, by name or namespace, to enable.""") | |||
|
65 | ||||
|
66 | _transformers = None | |||
|
67 | ||||
|
68 | default_transformers = List([nbtransformers.coalesce_streams, | |||
|
69 | nbtransformers.SVG2PDFTransformer, | |||
|
70 | nbtransformers.ExtractOutputTransformer, | |||
|
71 | nbtransformers.CSSHTMLHeaderTransformer, | |||
|
72 | nbtransformers.RevealHelpTransformer, | |||
|
73 | nbtransformers.LatexTransformer, | |||
|
74 | nbtransformers.SphinxTransformer], | |||
|
75 | config=True, | |||
|
76 | help="""List of transformers available by default, by name, namespace, | |||
|
77 | instance, or type.""") | |||
|
78 | ||||
|
79 | ||||
|
80 | def __init__(self, config=None, **kw): | |||
|
81 | """ | |||
|
82 | Public constructor | |||
|
83 | ||||
|
84 | Parameters | |||
|
85 | ---------- | |||
|
86 | config : config | |||
|
87 | User configuration instance. | |||
|
88 | """ | |||
|
89 | if not config: | |||
|
90 | config = self.default_config | |||
|
91 | ||||
|
92 | super(BaseExporter, self).__init__(config=config, **kw) | |||
|
93 | ||||
|
94 | #Init | |||
|
95 | self._init_transformers() | |||
|
96 | ||||
|
97 | ||||
|
98 | @property | |||
|
99 | def default_config(self): | |||
|
100 | return Config() | |||
|
101 | ||||
|
102 | def _config_changed(self, name, old, new): | |||
|
103 | """When setting config, make sure to start with our default_config""" | |||
|
104 | c = self.default_config | |||
|
105 | if new: | |||
|
106 | c.merge(new) | |||
|
107 | if c != old: | |||
|
108 | self.config = c | |||
|
109 | super(BaseExporter, self)._config_changed(name, old, c) | |||
|
110 | ||||
|
111 | ||||
|
112 | def from_notebook_node(self, nb, resources=None): | |||
|
113 | """ | |||
|
114 | Convert a notebook from a notebook node instance. | |||
|
115 | ||||
|
116 | Parameters | |||
|
117 | ---------- | |||
|
118 | nb : Notebook node | |||
|
119 | resources : dict (**kw) | |||
|
120 | of additional resources that can be accessed read/write by | |||
|
121 | transformers. | |||
|
122 | """ | |||
|
123 | nb_copy = copy.deepcopy(nb) | |||
|
124 | resources = self._init_resources(resources) | |||
|
125 | ||||
|
126 | # Preprocess | |||
|
127 | nb_copy, resources = self._transform(nb_copy, resources) | |||
|
128 | ||||
|
129 | return nb_copy, resources | |||
|
130 | ||||
|
131 | ||||
|
132 | def from_filename(self, filename, resources=None, **kw): | |||
|
133 | """ | |||
|
134 | Convert a notebook from a notebook file. | |||
|
135 | ||||
|
136 | Parameters | |||
|
137 | ---------- | |||
|
138 | filename : str | |||
|
139 | Full filename of the notebook file to open and convert. | |||
|
140 | """ | |||
|
141 | ||||
|
142 | #Pull the metadata from the filesystem. | |||
|
143 | if resources is None: | |||
|
144 | resources = ResourcesDict() | |||
|
145 | if not 'metadata' in resources or resources['metadata'] == '': | |||
|
146 | resources['metadata'] = ResourcesDict() | |||
|
147 | basename = os.path.basename(filename) | |||
|
148 | notebook_name = basename[:basename.rfind('.')] | |||
|
149 | resources['metadata']['name'] = notebook_name | |||
|
150 | ||||
|
151 | modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename)) | |||
|
152 | resources['metadata']['modified_date'] = modified_date.strftime("%B %d, %Y") | |||
|
153 | ||||
|
154 | with io.open(filename) as f: | |||
|
155 | return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw) | |||
|
156 | ||||
|
157 | ||||
|
158 | def from_file(self, file_stream, resources=None, **kw): | |||
|
159 | """ | |||
|
160 | Convert a notebook from a notebook file. | |||
|
161 | ||||
|
162 | Parameters | |||
|
163 | ---------- | |||
|
164 | file_stream : file-like object | |||
|
165 | Notebook file-like object to convert. | |||
|
166 | """ | |||
|
167 | return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw) | |||
|
168 | ||||
|
169 | ||||
|
170 | def register_transformer(self, transformer, enabled=False): | |||
|
171 | """ | |||
|
172 | Register a transformer. | |||
|
173 | Transformers are classes that act upon the notebook before it is | |||
|
174 | passed into the Jinja templating engine. Transformers are also | |||
|
175 | capable of passing additional information to the Jinja | |||
|
176 | templating engine. | |||
|
177 | ||||
|
178 | Parameters | |||
|
179 | ---------- | |||
|
180 | transformer : transformer | |||
|
181 | """ | |||
|
182 | if transformer is None: | |||
|
183 | raise TypeError('transformer') | |||
|
184 | isclass = isinstance(transformer, type) | |||
|
185 | constructed = not isclass | |||
|
186 | ||||
|
187 | #Handle transformer's registration based on it's type | |||
|
188 | if constructed and isinstance(transformer, py3compat.string_types): | |||
|
189 | #Transformer is a string, import the namespace and recursively call | |||
|
190 | #this register_transformer method | |||
|
191 | transformer_cls = import_item(transformer) | |||
|
192 | return self.register_transformer(transformer_cls, enabled) | |||
|
193 | ||||
|
194 | if constructed and hasattr(transformer, '__call__'): | |||
|
195 | #Transformer is a function, no need to construct it. | |||
|
196 | #Register and return the transformer. | |||
|
197 | if enabled: | |||
|
198 | transformer.enabled = True | |||
|
199 | self._transformers.append(transformer) | |||
|
200 | return transformer | |||
|
201 | ||||
|
202 | elif isclass and isinstance(transformer, MetaHasTraits): | |||
|
203 | #Transformer is configurable. Make sure to pass in new default for | |||
|
204 | #the enabled flag if one was specified. | |||
|
205 | self.register_transformer(transformer(parent=self), enabled) | |||
|
206 | ||||
|
207 | elif isclass: | |||
|
208 | #Transformer is not configurable, construct it | |||
|
209 | self.register_transformer(transformer(), enabled) | |||
|
210 | ||||
|
211 | else: | |||
|
212 | #Transformer is an instance of something without a __call__ | |||
|
213 | #attribute. | |||
|
214 | raise TypeError('transformer') | |||
|
215 | ||||
|
216 | ||||
|
217 | def _init_transformers(self): | |||
|
218 | """ | |||
|
219 | Register all of the transformers needed for this exporter, disabled | |||
|
220 | unless specified explicitly. | |||
|
221 | """ | |||
|
222 | if self._transformers is None: | |||
|
223 | self._transformers = [] | |||
|
224 | ||||
|
225 | #Load default transformers (not necessarly enabled by default). | |||
|
226 | if self.default_transformers: | |||
|
227 | for transformer in self.default_transformers: | |||
|
228 | self.register_transformer(transformer) | |||
|
229 | ||||
|
230 | #Load user transformers. Enable by default. | |||
|
231 | if self.transformers: | |||
|
232 | for transformer in self.transformers: | |||
|
233 | self.register_transformer(transformer, enabled=True) | |||
|
234 | ||||
|
235 | ||||
|
236 | def _init_resources(self, resources): | |||
|
237 | ||||
|
238 | #Make sure the resources dict is of ResourcesDict type. | |||
|
239 | if resources is None: | |||
|
240 | resources = ResourcesDict() | |||
|
241 | if not isinstance(resources, ResourcesDict): | |||
|
242 | new_resources = ResourcesDict() | |||
|
243 | new_resources.update(resources) | |||
|
244 | resources = new_resources | |||
|
245 | ||||
|
246 | #Make sure the metadata extension exists in resources | |||
|
247 | if 'metadata' in resources: | |||
|
248 | if not isinstance(resources['metadata'], ResourcesDict): | |||
|
249 | resources['metadata'] = ResourcesDict(resources['metadata']) | |||
|
250 | else: | |||
|
251 | resources['metadata'] = ResourcesDict() | |||
|
252 | if not resources['metadata']['name']: | |||
|
253 | resources['metadata']['name'] = 'Notebook' | |||
|
254 | ||||
|
255 | #Set the output extension | |||
|
256 | resources['output_extension'] = self.file_extension | |||
|
257 | return resources | |||
|
258 | ||||
|
259 | ||||
|
260 | def _transform(self, nb, resources): | |||
|
261 | """ | |||
|
262 | Preprocess the notebook before passing it into the Jinja engine. | |||
|
263 | To preprocess the notebook is to apply all of the | |||
|
264 | ||||
|
265 | Parameters | |||
|
266 | ---------- | |||
|
267 | nb : notebook node | |||
|
268 | notebook that is being exported. | |||
|
269 | resources : a dict of additional resources that | |||
|
270 | can be accessed read/write by transformers | |||
|
271 | """ | |||
|
272 | ||||
|
273 | # Do a copy.deepcopy first, | |||
|
274 | # we are never safe enough with what the transformers could do. | |||
|
275 | nbc = copy.deepcopy(nb) | |||
|
276 | resc = copy.deepcopy(resources) | |||
|
277 | ||||
|
278 | #Run each transformer on the notebook. Carry the output along | |||
|
279 | #to each transformer | |||
|
280 | for transformer in self._transformers: | |||
|
281 | nbc, resc = transformer(nbc, resc) | |||
|
282 | return nbc, resc |
General Comments 0
You need to be logged in to leave comments.
Login now