##// END OF EJS Templates
Make the output suffix optional
Jessica B. Hamrick -
Show More
@@ -1,324 +1,335 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """NbConvert is a utility for conversion of .ipynb files.
2 """NbConvert is a utility for conversion of .ipynb files.
3
3
4 Command-line interface for the NbConvert conversion utility.
4 Command-line interface for the NbConvert conversion utility.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 import logging
12 import logging
13 import sys
13 import sys
14 import os
14 import os
15 import glob
15 import glob
16
16
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.config import catch_config_error, Configurable
19 from IPython.config import catch_config_error, Configurable
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum, Bool,
22 )
22 )
23 from IPython.utils.importstring import import_item
23 from IPython.utils.importstring import import_item
24
24
25 from .exporters.export import get_export_names, exporter_map
25 from .exporters.export import get_export_names, exporter_map
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
27 from .utils.base import NbConvertBase
27 from .utils.base import NbConvertBase
28 from .utils.exceptions import ConversionException
28 from .utils.exceptions import ConversionException
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 #Classes and functions
31 #Classes and functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class DottedOrNone(DottedObjectName):
34 class DottedOrNone(DottedObjectName):
35 """
35 """
36 A string holding a valid dotted object name in Python, such as A.b3._c
36 A string holding a valid dotted object name in Python, such as A.b3._c
37 Also allows for None type."""
37 Also allows for None type."""
38
38
39 default_value = u''
39 default_value = u''
40
40
41 def validate(self, obj, value):
41 def validate(self, obj, value):
42 if value is not None and len(value) > 0:
42 if value is not None and len(value) > 0:
43 return super(DottedOrNone, self).validate(obj, value)
43 return super(DottedOrNone, self).validate(obj, value)
44 else:
44 else:
45 return value
45 return value
46
46
47 nbconvert_aliases = {}
47 nbconvert_aliases = {}
48 nbconvert_aliases.update(base_aliases)
48 nbconvert_aliases.update(base_aliases)
49 nbconvert_aliases.update({
49 nbconvert_aliases.update({
50 'to' : 'NbConvertApp.export_format',
50 'to' : 'NbConvertApp.export_format',
51 'template' : 'TemplateExporter.template_file',
51 'template' : 'TemplateExporter.template_file',
52 'writer' : 'NbConvertApp.writer_class',
52 'writer' : 'NbConvertApp.writer_class',
53 'post': 'NbConvertApp.postprocessor_class',
53 'post': 'NbConvertApp.postprocessor_class',
54 'output': 'NbConvertApp.output_base',
54 'output': 'NbConvertApp.output_base',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
56 'nbformat': 'NotebookExporter.nbformat_version',
56 'nbformat': 'NotebookExporter.nbformat_version',
57 })
57 })
58
58
59 nbconvert_flags = {}
59 nbconvert_flags = {}
60 nbconvert_flags.update(base_flags)
60 nbconvert_flags.update(base_flags)
61 nbconvert_flags.update({
61 nbconvert_flags.update({
62 'execute' : (
62 'execute' : (
63 {'ExecutePreprocessor' : {'enabled' : True}},
63 {'ExecutePreprocessor' : {'enabled' : True}},
64 "Execute the notebook prior to export."
64 "Execute the notebook prior to export."
65 ),
65 ),
66 'stdout' : (
66 'stdout' : (
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
68 "Write notebook output to stdout instead of files."
68 "Write notebook output to stdout instead of files."
69 ),
70 'no-output-suffix' : (
71 {'NbConvertApp' : {'use_output_suffix' : False}},
72 "Do not apply a suffix to filenames when converting to notebook format."
69 )
73 )
70 })
74 })
71
75
72
76
73 class NbConvertApp(BaseIPythonApplication):
77 class NbConvertApp(BaseIPythonApplication):
74 """Application used to convert from notebook file type (``*.ipynb``)"""
78 """Application used to convert from notebook file type (``*.ipynb``)"""
75
79
76 name = 'ipython-nbconvert'
80 name = 'ipython-nbconvert'
77 aliases = nbconvert_aliases
81 aliases = nbconvert_aliases
78 flags = nbconvert_flags
82 flags = nbconvert_flags
79
83
80 def _log_level_default(self):
84 def _log_level_default(self):
81 return logging.INFO
85 return logging.INFO
82
86
83 def _classes_default(self):
87 def _classes_default(self):
84 classes = [NbConvertBase, ProfileDir]
88 classes = [NbConvertBase, ProfileDir]
85 for pkg in (exporters, preprocessors, writers, postprocessors):
89 for pkg in (exporters, preprocessors, writers, postprocessors):
86 for name in dir(pkg):
90 for name in dir(pkg):
87 cls = getattr(pkg, name)
91 cls = getattr(pkg, name)
88 if isinstance(cls, type) and issubclass(cls, Configurable):
92 if isinstance(cls, type) and issubclass(cls, Configurable):
89 classes.append(cls)
93 classes.append(cls)
90
94
91 return classes
95 return classes
92
96
93 description = Unicode(
97 description = Unicode(
94 u"""This application is used to convert notebook files (*.ipynb)
98 u"""This application is used to convert notebook files (*.ipynb)
95 to various other formats.
99 to various other formats.
96
100
97 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
101 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
98
102
99 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
103 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
100 can only be used when converting one notebook at a time.
104 can only be used when converting one notebook at a time.
101 ''')
105 ''')
102
106
107 use_output_suffix = Bool(
108 True,
109 config=True,
110 help="""Whether to apply a suffix prior to the extension (only relevant
111 when converting to notebook format). The suffix is determined by
112 the exporter, and is usually '.nbconvert'.""")
113
103 examples = Unicode(u"""
114 examples = Unicode(u"""
104 The simplest way to use nbconvert is
115 The simplest way to use nbconvert is
105
116
106 > ipython nbconvert mynotebook.ipynb
117 > ipython nbconvert mynotebook.ipynb
107
118
108 which will convert mynotebook.ipynb to the default format (probably HTML).
119 which will convert mynotebook.ipynb to the default format (probably HTML).
109
120
110 You can specify the export format with `--to`.
121 You can specify the export format with `--to`.
111 Options include {0}
122 Options include {0}
112
123
113 > ipython nbconvert --to latex mynotebook.ipynb
124 > ipython nbconvert --to latex mynotebook.ipynb
114
125
115 Both HTML and LaTeX support multiple output templates. LaTeX includes
126 Both HTML and LaTeX support multiple output templates. LaTeX includes
116 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
127 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
117 can specify the flavor of the format used.
128 can specify the flavor of the format used.
118
129
119 > ipython nbconvert --to html --template basic mynotebook.ipynb
130 > ipython nbconvert --to html --template basic mynotebook.ipynb
120
131
121 You can also pipe the output to stdout, rather than a file
132 You can also pipe the output to stdout, rather than a file
122
133
123 > ipython nbconvert mynotebook.ipynb --stdout
134 > ipython nbconvert mynotebook.ipynb --stdout
124
135
125 PDF is generated via latex
136 PDF is generated via latex
126
137
127 > ipython nbconvert mynotebook.ipynb --to pdf
138 > ipython nbconvert mynotebook.ipynb --to pdf
128
139
129 You can get (and serve) a Reveal.js-powered slideshow
140 You can get (and serve) a Reveal.js-powered slideshow
130
141
131 > ipython nbconvert myslides.ipynb --to slides --post serve
142 > ipython nbconvert myslides.ipynb --to slides --post serve
132
143
133 Multiple notebooks can be given at the command line in a couple of
144 Multiple notebooks can be given at the command line in a couple of
134 different ways:
145 different ways:
135
146
136 > ipython nbconvert notebook*.ipynb
147 > ipython nbconvert notebook*.ipynb
137 > ipython nbconvert notebook1.ipynb notebook2.ipynb
148 > ipython nbconvert notebook1.ipynb notebook2.ipynb
138
149
139 or you can specify the notebooks list in a config file, containing::
150 or you can specify the notebooks list in a config file, containing::
140
151
141 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
152 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
142
153
143 > ipython nbconvert --config mycfg.py
154 > ipython nbconvert --config mycfg.py
144 """.format(get_export_names()))
155 """.format(get_export_names()))
145
156
146 # Writer specific variables
157 # Writer specific variables
147 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
158 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
148 help="""Instance of the writer class used to write the
159 help="""Instance of the writer class used to write the
149 results of the conversion.""")
160 results of the conversion.""")
150 writer_class = DottedObjectName('FilesWriter', config=True,
161 writer_class = DottedObjectName('FilesWriter', config=True,
151 help="""Writer class used to write the
162 help="""Writer class used to write the
152 results of the conversion""")
163 results of the conversion""")
153 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
164 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
154 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
165 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
155 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
166 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
156 writer_factory = Type()
167 writer_factory = Type()
157
168
158 def _writer_class_changed(self, name, old, new):
169 def _writer_class_changed(self, name, old, new):
159 if new.lower() in self.writer_aliases:
170 if new.lower() in self.writer_aliases:
160 new = self.writer_aliases[new.lower()]
171 new = self.writer_aliases[new.lower()]
161 self.writer_factory = import_item(new)
172 self.writer_factory = import_item(new)
162
173
163 # Post-processor specific variables
174 # Post-processor specific variables
164 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
175 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
165 help="""Instance of the PostProcessor class used to write the
176 help="""Instance of the PostProcessor class used to write the
166 results of the conversion.""")
177 results of the conversion.""")
167
178
168 postprocessor_class = DottedOrNone(config=True,
179 postprocessor_class = DottedOrNone(config=True,
169 help="""PostProcessor class used to write the
180 help="""PostProcessor class used to write the
170 results of the conversion""")
181 results of the conversion""")
171 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
182 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
172 postprocessor_factory = Type()
183 postprocessor_factory = Type()
173
184
174 def _postprocessor_class_changed(self, name, old, new):
185 def _postprocessor_class_changed(self, name, old, new):
175 if new.lower() in self.postprocessor_aliases:
186 if new.lower() in self.postprocessor_aliases:
176 new = self.postprocessor_aliases[new.lower()]
187 new = self.postprocessor_aliases[new.lower()]
177 if new:
188 if new:
178 self.postprocessor_factory = import_item(new)
189 self.postprocessor_factory = import_item(new)
179
190
180
191
181 # Other configurable variables
192 # Other configurable variables
182 export_format = CaselessStrEnum(get_export_names(),
193 export_format = CaselessStrEnum(get_export_names(),
183 default_value="html",
194 default_value="html",
184 config=True,
195 config=True,
185 help="""The export format to be used."""
196 help="""The export format to be used."""
186 )
197 )
187
198
188 notebooks = List([], config=True, help="""List of notebooks to convert.
199 notebooks = List([], config=True, help="""List of notebooks to convert.
189 Wildcards are supported.
200 Wildcards are supported.
190 Filenames passed positionally will be added to the list.
201 Filenames passed positionally will be added to the list.
191 """)
202 """)
192
203
193 @catch_config_error
204 @catch_config_error
194 def initialize(self, argv=None):
205 def initialize(self, argv=None):
195 self.init_syspath()
206 self.init_syspath()
196 super(NbConvertApp, self).initialize(argv)
207 super(NbConvertApp, self).initialize(argv)
197 self.init_notebooks()
208 self.init_notebooks()
198 self.init_writer()
209 self.init_writer()
199 self.init_postprocessor()
210 self.init_postprocessor()
200
211
201
212
202
213
203 def init_syspath(self):
214 def init_syspath(self):
204 """
215 """
205 Add the cwd to the sys.path ($PYTHONPATH)
216 Add the cwd to the sys.path ($PYTHONPATH)
206 """
217 """
207 sys.path.insert(0, os.getcwd())
218 sys.path.insert(0, os.getcwd())
208
219
209
220
210 def init_notebooks(self):
221 def init_notebooks(self):
211 """Construct the list of notebooks.
222 """Construct the list of notebooks.
212 If notebooks are passed on the command-line,
223 If notebooks are passed on the command-line,
213 they override notebooks specified in config files.
224 they override notebooks specified in config files.
214 Glob each notebook to replace notebook patterns with filenames.
225 Glob each notebook to replace notebook patterns with filenames.
215 """
226 """
216
227
217 # Specifying notebooks on the command-line overrides (rather than adds)
228 # Specifying notebooks on the command-line overrides (rather than adds)
218 # the notebook list
229 # the notebook list
219 if self.extra_args:
230 if self.extra_args:
220 patterns = self.extra_args
231 patterns = self.extra_args
221 else:
232 else:
222 patterns = self.notebooks
233 patterns = self.notebooks
223
234
224 # Use glob to replace all the notebook patterns with filenames.
235 # Use glob to replace all the notebook patterns with filenames.
225 filenames = []
236 filenames = []
226 for pattern in patterns:
237 for pattern in patterns:
227
238
228 # Use glob to find matching filenames. Allow the user to convert
239 # Use glob to find matching filenames. Allow the user to convert
229 # notebooks without having to type the extension.
240 # notebooks without having to type the extension.
230 globbed_files = glob.glob(pattern)
241 globbed_files = glob.glob(pattern)
231 globbed_files.extend(glob.glob(pattern + '.ipynb'))
242 globbed_files.extend(glob.glob(pattern + '.ipynb'))
232 if not globbed_files:
243 if not globbed_files:
233 self.log.warn("pattern %r matched no files", pattern)
244 self.log.warn("pattern %r matched no files", pattern)
234
245
235 for filename in globbed_files:
246 for filename in globbed_files:
236 if not filename in filenames:
247 if not filename in filenames:
237 filenames.append(filename)
248 filenames.append(filename)
238 self.notebooks = filenames
249 self.notebooks = filenames
239
250
240 def init_writer(self):
251 def init_writer(self):
241 """
252 """
242 Initialize the writer (which is stateless)
253 Initialize the writer (which is stateless)
243 """
254 """
244 self._writer_class_changed(None, self.writer_class, self.writer_class)
255 self._writer_class_changed(None, self.writer_class, self.writer_class)
245 self.writer = self.writer_factory(parent=self)
256 self.writer = self.writer_factory(parent=self)
246
257
247 def init_postprocessor(self):
258 def init_postprocessor(self):
248 """
259 """
249 Initialize the postprocessor (which is stateless)
260 Initialize the postprocessor (which is stateless)
250 """
261 """
251 self._postprocessor_class_changed(None, self.postprocessor_class,
262 self._postprocessor_class_changed(None, self.postprocessor_class,
252 self.postprocessor_class)
263 self.postprocessor_class)
253 if self.postprocessor_factory:
264 if self.postprocessor_factory:
254 self.postprocessor = self.postprocessor_factory(parent=self)
265 self.postprocessor = self.postprocessor_factory(parent=self)
255
266
256 def start(self):
267 def start(self):
257 """
268 """
258 Ran after initialization completed
269 Ran after initialization completed
259 """
270 """
260 super(NbConvertApp, self).start()
271 super(NbConvertApp, self).start()
261 self.convert_notebooks()
272 self.convert_notebooks()
262
273
263 def convert_notebooks(self):
274 def convert_notebooks(self):
264 """
275 """
265 Convert the notebooks in the self.notebook traitlet
276 Convert the notebooks in the self.notebook traitlet
266 """
277 """
267 # Export each notebook
278 # Export each notebook
268 conversion_success = 0
279 conversion_success = 0
269
280
270 if self.output_base != '' and len(self.notebooks) > 1:
281 if self.output_base != '' and len(self.notebooks) > 1:
271 self.log.error(
282 self.log.error(
272 """UsageError: --output flag or `NbConvertApp.output_base` config option
283 """UsageError: --output flag or `NbConvertApp.output_base` config option
273 cannot be used when converting multiple notebooks.
284 cannot be used when converting multiple notebooks.
274 """)
285 """)
275 self.exit(1)
286 self.exit(1)
276
287
277 exporter = exporter_map[self.export_format](config=self.config)
288 exporter = exporter_map[self.export_format](config=self.config)
278
289
279 for notebook_filename in self.notebooks:
290 for notebook_filename in self.notebooks:
280 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
291 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
281
292
282 # Get a unique key for the notebook and set it in the resources object.
293 # Get a unique key for the notebook and set it in the resources object.
283 basename = os.path.basename(notebook_filename)
294 basename = os.path.basename(notebook_filename)
284 notebook_name = basename[:basename.rfind('.')]
295 notebook_name = basename[:basename.rfind('.')]
285 if self.output_base:
296 if self.output_base:
286 # strip duplicate extension from output_base, to avoid Basname.ext.ext
297 # strip duplicate extension from output_base, to avoid Basname.ext.ext
287 if getattr(exporter, 'file_extension', False):
298 if getattr(exporter, 'file_extension', False):
288 base, ext = os.path.splitext(self.output_base)
299 base, ext = os.path.splitext(self.output_base)
289 if ext == exporter.file_extension:
300 if ext == exporter.file_extension:
290 self.output_base = base
301 self.output_base = base
291 notebook_name = self.output_base
302 notebook_name = self.output_base
292 resources = {}
303 resources = {}
293 resources['profile_dir'] = self.profile_dir.location
304 resources['profile_dir'] = self.profile_dir.location
294 resources['unique_key'] = notebook_name
305 resources['unique_key'] = notebook_name
295 resources['output_files_dir'] = '%s_files' % notebook_name
306 resources['output_files_dir'] = '%s_files' % notebook_name
296 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
307 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
297
308
298 # Try to export
309 # Try to export
299 try:
310 try:
300 output, resources = exporter.from_filename(notebook_filename, resources=resources)
311 output, resources = exporter.from_filename(notebook_filename, resources=resources)
301 except ConversionException as e:
312 except ConversionException as e:
302 self.log.error("Error while converting '%s'", notebook_filename,
313 self.log.error("Error while converting '%s'", notebook_filename,
303 exc_info=True)
314 exc_info=True)
304 self.exit(1)
315 self.exit(1)
305 else:
316 else:
306 if 'output_suffix' in resources and not self.output_base:
317 if self.use_output_suffix and 'output_suffix' in resources and not self.output_base:
307 notebook_name += resources['output_suffix']
318 notebook_name += resources['output_suffix']
308 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
319 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
309
320
310 #Post-process if post processor has been defined.
321 #Post-process if post processor has been defined.
311 if hasattr(self, 'postprocessor') and self.postprocessor:
322 if hasattr(self, 'postprocessor') and self.postprocessor:
312 self.postprocessor(write_results)
323 self.postprocessor(write_results)
313 conversion_success += 1
324 conversion_success += 1
314
325
315 # If nothing was converted successfully, help the user.
326 # If nothing was converted successfully, help the user.
316 if conversion_success == 0:
327 if conversion_success == 0:
317 self.print_help()
328 self.print_help()
318 sys.exit(-1)
329 sys.exit(-1)
319
330
320 #-----------------------------------------------------------------------------
331 #-----------------------------------------------------------------------------
321 # Main entry point
332 # Main entry point
322 #-----------------------------------------------------------------------------
333 #-----------------------------------------------------------------------------
323
334
324 launch_new_instance = NbConvertApp.launch_instance
335 launch_new_instance = NbConvertApp.launch_instance
General Comments 0
You need to be logged in to leave comments. Login now