##// END OF EJS Templates
Merge pull request #6067 from minrk/output_prefix...
Thomas Kluyver -
r17313:045d4856 merge
parent child Browse files
Show More
@@ -1,312 +1,317 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 21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
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 })
57 57
58 58 nbconvert_flags = {}
59 59 nbconvert_flags.update(base_flags)
60 60 nbconvert_flags.update({
61 61 'stdout' : (
62 62 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
63 63 "Write notebook output to stdout instead of files."
64 64 )
65 65 })
66 66
67 67
68 68 class NbConvertApp(BaseIPythonApplication):
69 69 """Application used to convert from notebook file type (``*.ipynb``)"""
70 70
71 71 name = 'ipython-nbconvert'
72 72 aliases = nbconvert_aliases
73 73 flags = nbconvert_flags
74 74
75 75 def _log_level_default(self):
76 76 return logging.INFO
77 77
78 78 def _classes_default(self):
79 79 classes = [NbConvertBase, ProfileDir]
80 80 for pkg in (exporters, preprocessors, writers, postprocessors):
81 81 for name in dir(pkg):
82 82 cls = getattr(pkg, name)
83 83 if isinstance(cls, type) and issubclass(cls, Configurable):
84 84 classes.append(cls)
85 85
86 86 return classes
87 87
88 88 description = Unicode(
89 89 u"""This application is used to convert notebook files (*.ipynb)
90 90 to various other formats.
91 91
92 92 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
93 93
94 94 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
95 95 can only be use when converting one notebook at a time.
96 96 ''')
97 97
98 98 examples = Unicode(u"""
99 99 The simplest way to use nbconvert is
100 100
101 101 > ipython nbconvert mynotebook.ipynb
102 102
103 103 which will convert mynotebook.ipynb to the default format (probably HTML).
104 104
105 105 You can specify the export format with `--to`.
106 106 Options include {0}
107 107
108 108 > ipython nbconvert --to latex mynotebook.ipynb
109 109
110 110 Both HTML and LaTeX support multiple output templates. LaTeX includes
111 111 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
112 112 can specify the flavor of the format used.
113 113
114 114 > ipython nbconvert --to html --template basic mynotebook.ipynb
115 115
116 116 You can also pipe the output to stdout, rather than a file
117 117
118 118 > ipython nbconvert mynotebook.ipynb --stdout
119 119
120 120 PDF is generated via latex
121 121
122 122 > ipython nbconvert mynotebook.ipynb --to pdf
123 123
124 124 You can get (and serve) a Reveal.js-powered slideshow
125 125
126 126 > ipython nbconvert myslides.ipynb --to slides --post serve
127 127
128 128 Multiple notebooks can be given at the command line in a couple of
129 129 different ways:
130 130
131 131 > ipython nbconvert notebook*.ipynb
132 132 > ipython nbconvert notebook1.ipynb notebook2.ipynb
133 133
134 134 or you can specify the notebooks list in a config file, containing::
135 135
136 136 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
137 137
138 138 > ipython nbconvert --config mycfg.py
139 139 """.format(get_export_names()))
140 140
141 141 # Writer specific variables
142 142 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
143 143 help="""Instance of the writer class used to write the
144 144 results of the conversion.""")
145 145 writer_class = DottedObjectName('FilesWriter', config=True,
146 146 help="""Writer class used to write the
147 147 results of the conversion""")
148 148 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
149 149 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
150 150 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
151 151 writer_factory = Type()
152 152
153 153 def _writer_class_changed(self, name, old, new):
154 154 if new.lower() in self.writer_aliases:
155 155 new = self.writer_aliases[new.lower()]
156 156 self.writer_factory = import_item(new)
157 157
158 158 # Post-processor specific variables
159 159 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
160 160 help="""Instance of the PostProcessor class used to write the
161 161 results of the conversion.""")
162 162
163 163 postprocessor_class = DottedOrNone(config=True,
164 164 help="""PostProcessor class used to write the
165 165 results of the conversion""")
166 166 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
167 167 postprocessor_factory = Type()
168 168
169 169 def _postprocessor_class_changed(self, name, old, new):
170 170 if new.lower() in self.postprocessor_aliases:
171 171 new = self.postprocessor_aliases[new.lower()]
172 172 if new:
173 173 self.postprocessor_factory = import_item(new)
174 174
175 175
176 176 # Other configurable variables
177 177 export_format = CaselessStrEnum(get_export_names(),
178 178 default_value="html",
179 179 config=True,
180 180 help="""The export format to be used."""
181 181 )
182 182
183 183 notebooks = List([], config=True, help="""List of notebooks to convert.
184 184 Wildcards are supported.
185 185 Filenames passed positionally will be added to the list.
186 186 """)
187 187
188 188 @catch_config_error
189 189 def initialize(self, argv=None):
190 190 self.init_syspath()
191 191 super(NbConvertApp, self).initialize(argv)
192 192 self.init_notebooks()
193 193 self.init_writer()
194 194 self.init_postprocessor()
195 195
196 196
197 197
198 198 def init_syspath(self):
199 199 """
200 200 Add the cwd to the sys.path ($PYTHONPATH)
201 201 """
202 202 sys.path.insert(0, os.getcwd())
203 203
204 204
205 205 def init_notebooks(self):
206 206 """Construct the list of notebooks.
207 207 If notebooks are passed on the command-line,
208 208 they override notebooks specified in config files.
209 209 Glob each notebook to replace notebook patterns with filenames.
210 210 """
211 211
212 212 # Specifying notebooks on the command-line overrides (rather than adds)
213 213 # the notebook list
214 214 if self.extra_args:
215 215 patterns = self.extra_args
216 216 else:
217 217 patterns = self.notebooks
218 218
219 219 # Use glob to replace all the notebook patterns with filenames.
220 220 filenames = []
221 221 for pattern in patterns:
222 222
223 223 # Use glob to find matching filenames. Allow the user to convert
224 224 # notebooks without having to type the extension.
225 225 globbed_files = glob.glob(pattern)
226 226 globbed_files.extend(glob.glob(pattern + '.ipynb'))
227 227 if not globbed_files:
228 228 self.log.warn("pattern %r matched no files", pattern)
229 229
230 230 for filename in globbed_files:
231 231 if not filename in filenames:
232 232 filenames.append(filename)
233 233 self.notebooks = filenames
234 234
235 235 def init_writer(self):
236 236 """
237 237 Initialize the writer (which is stateless)
238 238 """
239 239 self._writer_class_changed(None, self.writer_class, self.writer_class)
240 240 self.writer = self.writer_factory(parent=self)
241 241
242 242 def init_postprocessor(self):
243 243 """
244 244 Initialize the postprocessor (which is stateless)
245 245 """
246 246 self._postprocessor_class_changed(None, self.postprocessor_class,
247 247 self.postprocessor_class)
248 248 if self.postprocessor_factory:
249 249 self.postprocessor = self.postprocessor_factory(parent=self)
250 250
251 251 def start(self):
252 252 """
253 253 Ran after initialization completed
254 254 """
255 255 super(NbConvertApp, self).start()
256 256 self.convert_notebooks()
257 257
258 258 def convert_notebooks(self):
259 259 """
260 260 Convert the notebooks in the self.notebook traitlet
261 261 """
262 262 # Export each notebook
263 263 conversion_success = 0
264 264
265 265 if self.output_base != '' and len(self.notebooks) > 1:
266 266 self.log.error(
267 267 """UsageError: --output flag or `NbConvertApp.output_base` config option
268 268 cannot be used when converting multiple notebooks.
269 269 """)
270 270 self.exit(1)
271 271
272 272 exporter = exporter_map[self.export_format](config=self.config)
273 273
274 274 for notebook_filename in self.notebooks:
275 275 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
276 276
277 277 # Get a unique key for the notebook and set it in the resources object.
278 278 basename = os.path.basename(notebook_filename)
279 279 notebook_name = basename[:basename.rfind('.')]
280 280 if self.output_base:
281 # strip duplicate extension from output_base, to avoid Basname.ext.ext
282 if getattr(exporter, 'file_extension', False):
283 base, ext = os.path.splitext(self.output_base)
284 if ext == '.' + exporter.file_extension:
285 self.output_base = base
281 286 notebook_name = self.output_base
282 287 resources = {}
283 288 resources['profile_dir'] = self.profile_dir.location
284 289 resources['unique_key'] = notebook_name
285 290 resources['output_files_dir'] = '%s_files' % notebook_name
286 291 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
287 292
288 293 # Try to export
289 294 try:
290 295 output, resources = exporter.from_filename(notebook_filename, resources=resources)
291 296 except ConversionException as e:
292 297 self.log.error("Error while converting '%s'", notebook_filename,
293 298 exc_info=True)
294 299 self.exit(1)
295 300 else:
296 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
301 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
297 302
298 303 #Post-process if post processor has been defined.
299 304 if hasattr(self, 'postprocessor') and self.postprocessor:
300 self.postprocessor(write_resultes)
305 self.postprocessor(write_results)
301 306 conversion_success += 1
302 307
303 308 # If nothing was converted successfully, help the user.
304 309 if conversion_success == 0:
305 310 self.print_help()
306 311 sys.exit(-1)
307 312
308 313 #-----------------------------------------------------------------------------
309 314 # Main entry point
310 315 #-----------------------------------------------------------------------------
311 316
312 317 launch_new_instance = NbConvertApp.launch_instance
@@ -1,202 +1,212 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Test NbConvertApp"""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import glob
9 9 import sys
10 10
11 11 from .base import TestsBase
12 12 from ..postprocessors import PostProcessorBase
13 13
14 14 import IPython.testing.tools as tt
15 15 from IPython.testing import decorators as dec
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Classes and functions
19 19 #-----------------------------------------------------------------------------
20 20
21 21 class DummyPost(PostProcessorBase):
22 22 def postprocess(self, filename):
23 23 print("Dummy:%s" % filename)
24 24
25 25 class TestNbConvertApp(TestsBase):
26 26 """Collection of NbConvertApp tests"""
27 27
28 28
29 29 def test_notebook_help(self):
30 30 """Will help show if no notebooks are specified?"""
31 31 with self.create_temp_cwd():
32 32 out, err = self.call('nbconvert --log-level 0', ignore_return_code=True)
33 33 self.assertIn("see '--help-all'", out)
34 34
35 35 def test_help_output(self):
36 36 """ipython nbconvert --help-all works"""
37 37 tt.help_all_output_test('nbconvert')
38 38
39 39 def test_glob(self):
40 40 """
41 41 Do search patterns work for notebook names?
42 42 """
43 43 with self.create_temp_cwd(['notebook*.ipynb']):
44 44 self.call('nbconvert --to python *.ipynb --log-level 0')
45 45 assert os.path.isfile('notebook1.py')
46 46 assert os.path.isfile('notebook2.py')
47 47
48 48
49 49 def test_glob_subdir(self):
50 50 """
51 51 Do search patterns work for subdirectory notebook names?
52 52 """
53 53 with self.create_temp_cwd():
54 54 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
55 55 self.call('nbconvert --to python --log-level 0 ' +
56 56 os.path.join('subdir', '*.ipynb'))
57 57 assert os.path.isfile('notebook1.py')
58 58 assert os.path.isfile('notebook2.py')
59 59
60 60
61 61 def test_explicit(self):
62 62 """
63 63 Do explicit notebook names work?
64 64 """
65 65 with self.create_temp_cwd(['notebook*.ipynb']):
66 66 self.call('nbconvert --log-level 0 --to python notebook2')
67 67 assert not os.path.isfile('notebook1.py')
68 68 assert os.path.isfile('notebook2.py')
69 69
70 70
71 71 @dec.onlyif_cmds_exist('pdflatex')
72 72 @dec.onlyif_cmds_exist('pandoc')
73 73 def test_filename_spaces(self):
74 74 """
75 75 Generate PDFs with graphics if notebooks have spaces in the name?
76 76 """
77 77 with self.create_temp_cwd(['notebook2.ipynb']):
78 78 os.rename('notebook2.ipynb', 'notebook with spaces.ipynb')
79 79 self.call('nbconvert --log-level 0 --to pdf'
80 80 ' "notebook with spaces"'
81 81 ' --PDFExporter.latex_count=1'
82 82 ' --PDFExporter.verbose=True'
83 83 )
84 84 assert os.path.isfile('notebook with spaces.pdf')
85 85
86 86 def test_post_processor(self):
87 87 """Do post processors work?"""
88 88 with self.create_temp_cwd(['notebook1.ipynb']):
89 89 out, err = self.call('nbconvert --log-level 0 --to python notebook1 '
90 90 '--post IPython.nbconvert.tests.test_nbconvertapp.DummyPost')
91 91 self.assertIn('Dummy:notebook1.py', out)
92 92
93 93 @dec.onlyif_cmds_exist('pandoc')
94 94 def test_spurious_cr(self):
95 95 """Check for extra CR characters"""
96 96 with self.create_temp_cwd(['notebook2.ipynb']):
97 97 self.call('nbconvert --log-level 0 --to latex notebook2')
98 98 assert os.path.isfile('notebook2.tex')
99 99 with open('notebook2.tex') as f:
100 100 tex = f.read()
101 101 self.call('nbconvert --log-level 0 --to html notebook2')
102 102 assert os.path.isfile('notebook2.html')
103 103 with open('notebook2.html') as f:
104 104 html = f.read()
105 105 self.assertEqual(tex.count('\r'), tex.count('\r\n'))
106 106 self.assertEqual(html.count('\r'), html.count('\r\n'))
107 107
108 108 @dec.onlyif_cmds_exist('pandoc')
109 109 def test_png_base64_html_ok(self):
110 110 """Is embedded png data well formed in HTML?"""
111 111 with self.create_temp_cwd(['notebook2.ipynb']):
112 112 self.call('nbconvert --log-level 0 --to HTML '
113 113 'notebook2.ipynb --template full')
114 114 assert os.path.isfile('notebook2.html')
115 115 with open('notebook2.html') as f:
116 116 assert "data:image/png;base64,b'" not in f.read()
117 117
118 118 @dec.onlyif_cmds_exist('pandoc')
119 119 def test_template(self):
120 120 """
121 121 Do export templates work?
122 122 """
123 123 with self.create_temp_cwd(['notebook2.ipynb']):
124 124 self.call('nbconvert --log-level 0 --to slides '
125 125 'notebook2.ipynb')
126 126 assert os.path.isfile('notebook2.slides.html')
127 127 with open('notebook2.slides.html') as f:
128 128 assert '/reveal.css' in f.read()
129 129
130 def test_output_ext(self):
131 """test --output=outputfile[.ext]"""
132 with self.create_temp_cwd(['notebook1.ipynb']):
133 self.call('nbconvert --log-level 0 --to python '
134 'notebook1.ipynb --output nb.py')
135 assert os.path.exists('nb.py')
136
137 self.call('nbconvert --log-level 0 --to python '
138 'notebook1.ipynb --output nb2')
139 assert os.path.exists('nb2.py')
130 140
131 141 def test_glob_explicit(self):
132 142 """
133 143 Can a search pattern be used along with matching explicit notebook names?
134 144 """
135 145 with self.create_temp_cwd(['notebook*.ipynb']):
136 146 self.call('nbconvert --log-level 0 --to python '
137 147 '*.ipynb notebook1.ipynb notebook2.ipynb')
138 148 assert os.path.isfile('notebook1.py')
139 149 assert os.path.isfile('notebook2.py')
140 150
141 151
142 152 def test_explicit_glob(self):
143 153 """
144 154 Can explicit notebook names be used and then a matching search pattern?
145 155 """
146 156 with self.create_temp_cwd(['notebook*.ipynb']):
147 157 self.call('nbconvert --log-level 0 --to=python '
148 158 'notebook1.ipynb notebook2.ipynb *.ipynb')
149 159 assert os.path.isfile('notebook1.py')
150 160 assert os.path.isfile('notebook2.py')
151 161
152 162
153 163 def test_default_config(self):
154 164 """
155 165 Does the default config work?
156 166 """
157 167 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py']):
158 168 self.call('nbconvert --log-level 0')
159 169 assert os.path.isfile('notebook1.py')
160 170 assert not os.path.isfile('notebook2.py')
161 171
162 172
163 173 def test_override_config(self):
164 174 """
165 175 Can the default config be overriden?
166 176 """
167 177 with self.create_temp_cwd(['notebook*.ipynb',
168 178 'ipython_nbconvert_config.py',
169 179 'override.py']):
170 180 self.call('nbconvert --log-level 0 --config="override.py"')
171 181 assert not os.path.isfile('notebook1.py')
172 182 assert os.path.isfile('notebook2.py')
173 183
174 184 def test_accents_in_filename(self):
175 185 """
176 186 Can notebook names include accents?
177 187 """
178 188 with self.create_temp_cwd():
179 189 self.create_empty_notebook(u'nb1_análisis.ipynb')
180 190 self.call('nbconvert --log-level 0 --to python nb1_*')
181 191 assert os.path.isfile(u'nb1_análisis.py')
182 192
183 193 @dec.onlyif_cmds_exist('pdflatex', 'pandoc')
184 194 def test_filename_accent_pdf(self):
185 195 """
186 196 Generate PDFs if notebooks have an accent in their name?
187 197 """
188 198 with self.create_temp_cwd():
189 199 self.create_empty_notebook(u'nb1_análisis.ipynb')
190 200 self.call('nbconvert --log-level 0 --to pdf "nb1_*"'
191 201 ' --PDFExporter.latex_count=1'
192 202 ' --PDFExporter.verbose=True')
193 203 assert os.path.isfile(u'nb1_análisis.pdf')
194 204
195 205 def test_cwd_plugin(self):
196 206 """
197 207 Verify that an extension in the cwd can be imported.
198 208 """
199 209 with self.create_temp_cwd(['hello.py']):
200 210 self.create_empty_notebook(u'empty.ipynb')
201 211 self.call('nbconvert empty --to html --NbConvertApp.writer_class=\'hello.HelloWriter\'')
202 212 assert os.path.isfile(u'hello.txt')
General Comments 0
You need to be logged in to leave comments. Login now