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