##// END OF EJS Templates
Merge pull request #3846 from minrk/nbconvert_log...
Jonathan Frederic -
r11882:5fb79fea merge
parent child Browse files
Show More
@@ -85,7 +85,6 __all__ = [
85 class ExporterNameError(NameError):
85 class ExporterNameError(NameError):
86 pass
86 pass
87
87
88
89 @DocDecorator
88 @DocDecorator
90 def export(exporter, nb, **kw):
89 def export(exporter, nb, **kw):
91 """
90 """
@@ -112,7 +111,7 def export(exporter, nb, **kw):
112 exporter_instance = exporter
111 exporter_instance = exporter
113 else:
112 else:
114 exporter_instance = exporter(**kw)
113 exporter_instance = exporter(**kw)
115
114
116 #Try to convert the notebook using the appropriate conversion function.
115 #Try to convert the notebook using the appropriate conversion function.
117 if isinstance(nb, NotebookNode):
116 if isinstance(nb, NotebookNode):
118 output, resources = exporter_instance.from_notebook_node(nb, resources)
117 output, resources = exporter_instance.from_notebook_node(nb, resources)
@@ -122,62 +121,27 def export(exporter, nb, **kw):
122 output, resources = exporter_instance.from_file(nb, resources)
121 output, resources = exporter_instance.from_file(nb, resources)
123 return output, resources
122 return output, resources
124
123
124 exporter_map = dict(
125 custom=Exporter,
126 html=HTMLExporter,
127 slides=SlidesExporter,
128 latex=LatexExporter,
129 markdown=MarkdownExporter,
130 python=PythonExporter,
131 rst=RSTExporter,
132 )
133
134 def _make_exporter(name, E):
135 """make an export_foo function from a short key and Exporter class E"""
136 def _export(nb, **kw):
137 return export(E, nb, **kw)
138 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
139 return _export
140
141 g = globals()
125
142
126 @DocDecorator
143 for name, E in exporter_map.items():
127 def export_custom(nb, **kw):
144 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
128 """
129 Export a notebook object to a custom format
130 """
131 return export(Exporter, nb, **kw)
132
133
134 @DocDecorator
135 def export_html(nb, **kw):
136 """
137 Export a notebook object to HTML
138 """
139 return export(HTMLExporter, nb, **kw)
140
141
142 @DocDecorator
143 def export_slides(nb, **kw):
144 """
145 Export a notebook object to Slides
146 """
147 return export(SlidesExporter, nb, **kw)
148
149
150 @DocDecorator
151 def export_latex(nb, **kw):
152 """
153 Export a notebook object to LaTeX
154 """
155 return export(LatexExporter, nb, **kw)
156
157
158 @DocDecorator
159 def export_markdown(nb, **kw):
160 """
161 Export a notebook object to Markdown
162 """
163 return export(MarkdownExporter, nb, **kw)
164
165
166 @DocDecorator
167 def export_python(nb, **kw):
168 """
169 Export a notebook object to Python
170 """
171 return export(PythonExporter, nb, **kw)
172
173
174 @DocDecorator
175 def export_rst(nb, **kw):
176 """
177 Export a notebook object to reStructuredText
178 """
179 return export(RSTExporter, nb, **kw)
180
181
145
182 @DocDecorator
146 @DocDecorator
183 def export_by_name(format_name, nb, **kw):
147 def export_by_name(format_name, nb, **kw):
@@ -202,10 +166,4 def get_export_names():
202 """Return a list of the currently supported export targets
166 """Return a list of the currently supported export targets
203
167
204 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
168 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
205
169 return sorted(exporter_map.keys())
206 # grab everything after 'export_'
207 l = [x[len('export_'):] for x in __all__ if x.startswith('export_')]
208
209 # filter out the one method that is not a template
210 l = [x for x in l if 'by_name' not in x]
211 return sorted(l)
@@ -28,10 +28,10 import datetime
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
28 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
29
29
30 # IPython imports
30 # IPython imports
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import LoggingConfigurable
32 from IPython.config import Config
32 from IPython.config import Config
33 from IPython.nbformat import current as nbformat
33 from IPython.nbformat import current as nbformat
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict
34 from IPython.utils.traitlets import MetaHasTraits, DottedObjectName, Unicode, List, Dict, Any
35 from IPython.utils.importstring import import_item
35 from IPython.utils.importstring import import_item
36 from IPython.utils.text import indent
36 from IPython.utils.text import indent
37 from IPython.utils import py3compat
37 from IPython.utils import py3compat
@@ -78,7 +78,7 class ResourcesDict(collections.defaultdict):
78 return ''
78 return ''
79
79
80
80
81 class Exporter(Configurable):
81 class Exporter(LoggingConfigurable):
82 """
82 """
83 Exports notebooks into other file formats. Uses Jinja 2 templating engine
83 Exports notebooks into other file formats. Uses Jinja 2 templating engine
84 to output new formats. Inherit from this class if you are creating a new
84 to output new formats. Inherit from this class if you are creating a new
@@ -102,7 +102,12 class Exporter(Configurable):
102 self.template_file = self.default_template
102 self.template_file = self.default_template
103 else:
103 else:
104 self.template_file = new
104 self.template_file = new
105 self.template = None
106 self._load_template()
107
105 default_template = Unicode(u'')
108 default_template = Unicode(u'')
109 template = Any()
110 environment = Any()
106
111
107 file_extension = Unicode(
112 file_extension = Unicode(
108 'txt', config=True,
113 'txt', config=True,
@@ -110,6 +115,8 class Exporter(Configurable):
110 )
115 )
111
116
112 template_path = List(['.'], config=True)
117 template_path = List(['.'], config=True)
118 def _template_path_changed(self, name, old, new):
119 self._load_template()
113
120
114 default_template_path = Unicode(
121 default_template_path = Unicode(
115 os.path.join("..", "templates"),
122 os.path.join("..", "templates"),
@@ -159,21 +166,18 class Exporter(Configurable):
159 config : config
166 config : config
160 User configuration instance.
167 User configuration instance.
161 extra_loaders : list[of Jinja Loaders]
168 extra_loaders : list[of Jinja Loaders]
162 ordered list of Jinja loder to find templates. Will be tried in order
169 ordered list of Jinja loader to find templates. Will be tried in order
163 before the default FileSysteme ones.
170 before the default FileSystem ones.
164 template : str (optional, kw arg)
171 template : str (optional, kw arg)
165 Template to use when exporting.
172 Template to use when exporting.
166 """
173 """
174 if not config:
175 config = self.default_config
167
176
168 #Call the base class constructor
177 super(Exporter, self).__init__(config=config, **kw)
169 c = self.default_config
170 if config:
171 c.merge(config)
172
173 super(Exporter, self).__init__(config=c, **kw)
174
178
175 #Init
179 #Init
176 self._init_template(**kw)
180 self._init_template()
177 self._init_environment(extra_loaders=extra_loaders)
181 self._init_environment(extra_loaders=extra_loaders)
178 self._init_transformers()
182 self._init_transformers()
179 self._init_filters()
183 self._init_filters()
@@ -182,7 +186,48 class Exporter(Configurable):
182 @property
186 @property
183 def default_config(self):
187 def default_config(self):
184 return Config()
188 return Config()
189
190 def _config_changed(self, name, old, new):
191 """When setting config, make sure to start with our default_config"""
192 c = self.default_config
193 if new:
194 c.merge(new)
195 if c != old:
196 self.config = c
197 super(Exporter, self)._config_changed(name, old, c)
198
185
199
200 def _load_template(self):
201 """Load the Jinja template object from the template file
202
203 This is a no-op if the template attribute is already defined,
204 or the Jinja environment is not setup yet.
205
206 This is triggered by various trait changes that would change the template.
207 """
208 if self.template is not None:
209 return
210 # called too early, do nothing
211 if self.environment is None:
212 return
213 # Try different template names during conversion. First try to load the
214 # template by name with extension added, then try loading the template
215 # as if the name is explicitly specified, then try the name as a
216 # 'flavor', and lastly just try to load the template by module name.
217 module_name = self.__module__.rsplit('.', 1)[-1]
218 try_names = [self.template_file + self.template_extension,
219 self.template_file,
220 module_name + '_' + self.template_file + self.template_extension,
221 module_name + self.template_extension]
222 for try_name in try_names:
223 self.log.debug("Attempting to load template %s", try_name)
224 try:
225 self.template = self.environment.get_template(try_name)
226 except TemplateNotFound:
227 pass
228 else:
229 self.log.info("Loaded template %s", try_name)
230 break
186
231
187 def from_notebook_node(self, nb, resources=None, **kw):
232 def from_notebook_node(self, nb, resources=None, **kw):
188 """
233 """
@@ -201,23 +246,9 class Exporter(Configurable):
201 # Preprocess
246 # Preprocess
202 nb_copy, resources = self._transform(nb_copy, resources)
247 nb_copy, resources = self._transform(nb_copy, resources)
203
248
204 # Try different template names during conversion. First try to load the
249 self._load_template()
205 # template by name with extension added, then try loading the template
206 # as if the name is explicitly specified, then try the name as a
207 # 'flavor', and lastly just try to load the template by module name.
208 module_name = self.__module__.split('.')[-1]
209 try_names = [self.template_file + self.template_extension,
210 self.template_file,
211 module_name + '_' + self.template_file + self.template_extension,
212 module_name + self.template_extension]
213 for try_name in try_names:
214 try:
215 self.template = self.environment.get_template(try_name)
216 break
217 except TemplateNotFound:
218 pass
219
250
220 if hasattr(self, 'template'):
251 if self.template is not None:
221 output = self.template.render(nb=nb_copy, resources=resources)
252 output = self.template.render(nb=nb_copy, resources=resources)
222 else:
253 else:
223 raise IOError('template file "%s" could not be found' % self.template_file)
254 raise IOError('template file "%s" could not be found' % self.template_file)
@@ -355,14 +386,12 class Exporter(Configurable):
355 raise TypeError('filter')
386 raise TypeError('filter')
356
387
357
388
358 def _init_template(self, **kw):
389 def _init_template(self):
359 """
390 """
360 Make sure a template name is specified. If one isn't specified, try to
391 Make sure a template name is specified. If one isn't specified, try to
361 build one from the information we know.
392 build one from the information we know.
362 """
393 """
363 self._template_file_changed('template_file', self.template_file, self.template_file)
394 self._template_file_changed('template_file', self.template_file, self.template_file)
364 if 'template' in kw:
365 self.template_file = kw['template']
366
395
367
396
368 def _init_environment(self, extra_loaders=None):
397 def _init_environment(self, extra_loaders=None):
@@ -95,7 +95,7 class TestExporter(ExportersTestsBase):
95
95
96 def test_transformer_via_method(self):
96 def test_transformer_via_method(self):
97 """
97 """
98 Can a transformer be added via the Exporter convinience method?
98 Can a transformer be added via the Exporter convenience method?
99 """
99 """
100 exporter = self._make_exporter()
100 exporter = self._make_exporter()
101 exporter.register_transformer(CheeseTransformer, enabled=True)
101 exporter.register_transformer(CheeseTransformer, enabled=True)
@@ -108,6 +108,5 class TestExporter(ExportersTestsBase):
108 def _make_exporter(self, config=None):
108 def _make_exporter(self, config=None):
109 #Create the exporter instance, make sure to set a template name since
109 #Create the exporter instance, make sure to set a template name since
110 #the base Exporter doesn't have a template associated with it.
110 #the base Exporter doesn't have a template associated with it.
111 exporter = Exporter(config=config)
111 exporter = Exporter(config=config, template_file='python')
112 exporter.template_file = 'python'
113 return exporter No newline at end of file
112 return exporter
@@ -46,7 +46,7 class TestHTMLExporter(ExportersTestsBase):
46 """
46 """
47 Can a HTMLExporter export using the 'basic' template?
47 Can a HTMLExporter export using the 'basic' template?
48 """
48 """
49 (output, resources) = HTMLExporter(template='basic').from_filename(self._get_notebook())
49 (output, resources) = HTMLExporter(template_file='basic').from_filename(self._get_notebook())
50 assert len(output) > 0
50 assert len(output) > 0
51
51
52
52
@@ -55,5 +55,5 class TestHTMLExporter(ExportersTestsBase):
55 """
55 """
56 Can a HTMLExporter export using the 'full' template?
56 Can a HTMLExporter export using the 'full' template?
57 """
57 """
58 (output, resources) = HTMLExporter(template='full').from_filename(self._get_notebook())
58 (output, resources) = HTMLExporter(template_file='full').from_filename(self._get_notebook())
59 assert len(output) > 0
59 assert len(output) > 0
@@ -46,7 +46,7 class TestLatexExporter(ExportersTestsBase):
46 """
46 """
47 Can a LatexExporter export using 'book' template?
47 Can a LatexExporter export using 'book' template?
48 """
48 """
49 (output, resources) = LatexExporter(template='book').from_filename(self._get_notebook())
49 (output, resources) = LatexExporter(template_file='book').from_filename(self._get_notebook())
50 assert len(output) > 0
50 assert len(output) > 0
51
51
52
52
@@ -55,7 +55,7 class TestLatexExporter(ExportersTestsBase):
55 """
55 """
56 Can a LatexExporter export using 'basic' template?
56 Can a LatexExporter export using 'basic' template?
57 """
57 """
58 (output, resources) = LatexExporter(template='basic').from_filename(self._get_notebook())
58 (output, resources) = LatexExporter(template_file='basic').from_filename(self._get_notebook())
59 assert len(output) > 0
59 assert len(output) > 0
60
60
61
61
@@ -64,5 +64,5 class TestLatexExporter(ExportersTestsBase):
64 """
64 """
65 Can a LatexExporter export using 'article' template?
65 Can a LatexExporter export using 'article' template?
66 """
66 """
67 (output, resources) = LatexExporter(template='article').from_filename(self._get_notebook())
67 (output, resources) = LatexExporter(template_file='article').from_filename(self._get_notebook())
68 assert len(output) > 0 No newline at end of file
68 assert len(output) > 0
@@ -46,5 +46,5 class TestSlidesExporter(ExportersTestsBase):
46 """
46 """
47 Can a SlidesExporter export using the 'reveal' template?
47 Can a SlidesExporter export using the 'reveal' template?
48 """
48 """
49 (output, resources) = SlidesExporter(template='reveal').from_filename(self._get_notebook())
49 (output, resources) = SlidesExporter(template_file='reveal').from_filename(self._get_notebook())
50 assert len(output) > 0
50 assert len(output) > 0
@@ -17,6 +17,8 Command-line interface for the NbConvert conversion utility.
17
17
18 # Stdlib imports
18 # Stdlib imports
19 from __future__ import print_function
19 from __future__ import print_function
20
21 import logging
20 import sys
22 import sys
21 import os
23 import os
22 import glob
24 import glob
@@ -30,7 +32,7 from IPython.utils.traitlets import (
30 from IPython.utils.importstring import import_item
32 from IPython.utils.importstring import import_item
31 from IPython.utils.text import dedent
33 from IPython.utils.text import dedent
32
34
33 from .exporters.export import export_by_name, get_export_names, ExporterNameError
35 from .exporters.export import get_export_names, exporter_map
34 from IPython.nbconvert import exporters, transformers, writers, post_processors
36 from IPython.nbconvert import exporters, transformers, writers, post_processors
35 from .utils.base import NbConvertBase
37 from .utils.base import NbConvertBase
36 from .utils.exceptions import ConversionException
38 from .utils.exceptions import ConversionException
@@ -80,6 +82,9 class NbConvertApp(BaseIPythonApplication):
80 aliases = nbconvert_aliases
82 aliases = nbconvert_aliases
81 flags = nbconvert_flags
83 flags = nbconvert_flags
82
84
85 def _log_level_default(self):
86 return logging.INFO
87
83 def _classes_default(self):
88 def _classes_default(self):
84 classes = [NbConvertBase]
89 classes = [NbConvertBase]
85 for pkg in (exporters, transformers, writers):
90 for pkg in (exporters, transformers, writers):
@@ -229,6 +234,8 class NbConvertApp(BaseIPythonApplication):
229 # notebooks without having to type the extension.
234 # notebooks without having to type the extension.
230 globbed_files = glob.glob(pattern)
235 globbed_files = glob.glob(pattern)
231 globbed_files.extend(glob.glob(pattern + '.ipynb'))
236 globbed_files.extend(glob.glob(pattern + '.ipynb'))
237 if not globbed_files:
238 self.log.warn("pattern %r matched no files", pattern)
232
239
233 for filename in globbed_files:
240 for filename in globbed_files:
234 if not filename in filenames:
241 if not filename in filenames:
@@ -266,13 +273,16 class NbConvertApp(BaseIPythonApplication):
266 conversion_success = 0
273 conversion_success = 0
267
274
268 if self.output_base != '' and len(self.notebooks) > 1:
275 if self.output_base != '' and len(self.notebooks) > 1:
269 print(dedent(
276 self.log.error(
270 """UsageError: --output flag or `NbConvertApp.output_base` config option
277 """UsageError: --output flag or `NbConvertApp.output_base` config option
271 cannot be used when converting multiple notebooks.
278 cannot be used when converting multiple notebooks.
272 """))
279 """)
273 self.exit(1)
280 self.exit(1)
281
282 exporter = exporter_map[self.export_format](config=self.config)
274
283
275 for notebook_filename in self.notebooks:
284 for notebook_filename in self.notebooks:
285 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
276
286
277 # Get a unique key for the notebook and set it in the resources object.
287 # Get a unique key for the notebook and set it in the resources object.
278 basename = os.path.basename(notebook_filename)
288 basename = os.path.basename(notebook_filename)
@@ -282,24 +292,14 class NbConvertApp(BaseIPythonApplication):
282 resources = {}
292 resources = {}
283 resources['unique_key'] = notebook_name
293 resources['unique_key'] = notebook_name
284 resources['output_files_dir'] = '%s_files' % notebook_name
294 resources['output_files_dir'] = '%s_files' % notebook_name
295 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
285
296
286 # Try to export
297 # Try to export
287 try:
298 try:
288 output, resources = export_by_name(self.export_format,
299 output, resources = exporter.from_filename(notebook_filename, resources=resources)
289 notebook_filename,
290 resources=resources,
291 config=self.config)
292 except ExporterNameError as e:
293 print("Error while converting '%s': '%s' exporter not found."
294 %(notebook_filename, self.export_format),
295 file=sys.stderr)
296 print("Known exporters are:",
297 "\n\t" + "\n\t".join(get_export_names()),
298 file=sys.stderr)
299 self.exit(1)
300 except ConversionException as e:
300 except ConversionException as e:
301 print("Error while converting '%s': %s" %(notebook_filename, e),
301 self.log.error("Error while converting '%s'", notebook_filename,
302 file=sys.stderr)
302 exc_info=True)
303 self.exit(1)
303 self.exit(1)
304 else:
304 else:
305 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
305 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
@@ -43,6 +43,7 class PDFPostProcessor(PostProcessorBase):
43 See files.py for more...
43 See files.py for more...
44 """
44 """
45 command = self.compiler.format(input)
45 command = self.compiler.format(input)
46 self.log.info("Building PDF: `%s`", command)
46 for index in range(self.iteration_count):
47 for index in range(self.iteration_count):
47 if self.verbose:
48 if self.verbose:
48 subprocess.Popen(command, shell=True)
49 subprocess.Popen(command, shell=True)
@@ -79,8 +79,9 class Transformer(NbConvertBase):
79 Additional resources used in the conversion process. Allows
79 Additional resources used in the conversion process. Allows
80 transformers to pass variables into the Jinja engine.
80 transformers to pass variables into the Jinja engine.
81 """
81 """
82 self.log.debug("Applying transform: %s", self.__class__.__name__)
82 try :
83 try :
83 for worksheet in nb.worksheets :
84 for worksheet in nb.worksheets:
84 for index, cell in enumerate(worksheet.cells):
85 for index, cell in enumerate(worksheet.cells):
85 worksheet.cells[index], resources = self.transform_cell(cell, resources, index)
86 worksheet.cells[index], resources = self.transform_cell(cell, resources, index)
86 return nb, resources
87 return nb, resources
@@ -12,13 +12,13
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from IPython.utils.traitlets import List
14 from IPython.utils.traitlets import List
15 from IPython.config.configurable import Configurable
15 from IPython.config.configurable import LoggingConfigurable
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Classes and functions
18 # Classes and functions
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 class NbConvertBase(Configurable):
21 class NbConvertBase(LoggingConfigurable):
22 """Global configurable class for shared config
22 """Global configurable class for shared config
23
23
24 Usefull for display data priority that might be use by many trasnformers
24 Usefull for display data priority that might be use by many trasnformers
@@ -45,7 +45,12 class FilesWriter(WriterBase):
45 super(FilesWriter, self).__init__(**kw)
45 super(FilesWriter, self).__init__(**kw)
46 self._build_directory_changed('build_directory', self.build_directory,
46 self._build_directory_changed('build_directory', self.build_directory,
47 self.build_directory)
47 self.build_directory)
48
48
49 def _makedir(self, path):
50 """Make a directory if it doesn't already exist"""
51 if not os.path.isdir(path):
52 self.log.info("Making directory %s", path)
53 os.makedirs(path)
49
54
50 def write(self, output, resources, notebook_name=None, **kw):
55 def write(self, output, resources, notebook_name=None, **kw):
51 """
56 """
@@ -67,10 +72,10 class FilesWriter(WriterBase):
67 # Determine where to write the file to
72 # Determine where to write the file to
68 dest = os.path.join(self.build_directory, filename)
73 dest = os.path.join(self.build_directory, filename)
69 path = os.path.dirname(dest)
74 path = os.path.dirname(dest)
70 if not os.path.isdir(path):
75 self._makedir(path)
71 os.makedirs(path)
72
76
73 # Write file
77 # Write file
78 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
74 with io.open(dest, 'wb') as f:
79 with io.open(dest, 'wb') as f:
75 f.write(data)
80 f.write(data)
76
81
@@ -84,11 +89,11 class FilesWriter(WriterBase):
84 # Make sure folder exists.
89 # Make sure folder exists.
85 dest = os.path.join(self.build_directory, filename)
90 dest = os.path.join(self.build_directory, filename)
86 path = os.path.dirname(dest)
91 path = os.path.dirname(dest)
87 if not os.path.isdir(path):
92 self._makedir(path)
88 os.makedirs(path)
89
93
90 # Copy if destination is different.
94 # Copy if destination is different.
91 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
95 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
96 self.log.info("Linking %s -> %s", matching_filename, dest)
92 link_or_copy(matching_filename, dest)
97 link_or_copy(matching_filename, dest)
93
98
94 # Determine where to write conversion results.
99 # Determine where to write conversion results.
@@ -97,6 +102,7 class FilesWriter(WriterBase):
97 dest = os.path.join(self.build_directory, dest)
102 dest = os.path.join(self.build_directory, dest)
98
103
99 # Write conversion results.
104 # Write conversion results.
105 self.log.info("Writing %i bytes to %s", len(output), dest)
100 with io.open(dest, 'w') as f:
106 with io.open(dest, 'w') as f:
101 f.write(output)
107 f.write(output)
102 return dest No newline at end of file
108 return dest
General Comments 0
You need to be logged in to leave comments. Login now