##// END OF EJS Templates
Merge pull request #4048 from minrk/finish-notes...
Min RK -
r12517:829790a6 merge
parent child Browse files
Show More
@@ -1,16 +1,14 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 python:
3 python:
4 - 2.7
4 - 2.7
5 - 3.3
5 - 3.3
6 before_install:
6 before_install:
7 - pip install jinja2
8 - easy_install -q pyzmq
7 - easy_install -q pyzmq
8 - pip install jinja2 sphinx pygments tornado
9 - sudo apt-get install pandoc
9 - sudo apt-get install pandoc
10 - pip install pygments
11 - pip install sphinx
12 install:
10 install:
13 - python setup.py install -q
11 - python setup.py install -q
14 script:
12 script:
15 - if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then iptest -w /tmp; fi
13 - if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then iptest -w /tmp; fi
16 - if [[ $TRAVIS_PYTHON_VERSION == '3.'* ]]; then iptest3 -w /tmp; fi
14 - if [[ $TRAVIS_PYTHON_VERSION == '3.'* ]]; then iptest3 -w /tmp; fi
@@ -1,324 +1,323 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) 2013, the IPython Development Team.
7 #Copyright (c) 2013, the IPython Development Team.
8 #
8 #
9 #Distributed under the terms of the Modified BSD License.
9 #Distributed under the terms of the Modified BSD License.
10 #
10 #
11 #The full license is in the file COPYING.txt, distributed with this software.
11 #The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 #Imports
15 #Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Stdlib imports
18 # Stdlib imports
19 from __future__ import print_function
19 from __future__ import print_function
20
20
21 import logging
21 import logging
22 import sys
22 import sys
23 import os
23 import os
24 import glob
24 import glob
25
25
26 # From IPython
26 # From IPython
27 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
27 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
28 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
29 from IPython.config import catch_config_error, Configurable
29 from IPython.config import catch_config_error, Configurable
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
31 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
32 )
32 )
33 from IPython.utils.importstring import import_item
33 from IPython.utils.importstring import import_item
34 from IPython.utils.text import dedent
34 from IPython.utils.text import dedent
35
35
36 from .exporters.export import get_export_names, exporter_map
36 from .exporters.export import get_export_names, exporter_map
37 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
37 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
38 from .utils.base import NbConvertBase
38 from .utils.base import NbConvertBase
39 from .utils.exceptions import ConversionException
39 from .utils.exceptions import ConversionException
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 #Classes and functions
42 #Classes and functions
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 class DottedOrNone(DottedObjectName):
45 class DottedOrNone(DottedObjectName):
46 """
46 """
47 A string holding a valid dotted object name in Python, such as A.b3._c
47 A string holding a valid dotted object name in Python, such as A.b3._c
48 Also allows for None type."""
48 Also allows for None type."""
49
49
50 default_value = u''
50 default_value = u''
51
51
52 def validate(self, obj, value):
52 def validate(self, obj, value):
53 if value is not None and len(value) > 0:
53 if value is not None and len(value) > 0:
54 return super(DottedOrNone, self).validate(obj, value)
54 return super(DottedOrNone, self).validate(obj, value)
55 else:
55 else:
56 return value
56 return value
57
57
58 nbconvert_aliases = {}
58 nbconvert_aliases = {}
59 nbconvert_aliases.update(base_aliases)
59 nbconvert_aliases.update(base_aliases)
60 nbconvert_aliases.update({
60 nbconvert_aliases.update({
61 'to' : 'NbConvertApp.export_format',
61 'to' : 'NbConvertApp.export_format',
62 'template' : 'Exporter.template_file',
62 'template' : 'Exporter.template_file',
63 'writer' : 'NbConvertApp.writer_class',
63 'writer' : 'NbConvertApp.writer_class',
64 'post': 'NbConvertApp.postprocessor_class',
64 'post': 'NbConvertApp.postprocessor_class',
65 'output': 'NbConvertApp.output_base',
65 'output': 'NbConvertApp.output_base',
66 'offline-slides': 'RevealHelpPreprocessor.url_prefix',
66 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
67 'slide-notes': 'RevealHelpPreprocessor.speaker_notes'
68 })
67 })
69
68
70 nbconvert_flags = {}
69 nbconvert_flags = {}
71 nbconvert_flags.update(base_flags)
70 nbconvert_flags.update(base_flags)
72 nbconvert_flags.update({
71 nbconvert_flags.update({
73 'stdout' : (
72 'stdout' : (
74 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
73 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
75 "Write notebook output to stdout instead of files."
74 "Write notebook output to stdout instead of files."
76 )
75 )
77 })
76 })
78
77
79
78
80 class NbConvertApp(BaseIPythonApplication):
79 class NbConvertApp(BaseIPythonApplication):
81 """Application used to convert to and from notebook file type (*.ipynb)"""
80 """Application used to convert to and from notebook file type (*.ipynb)"""
82
81
83 name = 'ipython-nbconvert'
82 name = 'ipython-nbconvert'
84 aliases = nbconvert_aliases
83 aliases = nbconvert_aliases
85 flags = nbconvert_flags
84 flags = nbconvert_flags
86
85
87 def _log_level_default(self):
86 def _log_level_default(self):
88 return logging.INFO
87 return logging.INFO
89
88
90 def _classes_default(self):
89 def _classes_default(self):
91 classes = [NbConvertBase, ProfileDir]
90 classes = [NbConvertBase, ProfileDir]
92 for pkg in (exporters, preprocessors, writers):
91 for pkg in (exporters, preprocessors, writers):
93 for name in dir(pkg):
92 for name in dir(pkg):
94 cls = getattr(pkg, name)
93 cls = getattr(pkg, name)
95 if isinstance(cls, type) and issubclass(cls, Configurable):
94 if isinstance(cls, type) and issubclass(cls, Configurable):
96 classes.append(cls)
95 classes.append(cls)
97
96
98 return classes
97 return classes
99
98
100 description = Unicode(
99 description = Unicode(
101 u"""This application is used to convert notebook files (*.ipynb)
100 u"""This application is used to convert notebook files (*.ipynb)
102 to various other formats.
101 to various other formats.
103
102
104 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
103 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
105
104
106 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
105 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
107 can only be use when converting one notebook at a time.
106 can only be use when converting one notebook at a time.
108 ''')
107 ''')
109
108
110 examples = Unicode(u"""
109 examples = Unicode(u"""
111 The simplest way to use nbconvert is
110 The simplest way to use nbconvert is
112
111
113 > ipython nbconvert mynotebook.ipynb
112 > ipython nbconvert mynotebook.ipynb
114
113
115 which will convert mynotebook.ipynb to the default format (probably HTML).
114 which will convert mynotebook.ipynb to the default format (probably HTML).
116
115
117 You can specify the export format with `--to`.
116 You can specify the export format with `--to`.
118 Options include {0}
117 Options include {0}
119
118
120 > ipython nbconvert --to latex mynotebook.ipnynb
119 > ipython nbconvert --to latex mynotebook.ipnynb
121
120
122 Both HTML and LaTeX support multiple output templates. LaTeX includes
121 Both HTML and LaTeX support multiple output templates. LaTeX includes
123 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
122 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
124 can specify the flavor of the format used.
123 can specify the flavor of the format used.
125
124
126 > ipython nbconvert --to html --template basic mynotebook.ipynb
125 > ipython nbconvert --to html --template basic mynotebook.ipynb
127
126
128 You can also pipe the output to stdout, rather than a file
127 You can also pipe the output to stdout, rather than a file
129
128
130 > ipython nbconvert mynotebook.ipynb --stdout
129 > ipython nbconvert mynotebook.ipynb --stdout
131
130
132 A post-processor can be used to compile a PDF
131 A post-processor can be used to compile a PDF
133
132
134 > ipython nbconvert mynotebook.ipynb --to latex --post PDF
133 > ipython nbconvert mynotebook.ipynb --to latex --post PDF
135
134
136 You can get (and serve) a Reveal.js-powered slideshow
135 You can get (and serve) a Reveal.js-powered slideshow
137
136
138 > ipython nbconvert myslides.ipynb --to slides --post serve
137 > ipython nbconvert myslides.ipynb --to slides --post serve
139
138
140 Multiple notebooks can be given at the command line in a couple of
139 Multiple notebooks can be given at the command line in a couple of
141 different ways:
140 different ways:
142
141
143 > ipython nbconvert notebook*.ipynb
142 > ipython nbconvert notebook*.ipynb
144 > ipython nbconvert notebook1.ipynb notebook2.ipynb
143 > ipython nbconvert notebook1.ipynb notebook2.ipynb
145
144
146 or you can specify the notebooks list in a config file, containing::
145 or you can specify the notebooks list in a config file, containing::
147
146
148 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
147 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
149
148
150 > ipython nbconvert --config mycfg.py
149 > ipython nbconvert --config mycfg.py
151 """.format(get_export_names()))
150 """.format(get_export_names()))
152
151
153 # Writer specific variables
152 # Writer specific variables
154 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
153 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
155 help="""Instance of the writer class used to write the
154 help="""Instance of the writer class used to write the
156 results of the conversion.""")
155 results of the conversion.""")
157 writer_class = DottedObjectName('FilesWriter', config=True,
156 writer_class = DottedObjectName('FilesWriter', config=True,
158 help="""Writer class used to write the
157 help="""Writer class used to write the
159 results of the conversion""")
158 results of the conversion""")
160 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
159 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
161 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
160 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
162 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
161 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
163 writer_factory = Type()
162 writer_factory = Type()
164
163
165 def _writer_class_changed(self, name, old, new):
164 def _writer_class_changed(self, name, old, new):
166 if new.lower() in self.writer_aliases:
165 if new.lower() in self.writer_aliases:
167 new = self.writer_aliases[new.lower()]
166 new = self.writer_aliases[new.lower()]
168 self.writer_factory = import_item(new)
167 self.writer_factory = import_item(new)
169
168
170 # Post-processor specific variables
169 # Post-processor specific variables
171 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
170 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
172 help="""Instance of the PostProcessor class used to write the
171 help="""Instance of the PostProcessor class used to write the
173 results of the conversion.""")
172 results of the conversion.""")
174
173
175 postprocessor_class = DottedOrNone(config=True,
174 postprocessor_class = DottedOrNone(config=True,
176 help="""PostProcessor class used to write the
175 help="""PostProcessor class used to write the
177 results of the conversion""")
176 results of the conversion""")
178 postprocessor_aliases = {'pdf': 'IPython.nbconvert.postprocessors.pdf.PDFPostProcessor',
177 postprocessor_aliases = {'pdf': 'IPython.nbconvert.postprocessors.pdf.PDFPostProcessor',
179 'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
178 'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
180 postprocessor_factory = Type()
179 postprocessor_factory = Type()
181
180
182 def _postprocessor_class_changed(self, name, old, new):
181 def _postprocessor_class_changed(self, name, old, new):
183 if new.lower() in self.postprocessor_aliases:
182 if new.lower() in self.postprocessor_aliases:
184 new = self.postprocessor_aliases[new.lower()]
183 new = self.postprocessor_aliases[new.lower()]
185 if new:
184 if new:
186 self.postprocessor_factory = import_item(new)
185 self.postprocessor_factory = import_item(new)
187
186
188
187
189 # Other configurable variables
188 # Other configurable variables
190 export_format = CaselessStrEnum(get_export_names(),
189 export_format = CaselessStrEnum(get_export_names(),
191 default_value="html",
190 default_value="html",
192 config=True,
191 config=True,
193 help="""The export format to be used."""
192 help="""The export format to be used."""
194 )
193 )
195
194
196 notebooks = List([], config=True, help="""List of notebooks to convert.
195 notebooks = List([], config=True, help="""List of notebooks to convert.
197 Wildcards are supported.
196 Wildcards are supported.
198 Filenames passed positionally will be added to the list.
197 Filenames passed positionally will be added to the list.
199 """)
198 """)
200
199
201 @catch_config_error
200 @catch_config_error
202 def initialize(self, argv=None):
201 def initialize(self, argv=None):
203 super(NbConvertApp, self).initialize(argv)
202 super(NbConvertApp, self).initialize(argv)
204 self.init_syspath()
203 self.init_syspath()
205 self.init_notebooks()
204 self.init_notebooks()
206 self.init_writer()
205 self.init_writer()
207 self.init_postprocessor()
206 self.init_postprocessor()
208
207
209
208
210
209
211 def init_syspath(self):
210 def init_syspath(self):
212 """
211 """
213 Add the cwd to the sys.path ($PYTHONPATH)
212 Add the cwd to the sys.path ($PYTHONPATH)
214 """
213 """
215 sys.path.insert(0, os.getcwd())
214 sys.path.insert(0, os.getcwd())
216
215
217
216
218 def init_notebooks(self):
217 def init_notebooks(self):
219 """Construct the list of notebooks.
218 """Construct the list of notebooks.
220 If notebooks are passed on the command-line,
219 If notebooks are passed on the command-line,
221 they override notebooks specified in config files.
220 they override notebooks specified in config files.
222 Glob each notebook to replace notebook patterns with filenames.
221 Glob each notebook to replace notebook patterns with filenames.
223 """
222 """
224
223
225 # Specifying notebooks on the command-line overrides (rather than adds)
224 # Specifying notebooks on the command-line overrides (rather than adds)
226 # the notebook list
225 # the notebook list
227 if self.extra_args:
226 if self.extra_args:
228 patterns = self.extra_args
227 patterns = self.extra_args
229 else:
228 else:
230 patterns = self.notebooks
229 patterns = self.notebooks
231
230
232 # Use glob to replace all the notebook patterns with filenames.
231 # Use glob to replace all the notebook patterns with filenames.
233 filenames = []
232 filenames = []
234 for pattern in patterns:
233 for pattern in patterns:
235
234
236 # Use glob to find matching filenames. Allow the user to convert
235 # Use glob to find matching filenames. Allow the user to convert
237 # notebooks without having to type the extension.
236 # notebooks without having to type the extension.
238 globbed_files = glob.glob(pattern)
237 globbed_files = glob.glob(pattern)
239 globbed_files.extend(glob.glob(pattern + '.ipynb'))
238 globbed_files.extend(glob.glob(pattern + '.ipynb'))
240 if not globbed_files:
239 if not globbed_files:
241 self.log.warn("pattern %r matched no files", pattern)
240 self.log.warn("pattern %r matched no files", pattern)
242
241
243 for filename in globbed_files:
242 for filename in globbed_files:
244 if not filename in filenames:
243 if not filename in filenames:
245 filenames.append(filename)
244 filenames.append(filename)
246 self.notebooks = filenames
245 self.notebooks = filenames
247
246
248 def init_writer(self):
247 def init_writer(self):
249 """
248 """
250 Initialize the writer (which is stateless)
249 Initialize the writer (which is stateless)
251 """
250 """
252 self._writer_class_changed(None, self.writer_class, self.writer_class)
251 self._writer_class_changed(None, self.writer_class, self.writer_class)
253 self.writer = self.writer_factory(parent=self)
252 self.writer = self.writer_factory(parent=self)
254
253
255 def init_postprocessor(self):
254 def init_postprocessor(self):
256 """
255 """
257 Initialize the postprocessor (which is stateless)
256 Initialize the postprocessor (which is stateless)
258 """
257 """
259 self._postprocessor_class_changed(None, self.postprocessor_class,
258 self._postprocessor_class_changed(None, self.postprocessor_class,
260 self.postprocessor_class)
259 self.postprocessor_class)
261 if self.postprocessor_factory:
260 if self.postprocessor_factory:
262 self.postprocessor = self.postprocessor_factory(parent=self)
261 self.postprocessor = self.postprocessor_factory(parent=self)
263
262
264 def start(self):
263 def start(self):
265 """
264 """
266 Ran after initialization completed
265 Ran after initialization completed
267 """
266 """
268 super(NbConvertApp, self).start()
267 super(NbConvertApp, self).start()
269 self.convert_notebooks()
268 self.convert_notebooks()
270
269
271 def convert_notebooks(self):
270 def convert_notebooks(self):
272 """
271 """
273 Convert the notebooks in the self.notebook traitlet
272 Convert the notebooks in the self.notebook traitlet
274 """
273 """
275 # Export each notebook
274 # Export each notebook
276 conversion_success = 0
275 conversion_success = 0
277
276
278 if self.output_base != '' and len(self.notebooks) > 1:
277 if self.output_base != '' and len(self.notebooks) > 1:
279 self.log.error(
278 self.log.error(
280 """UsageError: --output flag or `NbConvertApp.output_base` config option
279 """UsageError: --output flag or `NbConvertApp.output_base` config option
281 cannot be used when converting multiple notebooks.
280 cannot be used when converting multiple notebooks.
282 """)
281 """)
283 self.exit(1)
282 self.exit(1)
284
283
285 exporter = exporter_map[self.export_format](config=self.config)
284 exporter = exporter_map[self.export_format](config=self.config)
286
285
287 for notebook_filename in self.notebooks:
286 for notebook_filename in self.notebooks:
288 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
287 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
289
288
290 # Get a unique key for the notebook and set it in the resources object.
289 # Get a unique key for the notebook and set it in the resources object.
291 basename = os.path.basename(notebook_filename)
290 basename = os.path.basename(notebook_filename)
292 notebook_name = basename[:basename.rfind('.')]
291 notebook_name = basename[:basename.rfind('.')]
293 if self.output_base:
292 if self.output_base:
294 notebook_name = self.output_base
293 notebook_name = self.output_base
295 resources = {}
294 resources = {}
296 resources['unique_key'] = notebook_name
295 resources['unique_key'] = notebook_name
297 resources['output_files_dir'] = '%s_files' % notebook_name
296 resources['output_files_dir'] = '%s_files' % notebook_name
298 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
297 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
299
298
300 # Try to export
299 # Try to export
301 try:
300 try:
302 output, resources = exporter.from_filename(notebook_filename, resources=resources)
301 output, resources = exporter.from_filename(notebook_filename, resources=resources)
303 except ConversionException as e:
302 except ConversionException as e:
304 self.log.error("Error while converting '%s'", notebook_filename,
303 self.log.error("Error while converting '%s'", notebook_filename,
305 exc_info=True)
304 exc_info=True)
306 self.exit(1)
305 self.exit(1)
307 else:
306 else:
308 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
307 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
309
308
310 #Post-process if post processor has been defined.
309 #Post-process if post processor has been defined.
311 if hasattr(self, 'postprocessor') and self.postprocessor:
310 if hasattr(self, 'postprocessor') and self.postprocessor:
312 self.postprocessor(write_resultes)
311 self.postprocessor(write_resultes)
313 conversion_success += 1
312 conversion_success += 1
314
313
315 # If nothing was converted successfully, help the user.
314 # If nothing was converted successfully, help the user.
316 if conversion_success == 0:
315 if conversion_success == 0:
317 self.print_help()
316 self.print_help()
318 sys.exit(-1)
317 sys.exit(-1)
319
318
320 #-----------------------------------------------------------------------------
319 #-----------------------------------------------------------------------------
321 # Main entry point
320 # Main entry point
322 #-----------------------------------------------------------------------------
321 #-----------------------------------------------------------------------------
323
322
324 launch_new_instance = NbConvertApp.launch_instance
323 launch_new_instance = NbConvertApp.launch_instance
@@ -1,3 +1,8 b''
1 from .base import PostProcessorBase
1 from .base import PostProcessorBase
2 from .pdf import PDFPostProcessor
2 from .pdf import PDFPostProcessor
3 from .serve import ServePostProcessor
3
4 # protect against unavailable tornado
5 try:
6 from .serve import ServePostProcessor
7 except ImportError:
8 pass
@@ -1,55 +1,107 b''
1 """
1 """PostProcessor for serving reveal.js HTML slideshows."""
2 Contains postprocessor for serving nbconvert output.
3 """
4 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
3 #Copyright (c) 2013, the IPython Development Team.
6 #
4 #
7 #Distributed under the terms of the Modified BSD License.
5 #Distributed under the terms of the Modified BSD License.
8 #
6 #
9 #The full license is in the file COPYING.txt, distributed with this software.
7 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
11
9
12 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
13 # Imports
11 # Imports
14 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
15
13
16 import os
14 import os
17 import webbrowser
15 import webbrowser
18
16
19 from BaseHTTPServer import HTTPServer
17 from tornado import web, ioloop, httpserver
20 from SimpleHTTPServer import SimpleHTTPRequestHandler
18 from tornado.httpclient import AsyncHTTPClient
21
19
22 from IPython.utils.traitlets import Bool
20 from IPython.utils.traitlets import Bool, Unicode, Int
23
21
24 from .base import PostProcessorBase
22 from .base import PostProcessorBase
25
23
26 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
27 # Classes
25 # Classes
28 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
28 class ProxyHandler(web.RequestHandler):
29 """handler the proxies requests from a local prefix to a CDN"""
30 @web.asynchronous
31 def get(self, prefix, url):
32 """proxy a request to a CDN"""
33 proxy_url = "/".join([self.settings['cdn'], url])
34 client = self.settings['client']
35 client.fetch(proxy_url, callback=self.finish_get)
36
37 def finish_get(self, response):
38 """finish the request"""
39 # copy potentially relevant headers
40 for header in ["Content-Type", "Cache-Control", "Date", "Last-Modified", "Expires"]:
41 if header in response.headers:
42 self.set_header(header, response.headers[header])
43 self.finish(response.body)
44
29 class ServePostProcessor(PostProcessorBase):
45 class ServePostProcessor(PostProcessorBase):
30 """Post processor designed to serve files"""
46 """Post processor designed to serve files
47
48 Proxies reveal.js requests to a CDN if no local reveal.js is present
49 """
31
50
32
51
33 open_in_browser = Bool(True, config=True,
52 open_in_browser = Bool(True, config=True,
34 help="""Set to False to deactivate
53 help="""Should the browser be opened automatically?"""
35 the opening of the browser""")
54 )
55 reveal_cdn = Unicode("https://cdn.jsdelivr.net/reveal.js/2.4.0", config=True,
56 help="""URL for reveal.js CDN."""
57 )
58 reveal_prefix = Unicode("reveal.js", config=True, help="URL prefix for reveal.js")
59 ip = Unicode("127.0.0.1", config=True, help="The IP address to listen on.")
60 port = Int(8000, config=True, help="port for the server to listen on.")
36
61
37 def postprocess(self, input):
62 def postprocess(self, input):
38 """
63 """Serve the build directory with a webserver."""
39 Simple implementation to serve the build directory.
64 dirname, filename = os.path.split(input)
40 """
65 handlers = [
41
66 (r"/(.+)", web.StaticFileHandler, {'path' : dirname}),
67 (r"/", web.RedirectHandler, {"url": "/%s" % filename})
68 ]
69
70 if ('://' in self.reveal_prefix or self.reveal_prefix.startswith("//")):
71 # reveal specifically from CDN, nothing to do
72 pass
73 elif os.path.isdir(os.path.join(dirname, self.reveal_prefix)):
74 # reveal prefix exists
75 self.log.info("Serving local %s", self.reveal_prefix)
76 else:
77 self.log.info("Redirecting %s requests to %s", self.reveal_prefix, self.reveal_cdn)
78 handlers.insert(0, (r"/(%s)/(.*)" % self.reveal_prefix, ProxyHandler))
79
80 app = web.Application(handlers,
81 cdn=self.reveal_cdn,
82 client=AsyncHTTPClient(),
83 )
84 # hook up tornado logging to our logger
85 from tornado import log
86 log.app_log = self.log
87
88 http_server = httpserver.HTTPServer(app)
89 http_server.listen(self.port, address=self.ip)
90 url = "http://%s:%i/%s" % (self.ip, self.port, filename)
91 print("Serving your slides at %s" % url)
92 print("Use Control-C to stop this server")
93 if self.open_in_browser:
94 webbrowser.open(url, new=2)
42 try:
95 try:
43 dirname, filename = os.path.split(input)
96 ioloop.IOLoop.instance().start()
44 if dirname:
45 os.chdir(dirname)
46 httpd = HTTPServer(('127.0.0.1', 8000), SimpleHTTPRequestHandler)
47 sa = httpd.socket.getsockname()
48 url = "http://" + sa[0] + ":" + str(sa[1]) + "/" + filename
49 if self.open_in_browser:
50 webbrowser.open(url, new=2)
51 print("Serving your slides on " + url)
52 print("Use Control-C to stop this server.")
53 httpd.serve_forever()
54 except KeyboardInterrupt:
97 except KeyboardInterrupt:
55 print("The server is shut down.")
98 print("\nInterrupted")
99
100 def main(path):
101 """allow running this module to serve the slides"""
102 server = ServePostProcessor()
103 server(path)
104
105 if __name__ == '__main__':
106 import sys
107 main(sys.argv[1])
@@ -1,94 +1,67 b''
1 """Module that pre-processes the notebook for export via Reveal.
1 """Module that pre-processes the notebook for export via Reveal.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16 import urllib2
16 import urllib2
17
17
18 from .base import Preprocessor
18 from .base import Preprocessor
19 from IPython.utils.traitlets import Unicode, Bool
19 from IPython.utils.traitlets import Unicode, Bool
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Classes and functions
22 # Classes and functions
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class RevealHelpPreprocessor(Preprocessor):
25 class RevealHelpPreprocessor(Preprocessor):
26
26
27 url_prefix = Unicode('//cdn.jsdelivr.net/reveal.js/2.4.0',
27 url_prefix = Unicode('reveal.js', config=True,
28 config=True,
28 help="""The URL prefix for reveal.js.
29 help="""If you want to use a local reveal.js library,
29 This can be a a relative URL for a local copy of reveal.js,
30 use 'url_prefix':'reveal.js' in your config object.""")
30 or point to a CDN.
31
31
32 speaker_notes = Bool(False,
32 For speaker notes to work, a local reveal.js prefix must be used.
33 config=True,
33 """
34 help="""If you want to use the speaker notes
34 )
35 set this to True.""")
36
35
37 def preprocess(self, nb, resources):
36 def preprocess(self, nb, resources):
38 """
37 """
39 Called once to 'preprocess' contents of the notebook.
38 Called once to 'preprocess' contents of the notebook.
40
39
41 Parameters
40 Parameters
42 ----------
41 ----------
43 nb : NotebookNode
42 nb : NotebookNode
44 Notebook being converted
43 Notebook being converted
45 resources : dictionary
44 resources : dictionary
46 Additional resources used in the conversion process. Allows
45 Additional resources used in the conversion process. Allows
47 preprocessors to pass variables into the Jinja engine.
46 preprocessors to pass variables into the Jinja engine.
48 """
47 """
49
48
50 for worksheet in nb.worksheets :
49 for worksheet in nb.worksheets :
51 for index, cell in enumerate(worksheet.cells):
50 for index, cell in enumerate(worksheet.cells):
52
51
53 #Make sure the cell has slideshow metadata.
52 #Make sure the cell has slideshow metadata.
54 cell.metadata.align_type = cell.get('metadata', {}).get('slideshow', {}).get('align_type', 'Left')
53 cell.metadata.align_type = cell.get('metadata', {}).get('slideshow', {}).get('align_type', 'Left')
55 cell.metadata.slide_type = cell.get('metadata', {}).get('slideshow', {}).get('slide_type', '-')
54 cell.metadata.slide_type = cell.get('metadata', {}).get('slideshow', {}).get('slide_type', '-')
56
55
57 #Get the slide type. If type is start of subslide or slide,
56 #Get the slide type. If type is start of subslide or slide,
58 #end the last subslide/slide.
57 #end the last subslide/slide.
59 if cell.metadata.slide_type in ['slide']:
58 if cell.metadata.slide_type in ['slide']:
60 worksheet.cells[index - 1].metadata.slide_helper = 'slide_end'
59 worksheet.cells[index - 1].metadata.slide_helper = 'slide_end'
61 if cell.metadata.slide_type in ['subslide']:
60 if cell.metadata.slide_type in ['subslide']:
62 worksheet.cells[index - 1].metadata.slide_helper = 'subslide_end'
61 worksheet.cells[index - 1].metadata.slide_helper = 'subslide_end'
63
62
64
63
65 if not isinstance(resources['reveal'], dict):
64 if not isinstance(resources['reveal'], dict):
66 resources['reveal'] = {}
65 resources['reveal'] = {}
67 resources['reveal']['url_prefix'] = self.url_prefix
66 resources['reveal']['url_prefix'] = self.url_prefix
68 resources['reveal']['notes_prefix'] = self.url_prefix
69
70 cdn = 'http://cdn.jsdelivr.net/reveal.js/2.4.0'
71 local = 'local'
72 html_path = 'plugin/notes/notes.html'
73 js_path = 'plugin/notes/notes.js'
74
75 html_infile = os.path.join(cdn, html_path)
76 js_infile = os.path.join(cdn, js_path)
77 html_outfile = os.path.join(local, html_path)
78 js_outfile = os.path.join(local, js_path)
79
80 if self.speaker_notes:
81 if 'outputs' not in resources:
82 resources['outputs'] = {}
83 resources['outputs'][html_outfile] = self.notes_helper(html_infile)
84 resources['outputs'][js_outfile] = self.notes_helper(js_infile)
85 resources['reveal']['notes_prefix'] = local
86
87 return nb, resources
67 return nb, resources
88
89 def notes_helper(self, infile):
90 """Helper function to get the content from an url."""
91
92 content = urllib2.urlopen(infile).read()
93
94 return content
@@ -1,185 +1,185 b''
1 {%- extends 'reveal_internals/slides.tpl' -%}
1 {%- extends 'reveal_internals/slides.tpl' -%}
2
2
3
3
4 {% block header %}
4 {% block header %}
5 <!DOCTYPE html>
5 <!DOCTYPE html>
6 <html>
6 <html>
7 <head>
7 <head>
8
8
9 <meta charset="utf-8" />
9 <meta charset="utf-8" />
10 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <meta http-equiv="X-UA-Compatible" content="chrome=1">
11
11
12 <meta name="apple-mobile-web-app-capable" content="yes" />
12 <meta name="apple-mobile-web-app-capable" content="yes" />
13 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
13 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
14
14
15 <!-- General and theme style sheets -->
15 <!-- General and theme style sheets -->
16 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/reveal.css">
16 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/reveal.css">
17 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/theme/simple.css" id="theme">
17 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/theme/simple.css" id="theme">
18
18
19 <!-- For syntax highlighting -->
19 <!-- For syntax highlighting -->
20 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/lib/css/zenburn.css">
20 <link rel="stylesheet" href="{{resources.reveal.url_prefix}}/lib/css/zenburn.css">
21
21
22 <!-- If the query includes 'print-pdf', use the PDF print sheet -->
22 <!-- If the query includes 'print-pdf', use the PDF print sheet -->
23 <script>
23 <script>
24 document.write( '<link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/print/' + ( window.location.search.match( /print-pdf/gi ) ? 'pdf' : 'paper' ) + '.css" type="text/css" media="print">' );
24 document.write( '<link rel="stylesheet" href="{{resources.reveal.url_prefix}}/css/print/' + ( window.location.search.match( /print-pdf/gi ) ? 'pdf' : 'paper' ) + '.css" type="text/css" media="print">' );
25 </script>
25 </script>
26
26
27 <!--[if lt IE 9]>
27 <!--[if lt IE 9]>
28 <script src="{{resources.reveal.url_prefix}}/lib/js/html5shiv.js"></script>
28 <script src="{{resources.reveal.url_prefix}}/lib/js/html5shiv.js"></script>
29 <![endif]-->
29 <![endif]-->
30
30
31 {% for css in resources.inlining.css -%}
31 {% for css in resources.inlining.css -%}
32 <style type="text/css">
32 <style type="text/css">
33 {{ css }}
33 {{ css }}
34 </style>
34 </style>
35 {% endfor %}
35 {% endfor %}
36
36
37 <style type="text/css">
37 <style type="text/css">
38 /* Overrides of notebook CSS for static HTML export */
38 /* Overrides of notebook CSS for static HTML export */
39 .reveal {
39 .reveal {
40 font-size: 20px;
40 font-size: 20px;
41 overflow-y: auto;
41 overflow-y: auto;
42 overflow-x: hidden;
42 overflow-x: hidden;
43 }
43 }
44 .reveal pre {
44 .reveal pre {
45 width: 95%;
45 width: 95%;
46 padding: 0.4em;
46 padding: 0.4em;
47 margin: 0px;
47 margin: 0px;
48 font-family: monospace, sans-serif;
48 font-family: monospace, sans-serif;
49 font-size: 80%;
49 font-size: 80%;
50 box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
50 box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
51 }
51 }
52 .reveal section img {
52 .reveal section img {
53 border: 0px solid black;
53 border: 0px solid black;
54 box-shadow: 0 0 10px rgba(0, 0, 0, 0);
54 box-shadow: 0 0 10px rgba(0, 0, 0, 0);
55 }
55 }
56 .reveal .slides {
56 .reveal .slides {
57 text-align: left;
57 text-align: left;
58 }
58 }
59 .reveal.fade {
59 .reveal.fade {
60 opacity: 1;
60 opacity: 1;
61 }
61 }
62 div.input_area {
62 div.input_area {
63 padding: 0.06em;
63 padding: 0.06em;
64 }
64 }
65 div.code_cell {
65 div.code_cell {
66 background-color: transparent;
66 background-color: transparent;
67 }
67 }
68 div.prompt {
68 div.prompt {
69 width: 11ex;
69 width: 11ex;
70 padding: 0.4em;
70 padding: 0.4em;
71 margin: 0px;
71 margin: 0px;
72 font-family: monospace, sans-serif;
72 font-family: monospace, sans-serif;
73 font-size: 80%;
73 font-size: 80%;
74 text-align: right;
74 text-align: right;
75 }
75 }
76 div.output_area pre {
76 div.output_area pre {
77 font-family: monospace, sans-serif;
77 font-family: monospace, sans-serif;
78 font-size: 80%;
78 font-size: 80%;
79 }
79 }
80 div.output_prompt {
80 div.output_prompt {
81 /* 5px right shift to account for margin in parent container */
81 /* 5px right shift to account for margin in parent container */
82 margin: 5px 5px 0 0;
82 margin: 5px 5px 0 0;
83 }
83 }
84 .rendered_html p {
84 .rendered_html p {
85 text-align: inherit;
85 text-align: inherit;
86 }
86 }
87 </style>
87 </style>
88
88
89 <!-- Custom stylesheet, it must be in the same directory as the html file -->
89 <!-- Custom stylesheet, it must be in the same directory as the html file -->
90 <link rel="stylesheet" href="custom.css">
90 <link rel="stylesheet" href="custom.css">
91
91
92 </head>
92 </head>
93 {% endblock header%}
93 {% endblock header%}
94
94
95
95
96 {% block body %}
96 {% block body %}
97 <body>
97 <body>
98 <div class="reveal">
98 <div class="reveal">
99 <div class="slides">
99 <div class="slides">
100 {{ super() }}
100 {{ super() }}
101 </div>
101 </div>
102 </div>
102 </div>
103
103
104 <!--
104 <!--
105 Uncomment the following block and the addthis_widget.js (see below inside dependencies)
105 Uncomment the following block and the addthis_widget.js (see below inside dependencies)
106 to get enable social buttons.
106 to get enable social buttons.
107 -->
107 -->
108
108
109 <!--
109 <!--
110 <div class="addthis_toolbox addthis_floating_style addthis_32x32_style" style="left:20px;top:20px;">
110 <div class="addthis_toolbox addthis_floating_style addthis_32x32_style" style="left:20px;top:20px;">
111 <a class="addthis_button_twitter"></a>
111 <a class="addthis_button_twitter"></a>
112 <a class="addthis_button_google_plusone_share"></a>
112 <a class="addthis_button_google_plusone_share"></a>
113 <a class="addthis_button_linkedin"></a>
113 <a class="addthis_button_linkedin"></a>
114 <a class="addthis_button_facebook"></a>
114 <a class="addthis_button_facebook"></a>
115 <a class="addthis_button_more"></a>
115 <a class="addthis_button_more"></a>
116 </div>
116 </div>
117 -->
117 -->
118
118
119 <script src="{{resources.reveal.url_prefix}}/lib/js/head.min.js"></script>
119 <script src="{{resources.reveal.url_prefix}}/lib/js/head.min.js"></script>
120
120
121 <script src="{{resources.reveal.url_prefix}}/js/reveal.js"></script>
121 <script src="{{resources.reveal.url_prefix}}/js/reveal.js"></script>
122
122
123 <script>
123 <script>
124
124
125 // Full list of configuration options available here: https://github.com/hakimel/reveal.js#configuration
125 // Full list of configuration options available here: https://github.com/hakimel/reveal.js#configuration
126 Reveal.initialize({
126 Reveal.initialize({
127 controls: true,
127 controls: true,
128 progress: true,
128 progress: true,
129 history: true,
129 history: true,
130
130
131 theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
131 theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
132 transition: Reveal.getQueryHash().transition || 'linear', // default/cube/page/concave/zoom/linear/none
132 transition: Reveal.getQueryHash().transition || 'linear', // default/cube/page/concave/zoom/linear/none
133
133
134 // Optional libraries used to extend on reveal.js
134 // Optional libraries used to extend on reveal.js
135 dependencies: [
135 dependencies: [
136 { src: "{{resources.reveal.url_prefix}}/lib/js/classList.js", condition: function() { return !document.body.classList; } },
136 { src: "{{resources.reveal.url_prefix}}/lib/js/classList.js", condition: function() { return !document.body.classList; } },
137 { src: "{{resources.reveal.url_prefix}}/plugin/highlight/highlight.js", async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
137 { src: "{{resources.reveal.url_prefix}}/plugin/highlight/highlight.js", async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
138 { src: "{{resources.reveal.notes_prefix}}/plugin/notes/notes.js", async: true, condition: function() { return !!document.body.classList; } }
138 { src: "{{resources.reveal.url_prefix}}/plugin/notes/notes.js", async: true, condition: function() { return !!document.body.classList; } }
139 // { src: 'http://s7.addthis.com/js/300/addthis_widget.js', async: true},
139 // { src: 'http://s7.addthis.com/js/300/addthis_widget.js', async: true},
140 ]
140 ]
141 });
141 });
142 </script>
142 </script>
143
143
144 <!-- MathJax configuration -->
144 <!-- MathJax configuration -->
145 <script type="text/x-mathjax-config">
145 <script type="text/x-mathjax-config">
146 MathJax.Hub.Config({
146 MathJax.Hub.Config({
147 tex2jax: {
147 tex2jax: {
148 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
148 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
149 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
149 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
150 },
150 },
151 displayAlign: 'left', // Change this to 'center' to center equations.
151 displayAlign: 'left', // Change this to 'center' to center equations.
152 "HTML-CSS": {
152 "HTML-CSS": {
153 styles: {'.MathJax_Display': {"margin": 0}}
153 styles: {'.MathJax_Display': {"margin": 0}}
154 }
154 }
155 });
155 });
156 </script>
156 </script>
157 <!-- End of mathjax configuration -->
157 <!-- End of mathjax configuration -->
158
158
159 <script>
159 <script>
160 // We wait for the onload function to load MathJax after the page is completely loaded.
160 // We wait for the onload function to load MathJax after the page is completely loaded.
161 // MathJax is loaded 1 unit of time after the page is ready.
161 // MathJax is loaded 1 unit of time after the page is ready.
162 // This hack prevent problems when you load multiple js files (i.e. social button from addthis).
162 // This hack prevent problems when you load multiple js files (i.e. social button from addthis).
163 //
163 //
164 window.onload = function () {
164 window.onload = function () {
165 setTimeout(function () {
165 setTimeout(function () {
166 var script = document.createElement("script");
166 var script = document.createElement("script");
167 script.type = "text/javascript";
167 script.type = "text/javascript";
168 script.src = "https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML";
168 script.src = "https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML";
169 document.getElementsByTagName("head")[0].appendChild(script);
169 document.getElementsByTagName("head")[0].appendChild(script);
170 },1)
170 },1)
171 }
171 }
172 </script>
172 </script>
173
173
174 <script>
174 <script>
175 Reveal.addEventListener( 'slidechanged', function( event ) {
175 Reveal.addEventListener( 'slidechanged', function( event ) {
176 MathJax.Hub.Rerender(event.currentSlide);
176 MathJax.Hub.Rerender(event.currentSlide);
177 });
177 });
178 </script>
178 </script>
179
179
180 </body>
180 </body>
181 {% endblock body %}
181 {% endblock body %}
182
182
183 {% block footer %}
183 {% block footer %}
184 </html>
184 </html>
185 {% endblock footer %} No newline at end of file
185 {% endblock footer %}
@@ -1,654 +1,656 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 # Stdlib
29 # Stdlib
30 import glob
30 import glob
31 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39 import multiprocessing.pool
39 import multiprocessing.pool
40
40
41 # Now, proceed to import nose itself
41 # Now, proceed to import nose itself
42 import nose.plugins.builtin
42 import nose.plugins.builtin
43 from nose.plugins.xunit import Xunit
43 from nose.plugins.xunit import Xunit
44 from nose import SkipTest
44 from nose import SkipTest
45 from nose.core import TestProgram
45 from nose.core import TestProgram
46
46
47 # Our own imports
47 # Our own imports
48 from IPython.utils import py3compat
48 from IPython.utils import py3compat
49 from IPython.utils.importstring import import_item
49 from IPython.utils.importstring import import_item
50 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
50 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
51 from IPython.utils.process import pycmd2argv
51 from IPython.utils.process import pycmd2argv
52 from IPython.utils.sysinfo import sys_info
52 from IPython.utils.sysinfo import sys_info
53 from IPython.utils.tempdir import TemporaryDirectory
53 from IPython.utils.tempdir import TemporaryDirectory
54 from IPython.utils.warn import warn
54 from IPython.utils.warn import warn
55
55
56 from IPython.testing import globalipapp
56 from IPython.testing import globalipapp
57 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 from IPython.testing.plugin.ipdoctest import IPythonDoctest
58 from IPython.external.decorators import KnownFailure, knownfailureif
58 from IPython.external.decorators import KnownFailure, knownfailureif
59
59
60 pjoin = path.join
60 pjoin = path.join
61
61
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Globals
64 # Globals
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Warnings control
69 # Warnings control
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72 # Twisted generates annoying warnings with Python 2.6, as will do other code
72 # Twisted generates annoying warnings with Python 2.6, as will do other code
73 # that imports 'sets' as of today
73 # that imports 'sets' as of today
74 warnings.filterwarnings('ignore', 'the sets module is deprecated',
74 warnings.filterwarnings('ignore', 'the sets module is deprecated',
75 DeprecationWarning )
75 DeprecationWarning )
76
76
77 # This one also comes from Twisted
77 # This one also comes from Twisted
78 warnings.filterwarnings('ignore', 'the sha module is deprecated',
78 warnings.filterwarnings('ignore', 'the sha module is deprecated',
79 DeprecationWarning)
79 DeprecationWarning)
80
80
81 # Wx on Fedora11 spits these out
81 # Wx on Fedora11 spits these out
82 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
82 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
83 UserWarning)
83 UserWarning)
84
84
85 # ------------------------------------------------------------------------------
85 # ------------------------------------------------------------------------------
86 # Monkeypatch Xunit to count known failures as skipped.
86 # Monkeypatch Xunit to count known failures as skipped.
87 # ------------------------------------------------------------------------------
87 # ------------------------------------------------------------------------------
88 def monkeypatch_xunit():
88 def monkeypatch_xunit():
89 try:
89 try:
90 knownfailureif(True)(lambda: None)()
90 knownfailureif(True)(lambda: None)()
91 except Exception as e:
91 except Exception as e:
92 KnownFailureTest = type(e)
92 KnownFailureTest = type(e)
93
93
94 def addError(self, test, err, capt=None):
94 def addError(self, test, err, capt=None):
95 if issubclass(err[0], KnownFailureTest):
95 if issubclass(err[0], KnownFailureTest):
96 err = (SkipTest,) + err[1:]
96 err = (SkipTest,) + err[1:]
97 return self.orig_addError(test, err, capt)
97 return self.orig_addError(test, err, capt)
98
98
99 Xunit.orig_addError = Xunit.addError
99 Xunit.orig_addError = Xunit.addError
100 Xunit.addError = addError
100 Xunit.addError = addError
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # Logic for skipping doctests
103 # Logic for skipping doctests
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105 def extract_version(mod):
105 def extract_version(mod):
106 return mod.__version__
106 return mod.__version__
107
107
108 def test_for(item, min_version=None, callback=extract_version):
108 def test_for(item, min_version=None, callback=extract_version):
109 """Test to see if item is importable, and optionally check against a minimum
109 """Test to see if item is importable, and optionally check against a minimum
110 version.
110 version.
111
111
112 If min_version is given, the default behavior is to check against the
112 If min_version is given, the default behavior is to check against the
113 `__version__` attribute of the item, but specifying `callback` allows you to
113 `__version__` attribute of the item, but specifying `callback` allows you to
114 extract the value you are interested in. e.g::
114 extract the value you are interested in. e.g::
115
115
116 In [1]: import sys
116 In [1]: import sys
117
117
118 In [2]: from IPython.testing.iptest import test_for
118 In [2]: from IPython.testing.iptest import test_for
119
119
120 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
120 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
121 Out[3]: True
121 Out[3]: True
122
122
123 """
123 """
124 try:
124 try:
125 check = import_item(item)
125 check = import_item(item)
126 except (ImportError, RuntimeError):
126 except (ImportError, RuntimeError):
127 # GTK reports Runtime error if it can't be initialized even if it's
127 # GTK reports Runtime error if it can't be initialized even if it's
128 # importable.
128 # importable.
129 return False
129 return False
130 else:
130 else:
131 if min_version:
131 if min_version:
132 if callback:
132 if callback:
133 # extra processing step to get version to compare
133 # extra processing step to get version to compare
134 check = callback(check)
134 check = callback(check)
135
135
136 return check >= min_version
136 return check >= min_version
137 else:
137 else:
138 return True
138 return True
139
139
140 # Global dict where we can store information on what we have and what we don't
140 # Global dict where we can store information on what we have and what we don't
141 # have available at test run time
141 # have available at test run time
142 have = {}
142 have = {}
143
143
144 have['curses'] = test_for('_curses')
144 have['curses'] = test_for('_curses')
145 have['matplotlib'] = test_for('matplotlib')
145 have['matplotlib'] = test_for('matplotlib')
146 have['numpy'] = test_for('numpy')
146 have['numpy'] = test_for('numpy')
147 have['pexpect'] = test_for('IPython.external.pexpect')
147 have['pexpect'] = test_for('IPython.external.pexpect')
148 have['pymongo'] = test_for('pymongo')
148 have['pymongo'] = test_for('pymongo')
149 have['pygments'] = test_for('pygments')
149 have['pygments'] = test_for('pygments')
150 have['qt'] = test_for('IPython.external.qt')
150 have['qt'] = test_for('IPython.external.qt')
151 have['rpy2'] = test_for('rpy2')
151 have['rpy2'] = test_for('rpy2')
152 have['sqlite3'] = test_for('sqlite3')
152 have['sqlite3'] = test_for('sqlite3')
153 have['cython'] = test_for('Cython')
153 have['cython'] = test_for('Cython')
154 have['oct2py'] = test_for('oct2py')
154 have['oct2py'] = test_for('oct2py')
155 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
155 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
156 have['jinja2'] = test_for('jinja2')
156 have['jinja2'] = test_for('jinja2')
157 have['wx'] = test_for('wx')
157 have['wx'] = test_for('wx')
158 have['wx.aui'] = test_for('wx.aui')
158 have['wx.aui'] = test_for('wx.aui')
159 have['azure'] = test_for('azure')
159 have['azure'] = test_for('azure')
160 have['sphinx'] = test_for('sphinx')
160 have['sphinx'] = test_for('sphinx')
161
161
162 min_zmq = (2,1,11)
162 min_zmq = (2,1,11)
163
163
164 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
164 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
165
165
166 #-----------------------------------------------------------------------------
166 #-----------------------------------------------------------------------------
167 # Functions and classes
167 # Functions and classes
168 #-----------------------------------------------------------------------------
168 #-----------------------------------------------------------------------------
169
169
170 def report():
170 def report():
171 """Return a string with a summary report of test-related variables."""
171 """Return a string with a summary report of test-related variables."""
172
172
173 out = [ sys_info(), '\n']
173 out = [ sys_info(), '\n']
174
174
175 avail = []
175 avail = []
176 not_avail = []
176 not_avail = []
177
177
178 for k, is_avail in have.items():
178 for k, is_avail in have.items():
179 if is_avail:
179 if is_avail:
180 avail.append(k)
180 avail.append(k)
181 else:
181 else:
182 not_avail.append(k)
182 not_avail.append(k)
183
183
184 if avail:
184 if avail:
185 out.append('\nTools and libraries available at test time:\n')
185 out.append('\nTools and libraries available at test time:\n')
186 avail.sort()
186 avail.sort()
187 out.append(' ' + ' '.join(avail)+'\n')
187 out.append(' ' + ' '.join(avail)+'\n')
188
188
189 if not_avail:
189 if not_avail:
190 out.append('\nTools and libraries NOT available at test time:\n')
190 out.append('\nTools and libraries NOT available at test time:\n')
191 not_avail.sort()
191 not_avail.sort()
192 out.append(' ' + ' '.join(not_avail)+'\n')
192 out.append(' ' + ' '.join(not_avail)+'\n')
193
193
194 return ''.join(out)
194 return ''.join(out)
195
195
196
196
197 def make_exclude():
197 def make_exclude():
198 """Make patterns of modules and packages to exclude from testing.
198 """Make patterns of modules and packages to exclude from testing.
199
199
200 For the IPythonDoctest plugin, we need to exclude certain patterns that
200 For the IPythonDoctest plugin, we need to exclude certain patterns that
201 cause testing problems. We should strive to minimize the number of
201 cause testing problems. We should strive to minimize the number of
202 skipped modules, since this means untested code.
202 skipped modules, since this means untested code.
203
203
204 These modules and packages will NOT get scanned by nose at all for tests.
204 These modules and packages will NOT get scanned by nose at all for tests.
205 """
205 """
206 # Simple utility to make IPython paths more readably, we need a lot of
206 # Simple utility to make IPython paths more readably, we need a lot of
207 # these below
207 # these below
208 ipjoin = lambda *paths: pjoin('IPython', *paths)
208 ipjoin = lambda *paths: pjoin('IPython', *paths)
209
209
210 exclusions = [ipjoin('external'),
210 exclusions = [ipjoin('external'),
211 ipjoin('quarantine'),
211 ipjoin('quarantine'),
212 ipjoin('deathrow'),
212 ipjoin('deathrow'),
213 # This guy is probably attic material
213 # This guy is probably attic material
214 ipjoin('testing', 'mkdoctests'),
214 ipjoin('testing', 'mkdoctests'),
215 # Testing inputhook will need a lot of thought, to figure out
215 # Testing inputhook will need a lot of thought, to figure out
216 # how to have tests that don't lock up with the gui event
216 # how to have tests that don't lock up with the gui event
217 # loops in the picture
217 # loops in the picture
218 ipjoin('lib', 'inputhook'),
218 ipjoin('lib', 'inputhook'),
219 # Config files aren't really importable stand-alone
219 # Config files aren't really importable stand-alone
220 ipjoin('config', 'profile'),
220 ipjoin('config', 'profile'),
221 # The notebook 'static' directory contains JS, css and other
221 # The notebook 'static' directory contains JS, css and other
222 # files for web serving. Occasionally projects may put a .py
222 # files for web serving. Occasionally projects may put a .py
223 # file in there (MathJax ships a conf.py), so we might as
223 # file in there (MathJax ships a conf.py), so we might as
224 # well play it safe and skip the whole thing.
224 # well play it safe and skip the whole thing.
225 ipjoin('html', 'static'),
225 ipjoin('html', 'static'),
226 ipjoin('html', 'fabfile'),
226 ipjoin('html', 'fabfile'),
227 ]
227 ]
228 if not have['sqlite3']:
228 if not have['sqlite3']:
229 exclusions.append(ipjoin('core', 'tests', 'test_history'))
229 exclusions.append(ipjoin('core', 'tests', 'test_history'))
230 exclusions.append(ipjoin('core', 'history'))
230 exclusions.append(ipjoin('core', 'history'))
231 if not have['wx']:
231 if not have['wx']:
232 exclusions.append(ipjoin('lib', 'inputhookwx'))
232 exclusions.append(ipjoin('lib', 'inputhookwx'))
233
233
234 if 'IPython.kernel.inprocess' not in sys.argv:
234 if 'IPython.kernel.inprocess' not in sys.argv:
235 exclusions.append(ipjoin('kernel', 'inprocess'))
235 exclusions.append(ipjoin('kernel', 'inprocess'))
236
236
237 # FIXME: temporarily disable autoreload tests, as they can produce
237 # FIXME: temporarily disable autoreload tests, as they can produce
238 # spurious failures in subsequent tests (cythonmagic).
238 # spurious failures in subsequent tests (cythonmagic).
239 exclusions.append(ipjoin('extensions', 'autoreload'))
239 exclusions.append(ipjoin('extensions', 'autoreload'))
240 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
240 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
241
241
242 # We do this unconditionally, so that the test suite doesn't import
242 # We do this unconditionally, so that the test suite doesn't import
243 # gtk, changing the default encoding and masking some unicode bugs.
243 # gtk, changing the default encoding and masking some unicode bugs.
244 exclusions.append(ipjoin('lib', 'inputhookgtk'))
244 exclusions.append(ipjoin('lib', 'inputhookgtk'))
245 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
245 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
246
246
247 #Also done unconditionally, exclude nbconvert directories containing
247 #Also done unconditionally, exclude nbconvert directories containing
248 #config files used to test. Executing the config files with iptest would
248 #config files used to test. Executing the config files with iptest would
249 #cause an exception.
249 #cause an exception.
250 exclusions.append(ipjoin('nbconvert', 'tests', 'files'))
250 exclusions.append(ipjoin('nbconvert', 'tests', 'files'))
251 exclusions.append(ipjoin('nbconvert', 'exporters', 'tests', 'files'))
251 exclusions.append(ipjoin('nbconvert', 'exporters', 'tests', 'files'))
252
252
253 # These have to be skipped on win32 because the use echo, rm, cd, etc.
253 # These have to be skipped on win32 because the use echo, rm, cd, etc.
254 # See ticket https://github.com/ipython/ipython/issues/87
254 # See ticket https://github.com/ipython/ipython/issues/87
255 if sys.platform == 'win32':
255 if sys.platform == 'win32':
256 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
256 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
257 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
257 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
258
258
259 if not have['pexpect']:
259 if not have['pexpect']:
260 exclusions.extend([ipjoin('lib', 'irunner'),
260 exclusions.extend([ipjoin('lib', 'irunner'),
261 ipjoin('lib', 'tests', 'test_irunner'),
261 ipjoin('lib', 'tests', 'test_irunner'),
262 ipjoin('terminal', 'console'),
262 ipjoin('terminal', 'console'),
263 ])
263 ])
264
264
265 if not have['zmq']:
265 if not have['zmq']:
266 exclusions.append(ipjoin('lib', 'kernel'))
266 exclusions.append(ipjoin('lib', 'kernel'))
267 exclusions.append(ipjoin('kernel'))
267 exclusions.append(ipjoin('kernel'))
268 exclusions.append(ipjoin('qt'))
268 exclusions.append(ipjoin('qt'))
269 exclusions.append(ipjoin('html'))
269 exclusions.append(ipjoin('html'))
270 exclusions.append(ipjoin('consoleapp.py'))
270 exclusions.append(ipjoin('consoleapp.py'))
271 exclusions.append(ipjoin('terminal', 'console'))
271 exclusions.append(ipjoin('terminal', 'console'))
272 exclusions.append(ipjoin('parallel'))
272 exclusions.append(ipjoin('parallel'))
273 elif not have['qt'] or not have['pygments']:
273 elif not have['qt'] or not have['pygments']:
274 exclusions.append(ipjoin('qt'))
274 exclusions.append(ipjoin('qt'))
275
275
276 if not have['pymongo']:
276 if not have['pymongo']:
277 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
277 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
278 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
278 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
279
279
280 if not have['matplotlib']:
280 if not have['matplotlib']:
281 exclusions.extend([ipjoin('core', 'pylabtools'),
281 exclusions.extend([ipjoin('core', 'pylabtools'),
282 ipjoin('core', 'tests', 'test_pylabtools'),
282 ipjoin('core', 'tests', 'test_pylabtools'),
283 ipjoin('kernel', 'zmq', 'pylab'),
283 ipjoin('kernel', 'zmq', 'pylab'),
284 ])
284 ])
285
285
286 if not have['cython']:
286 if not have['cython']:
287 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
287 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
288 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
288 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
289
289
290 if not have['oct2py']:
290 if not have['oct2py']:
291 exclusions.extend([ipjoin('extensions', 'octavemagic')])
291 exclusions.extend([ipjoin('extensions', 'octavemagic')])
292 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
292 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
293
293
294 if not have['tornado']:
294 if not have['tornado']:
295 exclusions.append(ipjoin('html'))
295 exclusions.append(ipjoin('html'))
296 exclusions.append(ipjoin('nbconvert', 'post_processors', 'serve'))
297 exclusions.append(ipjoin('nbconvert', 'post_processors', 'tests', 'test_serve'))
296
298
297 if not have['jinja2']:
299 if not have['jinja2']:
298 exclusions.append(ipjoin('html', 'notebookapp'))
300 exclusions.append(ipjoin('html', 'notebookapp'))
299
301
300 if not have['rpy2'] or not have['numpy']:
302 if not have['rpy2'] or not have['numpy']:
301 exclusions.append(ipjoin('extensions', 'rmagic'))
303 exclusions.append(ipjoin('extensions', 'rmagic'))
302 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
304 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
303
305
304 if not have['azure']:
306 if not have['azure']:
305 exclusions.append(ipjoin('html', 'services', 'notebooks', 'azurenbmanager'))
307 exclusions.append(ipjoin('html', 'services', 'notebooks', 'azurenbmanager'))
306
308
307 if not all((have['pygments'], have['jinja2'], have['sphinx'])):
309 if not all((have['pygments'], have['jinja2'], have['sphinx'])):
308 exclusions.append(ipjoin('nbconvert'))
310 exclusions.append(ipjoin('nbconvert'))
309
311
310 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
312 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
311 if sys.platform == 'win32':
313 if sys.platform == 'win32':
312 exclusions = [s.replace('\\','\\\\') for s in exclusions]
314 exclusions = [s.replace('\\','\\\\') for s in exclusions]
313
315
314 # check for any exclusions that don't seem to exist:
316 # check for any exclusions that don't seem to exist:
315 parent, _ = os.path.split(get_ipython_package_dir())
317 parent, _ = os.path.split(get_ipython_package_dir())
316 for exclusion in exclusions:
318 for exclusion in exclusions:
317 if exclusion.endswith(('deathrow', 'quarantine')):
319 if exclusion.endswith(('deathrow', 'quarantine')):
318 # ignore deathrow/quarantine, which exist in dev, but not install
320 # ignore deathrow/quarantine, which exist in dev, but not install
319 continue
321 continue
320 fullpath = pjoin(parent, exclusion)
322 fullpath = pjoin(parent, exclusion)
321 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
323 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
322 warn("Excluding nonexistent file: %r" % exclusion)
324 warn("Excluding nonexistent file: %r" % exclusion)
323
325
324 return exclusions
326 return exclusions
325
327
326
328
327 class IPTester(object):
329 class IPTester(object):
328 """Call that calls iptest or trial in a subprocess.
330 """Call that calls iptest or trial in a subprocess.
329 """
331 """
330 #: string, name of test runner that will be called
332 #: string, name of test runner that will be called
331 runner = None
333 runner = None
332 #: list, parameters for test runner
334 #: list, parameters for test runner
333 params = None
335 params = None
334 #: list, arguments of system call to be made to call test runner
336 #: list, arguments of system call to be made to call test runner
335 call_args = None
337 call_args = None
336 #: list, subprocesses we start (for cleanup)
338 #: list, subprocesses we start (for cleanup)
337 processes = None
339 processes = None
338 #: str, coverage xml output file
340 #: str, coverage xml output file
339 coverage_xml = None
341 coverage_xml = None
340 buffer_output = False
342 buffer_output = False
341
343
342 def __init__(self, runner='iptest', params=None):
344 def __init__(self, runner='iptest', params=None):
343 """Create new test runner."""
345 """Create new test runner."""
344 p = os.path
346 p = os.path
345 if runner == 'iptest':
347 if runner == 'iptest':
346 iptest_app = os.path.abspath(get_ipython_module_path('IPython.testing.iptest'))
348 iptest_app = os.path.abspath(get_ipython_module_path('IPython.testing.iptest'))
347 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
349 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
348 else:
350 else:
349 raise Exception('Not a valid test runner: %s' % repr(runner))
351 raise Exception('Not a valid test runner: %s' % repr(runner))
350 if params is None:
352 if params is None:
351 params = []
353 params = []
352 if isinstance(params, str):
354 if isinstance(params, str):
353 params = [params]
355 params = [params]
354 self.params = params
356 self.params = params
355
357
356 # Assemble call
358 # Assemble call
357 self.call_args = self.runner+self.params
359 self.call_args = self.runner+self.params
358
360
359 # Find the section we're testing (IPython.foo)
361 # Find the section we're testing (IPython.foo)
360 for sect in self.params:
362 for sect in self.params:
361 if sect.startswith('IPython') or sect in special_test_suites: break
363 if sect.startswith('IPython') or sect in special_test_suites: break
362 else:
364 else:
363 raise ValueError("Section not found", self.params)
365 raise ValueError("Section not found", self.params)
364
366
365 if '--with-xunit' in self.call_args:
367 if '--with-xunit' in self.call_args:
366
368
367 self.call_args.append('--xunit-file')
369 self.call_args.append('--xunit-file')
368 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
370 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
369 xunit_file = path.abspath(sect+'.xunit.xml')
371 xunit_file = path.abspath(sect+'.xunit.xml')
370 if sys.platform == 'win32':
372 if sys.platform == 'win32':
371 xunit_file = '"%s"' % xunit_file
373 xunit_file = '"%s"' % xunit_file
372 self.call_args.append(xunit_file)
374 self.call_args.append(xunit_file)
373
375
374 if '--with-xml-coverage' in self.call_args:
376 if '--with-xml-coverage' in self.call_args:
375 self.coverage_xml = path.abspath(sect+".coverage.xml")
377 self.coverage_xml = path.abspath(sect+".coverage.xml")
376 self.call_args.remove('--with-xml-coverage')
378 self.call_args.remove('--with-xml-coverage')
377 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
379 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
378
380
379 # Store anything we start to clean up on deletion
381 # Store anything we start to clean up on deletion
380 self.processes = []
382 self.processes = []
381
383
382 def _run_cmd(self):
384 def _run_cmd(self):
383 with TemporaryDirectory() as IPYTHONDIR:
385 with TemporaryDirectory() as IPYTHONDIR:
384 env = os.environ.copy()
386 env = os.environ.copy()
385 env['IPYTHONDIR'] = IPYTHONDIR
387 env['IPYTHONDIR'] = IPYTHONDIR
386 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
388 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
387 output = subprocess.PIPE if self.buffer_output else None
389 output = subprocess.PIPE if self.buffer_output else None
388 subp = subprocess.Popen(self.call_args, stdout=output,
390 subp = subprocess.Popen(self.call_args, stdout=output,
389 stderr=output, env=env)
391 stderr=output, env=env)
390 self.processes.append(subp)
392 self.processes.append(subp)
391 # If this fails, the process will be left in self.processes and
393 # If this fails, the process will be left in self.processes and
392 # cleaned up later, but if the wait call succeeds, then we can
394 # cleaned up later, but if the wait call succeeds, then we can
393 # clear the stored process.
395 # clear the stored process.
394 retcode = subp.wait()
396 retcode = subp.wait()
395 self.processes.pop()
397 self.processes.pop()
396 self.stdout = subp.stdout
398 self.stdout = subp.stdout
397 self.stderr = subp.stderr
399 self.stderr = subp.stderr
398 return retcode
400 return retcode
399
401
400 def run(self):
402 def run(self):
401 """Run the stored commands"""
403 """Run the stored commands"""
402 try:
404 try:
403 retcode = self._run_cmd()
405 retcode = self._run_cmd()
404 except KeyboardInterrupt:
406 except KeyboardInterrupt:
405 return -signal.SIGINT
407 return -signal.SIGINT
406 except:
408 except:
407 import traceback
409 import traceback
408 traceback.print_exc()
410 traceback.print_exc()
409 return 1 # signal failure
411 return 1 # signal failure
410
412
411 if self.coverage_xml:
413 if self.coverage_xml:
412 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
414 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
413 return retcode
415 return retcode
414
416
415 def __del__(self):
417 def __del__(self):
416 """Cleanup on exit by killing any leftover processes."""
418 """Cleanup on exit by killing any leftover processes."""
417 for subp in self.processes:
419 for subp in self.processes:
418 if subp.poll() is not None:
420 if subp.poll() is not None:
419 continue # process is already dead
421 continue # process is already dead
420
422
421 try:
423 try:
422 print('Cleaning up stale PID: %d' % subp.pid)
424 print('Cleaning up stale PID: %d' % subp.pid)
423 subp.kill()
425 subp.kill()
424 except: # (OSError, WindowsError) ?
426 except: # (OSError, WindowsError) ?
425 # This is just a best effort, if we fail or the process was
427 # This is just a best effort, if we fail or the process was
426 # really gone, ignore it.
428 # really gone, ignore it.
427 pass
429 pass
428 else:
430 else:
429 for i in range(10):
431 for i in range(10):
430 if subp.poll() is None:
432 if subp.poll() is None:
431 time.sleep(0.1)
433 time.sleep(0.1)
432 else:
434 else:
433 break
435 break
434
436
435 if subp.poll() is None:
437 if subp.poll() is None:
436 # The process did not die...
438 # The process did not die...
437 print('... failed. Manual cleanup may be required.')
439 print('... failed. Manual cleanup may be required.')
438
440
439
441
440 special_test_suites = {
442 special_test_suites = {
441 'autoreload': ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'],
443 'autoreload': ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'],
442 }
444 }
443
445
444 def make_runners(inc_slow=False):
446 def make_runners(inc_slow=False):
445 """Define the top-level packages that need to be tested.
447 """Define the top-level packages that need to be tested.
446 """
448 """
447
449
448 # Packages to be tested via nose, that only depend on the stdlib
450 # Packages to be tested via nose, that only depend on the stdlib
449 nose_pkg_names = ['config', 'core', 'extensions', 'lib', 'terminal',
451 nose_pkg_names = ['config', 'core', 'extensions', 'lib', 'terminal',
450 'testing', 'utils', 'nbformat']
452 'testing', 'utils', 'nbformat']
451
453
452 if have['qt']:
454 if have['qt']:
453 nose_pkg_names.append('qt')
455 nose_pkg_names.append('qt')
454
456
455 if have['tornado']:
457 if have['tornado']:
456 nose_pkg_names.append('html')
458 nose_pkg_names.append('html')
457
459
458 if have['zmq']:
460 if have['zmq']:
459 nose_pkg_names.insert(0, 'kernel')
461 nose_pkg_names.insert(0, 'kernel')
460 nose_pkg_names.insert(1, 'kernel.inprocess')
462 nose_pkg_names.insert(1, 'kernel.inprocess')
461 if inc_slow:
463 if inc_slow:
462 nose_pkg_names.insert(0, 'parallel')
464 nose_pkg_names.insert(0, 'parallel')
463
465
464 if all((have['pygments'], have['jinja2'], have['sphinx'])):
466 if all((have['pygments'], have['jinja2'], have['sphinx'])):
465 nose_pkg_names.append('nbconvert')
467 nose_pkg_names.append('nbconvert')
466
468
467 # For debugging this code, only load quick stuff
469 # For debugging this code, only load quick stuff
468 #nose_pkg_names = ['core', 'extensions'] # dbg
470 #nose_pkg_names = ['core', 'extensions'] # dbg
469
471
470 # Make fully qualified package names prepending 'IPython.' to our name lists
472 # Make fully qualified package names prepending 'IPython.' to our name lists
471 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
473 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
472
474
473 # Make runners
475 # Make runners
474 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
476 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
475
477
476 for name in special_test_suites:
478 for name in special_test_suites:
477 runners.append((name, IPTester('iptest', params=name)))
479 runners.append((name, IPTester('iptest', params=name)))
478
480
479 return runners
481 return runners
480
482
481
483
482 def run_iptest():
484 def run_iptest():
483 """Run the IPython test suite using nose.
485 """Run the IPython test suite using nose.
484
486
485 This function is called when this script is **not** called with the form
487 This function is called when this script is **not** called with the form
486 `iptest all`. It simply calls nose with appropriate command line flags
488 `iptest all`. It simply calls nose with appropriate command line flags
487 and accepts all of the standard nose arguments.
489 and accepts all of the standard nose arguments.
488 """
490 """
489 # Apply our monkeypatch to Xunit
491 # Apply our monkeypatch to Xunit
490 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
492 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
491 monkeypatch_xunit()
493 monkeypatch_xunit()
492
494
493 warnings.filterwarnings('ignore',
495 warnings.filterwarnings('ignore',
494 'This will be removed soon. Use IPython.testing.util instead')
496 'This will be removed soon. Use IPython.testing.util instead')
495
497
496 if sys.argv[1] in special_test_suites:
498 if sys.argv[1] in special_test_suites:
497 sys.argv[1:2] = special_test_suites[sys.argv[1]]
499 sys.argv[1:2] = special_test_suites[sys.argv[1]]
498 special_suite = True
500 special_suite = True
499 else:
501 else:
500 special_suite = False
502 special_suite = False
501
503
502 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
504 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
503
505
504 '--with-ipdoctest',
506 '--with-ipdoctest',
505 '--ipdoctest-tests','--ipdoctest-extension=txt',
507 '--ipdoctest-tests','--ipdoctest-extension=txt',
506
508
507 # We add --exe because of setuptools' imbecility (it
509 # We add --exe because of setuptools' imbecility (it
508 # blindly does chmod +x on ALL files). Nose does the
510 # blindly does chmod +x on ALL files). Nose does the
509 # right thing and it tries to avoid executables,
511 # right thing and it tries to avoid executables,
510 # setuptools unfortunately forces our hand here. This
512 # setuptools unfortunately forces our hand here. This
511 # has been discussed on the distutils list and the
513 # has been discussed on the distutils list and the
512 # setuptools devs refuse to fix this problem!
514 # setuptools devs refuse to fix this problem!
513 '--exe',
515 '--exe',
514 ]
516 ]
515 if '-a' not in argv and '-A' not in argv:
517 if '-a' not in argv and '-A' not in argv:
516 argv = argv + ['-a', '!crash']
518 argv = argv + ['-a', '!crash']
517
519
518 if nose.__version__ >= '0.11':
520 if nose.__version__ >= '0.11':
519 # I don't fully understand why we need this one, but depending on what
521 # I don't fully understand why we need this one, but depending on what
520 # directory the test suite is run from, if we don't give it, 0 tests
522 # directory the test suite is run from, if we don't give it, 0 tests
521 # get run. Specifically, if the test suite is run from the source dir
523 # get run. Specifically, if the test suite is run from the source dir
522 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
524 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
523 # even if the same call done in this directory works fine). It appears
525 # even if the same call done in this directory works fine). It appears
524 # that if the requested package is in the current dir, nose bails early
526 # that if the requested package is in the current dir, nose bails early
525 # by default. Since it's otherwise harmless, leave it in by default
527 # by default. Since it's otherwise harmless, leave it in by default
526 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
528 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
527 argv.append('--traverse-namespace')
529 argv.append('--traverse-namespace')
528
530
529 # use our plugin for doctesting. It will remove the standard doctest plugin
531 # use our plugin for doctesting. It will remove the standard doctest plugin
530 # if it finds it enabled
532 # if it finds it enabled
531 ipdt = IPythonDoctest() if special_suite else IPythonDoctest(make_exclude())
533 ipdt = IPythonDoctest() if special_suite else IPythonDoctest(make_exclude())
532 plugins = [ipdt, KnownFailure()]
534 plugins = [ipdt, KnownFailure()]
533
535
534 # We need a global ipython running in this process, but the special
536 # We need a global ipython running in this process, but the special
535 # in-process group spawns its own IPython kernels, so for *that* group we
537 # in-process group spawns its own IPython kernels, so for *that* group we
536 # must avoid also opening the global one (otherwise there's a conflict of
538 # must avoid also opening the global one (otherwise there's a conflict of
537 # singletons). Ultimately the solution to this problem is to refactor our
539 # singletons). Ultimately the solution to this problem is to refactor our
538 # assumptions about what needs to be a singleton and what doesn't (app
540 # assumptions about what needs to be a singleton and what doesn't (app
539 # objects should, individual shells shouldn't). But for now, this
541 # objects should, individual shells shouldn't). But for now, this
540 # workaround allows the test suite for the inprocess module to complete.
542 # workaround allows the test suite for the inprocess module to complete.
541 if not 'IPython.kernel.inprocess' in sys.argv:
543 if not 'IPython.kernel.inprocess' in sys.argv:
542 globalipapp.start_ipython()
544 globalipapp.start_ipython()
543
545
544 # Now nose can run
546 # Now nose can run
545 TestProgram(argv=argv, addplugins=plugins)
547 TestProgram(argv=argv, addplugins=plugins)
546
548
547 def do_run(x):
549 def do_run(x):
548 print('IPython test group:',x[0])
550 print('IPython test group:',x[0])
549 ret = x[1].run()
551 ret = x[1].run()
550 return ret
552 return ret
551
553
552 def run_iptestall(inc_slow=False, fast=False):
554 def run_iptestall(inc_slow=False, fast=False):
553 """Run the entire IPython test suite by calling nose and trial.
555 """Run the entire IPython test suite by calling nose and trial.
554
556
555 This function constructs :class:`IPTester` instances for all IPython
557 This function constructs :class:`IPTester` instances for all IPython
556 modules and package and then runs each of them. This causes the modules
558 modules and package and then runs each of them. This causes the modules
557 and packages of IPython to be tested each in their own subprocess using
559 and packages of IPython to be tested each in their own subprocess using
558 nose.
560 nose.
559
561
560 Parameters
562 Parameters
561 ----------
563 ----------
562
564
563 inc_slow : bool, optional
565 inc_slow : bool, optional
564 Include slow tests, like IPython.parallel. By default, these tests aren't
566 Include slow tests, like IPython.parallel. By default, these tests aren't
565 run.
567 run.
566
568
567 fast : bool, option
569 fast : bool, option
568 Run the test suite in parallel, if True, using as many threads as there
570 Run the test suite in parallel, if True, using as many threads as there
569 are processors
571 are processors
570 """
572 """
571 if fast:
573 if fast:
572 p = multiprocessing.pool.ThreadPool()
574 p = multiprocessing.pool.ThreadPool()
573 else:
575 else:
574 p = multiprocessing.pool.ThreadPool(1)
576 p = multiprocessing.pool.ThreadPool(1)
575
577
576 runners = make_runners(inc_slow=inc_slow)
578 runners = make_runners(inc_slow=inc_slow)
577
579
578 # Run the test runners in a temporary dir so we can nuke it when finished
580 # Run the test runners in a temporary dir so we can nuke it when finished
579 # to clean up any junk files left over by accident. This also makes it
581 # to clean up any junk files left over by accident. This also makes it
580 # robust against being run in non-writeable directories by mistake, as the
582 # robust against being run in non-writeable directories by mistake, as the
581 # temp dir will always be user-writeable.
583 # temp dir will always be user-writeable.
582 curdir = os.getcwdu()
584 curdir = os.getcwdu()
583 testdir = tempfile.gettempdir()
585 testdir = tempfile.gettempdir()
584 os.chdir(testdir)
586 os.chdir(testdir)
585
587
586 # Run all test runners, tracking execution time
588 # Run all test runners, tracking execution time
587 failed = []
589 failed = []
588 t_start = time.time()
590 t_start = time.time()
589
591
590 try:
592 try:
591 all_res = p.map(do_run, runners)
593 all_res = p.map(do_run, runners)
592 print('*'*70)
594 print('*'*70)
593 for ((name, runner), res) in zip(runners, all_res):
595 for ((name, runner), res) in zip(runners, all_res):
594 tgroup = 'IPython test group: ' + name
596 tgroup = 'IPython test group: ' + name
595 res_string = 'OK' if res == 0 else 'FAILED'
597 res_string = 'OK' if res == 0 else 'FAILED'
596 res_string = res_string.rjust(70 - len(tgroup), '.')
598 res_string = res_string.rjust(70 - len(tgroup), '.')
597 print(tgroup + res_string)
599 print(tgroup + res_string)
598 if res:
600 if res:
599 failed.append( (name, runner) )
601 failed.append( (name, runner) )
600 if res == -signal.SIGINT:
602 if res == -signal.SIGINT:
601 print("Interrupted")
603 print("Interrupted")
602 break
604 break
603 finally:
605 finally:
604 os.chdir(curdir)
606 os.chdir(curdir)
605 t_end = time.time()
607 t_end = time.time()
606 t_tests = t_end - t_start
608 t_tests = t_end - t_start
607 nrunners = len(runners)
609 nrunners = len(runners)
608 nfail = len(failed)
610 nfail = len(failed)
609 # summarize results
611 # summarize results
610 print()
612 print()
611 print('*'*70)
613 print('*'*70)
612 print('Test suite completed for system with the following information:')
614 print('Test suite completed for system with the following information:')
613 print(report())
615 print(report())
614 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
616 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
615 print()
617 print()
616 print('Status:')
618 print('Status:')
617 if not failed:
619 if not failed:
618 print('OK')
620 print('OK')
619 else:
621 else:
620 # If anything went wrong, point out what command to rerun manually to
622 # If anything went wrong, point out what command to rerun manually to
621 # see the actual errors and individual summary
623 # see the actual errors and individual summary
622 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
624 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
623 for name, failed_runner in failed:
625 for name, failed_runner in failed:
624 print('-'*40)
626 print('-'*40)
625 print('Runner failed:',name)
627 print('Runner failed:',name)
626 print('You may wish to rerun this one individually, with:')
628 print('You may wish to rerun this one individually, with:')
627 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
629 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
628 print(u' '.join(failed_call_args))
630 print(u' '.join(failed_call_args))
629 print()
631 print()
630 # Ensure that our exit code indicates failure
632 # Ensure that our exit code indicates failure
631 sys.exit(1)
633 sys.exit(1)
632
634
633
635
634 def main():
636 def main():
635 for arg in sys.argv[1:]:
637 for arg in sys.argv[1:]:
636 if arg.startswith('IPython') or arg in special_test_suites:
638 if arg.startswith('IPython') or arg in special_test_suites:
637 # This is in-process
639 # This is in-process
638 run_iptest()
640 run_iptest()
639 else:
641 else:
640 inc_slow = "--all" in sys.argv
642 inc_slow = "--all" in sys.argv
641 if inc_slow:
643 if inc_slow:
642 sys.argv.remove("--all")
644 sys.argv.remove("--all")
643
645
644 fast = "--fast" in sys.argv
646 fast = "--fast" in sys.argv
645 if fast:
647 if fast:
646 sys.argv.remove("--fast")
648 sys.argv.remove("--fast")
647 IPTester.buffer_output = True
649 IPTester.buffer_output = True
648
650
649 # This starts subprocesses
651 # This starts subprocesses
650 run_iptestall(inc_slow=inc_slow, fast=fast)
652 run_iptestall(inc_slow=inc_slow, fast=fast)
651
653
652
654
653 if __name__ == '__main__':
655 if __name__ == '__main__':
654 main()
656 main()
@@ -1,232 +1,227 b''
1 .. _nbconvert:
1 .. _nbconvert:
2
2
3 Converting notebooks to other formats
3 Converting notebooks to other formats
4 =====================================
4 =====================================
5
5
6 Newly added in the 1.0 release of IPython is the ``nbconvert`` tool, which
6 Newly added in the 1.0 release of IPython is the ``nbconvert`` tool, which
7 allows you to convert an ``.ipynb`` notebook document file into various static
7 allows you to convert an ``.ipynb`` notebook document file into various static
8 formats.
8 formats.
9
9
10 Currently, ``nbconvert`` is provided as a command line tool, run as a script
10 Currently, ``nbconvert`` is provided as a command line tool, run as a script
11 using IPython. A direct export capability from within the
11 using IPython. A direct export capability from within the
12 IPython Notebook web app is planned.
12 IPython Notebook web app is planned.
13
13
14 The command-line syntax to run the ``nbconvert`` script is::
14 The command-line syntax to run the ``nbconvert`` script is::
15
15
16 $ ipython nbconvert --to FORMAT notebook.ipynb
16 $ ipython nbconvert --to FORMAT notebook.ipynb
17
17
18 This will convert the IPython document file ``notebook.ipynb`` into the output
18 This will convert the IPython document file ``notebook.ipynb`` into the output
19 format given by the ``FORMAT`` string.
19 format given by the ``FORMAT`` string.
20
20
21 The default output format is html, for which the ``--to`` argument may be
21 The default output format is html, for which the ``--to`` argument may be
22 omitted::
22 omitted::
23
23
24 $ ipython nbconvert notebook.ipynb
24 $ ipython nbconvert notebook.ipynb
25
25
26 IPython provides a few templates for some output formats, and these can be
26 IPython provides a few templates for some output formats, and these can be
27 specified via an additional ``--template`` argument.
27 specified via an additional ``--template`` argument.
28
28
29 The currently supported export formats are:
29 The currently supported export formats are:
30
30
31 * ``--to html``
31 * ``--to html``
32
32
33 - ``--template full`` (default)
33 - ``--template full`` (default)
34
34
35 A full static HTML render of the notebook.
35 A full static HTML render of the notebook.
36 This looks very similar to the interactive view.
36 This looks very similar to the interactive view.
37
37
38 - ``--template basic``
38 - ``--template basic``
39
39
40 Simplified HTML, useful for embedding in webpages, blogs, etc.
40 Simplified HTML, useful for embedding in webpages, blogs, etc.
41 This excludes HTML headers.
41 This excludes HTML headers.
42
42
43 * ``--to latex``
43 * ``--to latex``
44
44
45 Latex export. This generates ``NOTEBOOK_NAME.tex`` file,
45 Latex export. This generates ``NOTEBOOK_NAME.tex`` file,
46 ready for export. You can automatically run latex on it to generate a PDF
46 ready for export. You can automatically run latex on it to generate a PDF
47 by adding ``--post PDF``.
47 by adding ``--post PDF``.
48
48
49 - ``--template article`` (default)
49 - ``--template article`` (default)
50
50
51 Latex article, derived from Sphinx's howto template.
51 Latex article, derived from Sphinx's howto template.
52
52
53 - ``--template book``
53 - ``--template book``
54
54
55 Latex book, derived from Sphinx's manual template.
55 Latex book, derived from Sphinx's manual template.
56
56
57 - ``--template basic``
57 - ``--template basic``
58
58
59 Very basic latex output - mainly meant as a starting point for custom templates.
59 Very basic latex output - mainly meant as a starting point for custom templates.
60
60
61 * ``--to slides``
61 * ``--to slides``
62
62
63 This generates a Reveal.js HTML slideshow.
63 This generates a Reveal.js HTML slideshow.
64 It must be served by an HTTP server. The easiest way to get this is to add
64 It must be served by an HTTP server. The easiest way to do this is adding
65 ``--post serve`` on the command-line.
65 ``--post serve`` on the command-line.
66 If you want to use the speaker notes plugin, just add
66
67 ``--slide-notes=True`` on the command-line.
68 For low connectivity environments, you can use a local copy of the reveal.js library,
69 just add ``--offline-slides=reveal.js`` on the command-line, and do not forget to move
70 your downloaded ``reveal.js`` library to the same folder where your slides are located.
71
72 * ``--to markdown``
67 * ``--to markdown``
73
68
74 Simple markdown output. Markdown cells are unaffected,
69 Simple markdown output. Markdown cells are unaffected,
75 and code cells are placed in triple-backtick (```````) blocks.
70 and code cells are placed in triple-backtick (```````) blocks.
76
71
77 * ``--to rst``
72 * ``--to rst``
78
73
79 Basic reStructuredText output. Useful as a starting point for embedding notebooks
74 Basic reStructuredText output. Useful as a starting point for embedding notebooks
80 in Sphinx docs.
75 in Sphinx docs.
81
76
82 * ``--to python``
77 * ``--to python``
83
78
84 Convert a notebook to an executable Python script.
79 Convert a notebook to an executable Python script.
85 This is the simplest way to get a Python script out of a notebook.
80 This is the simplest way to get a Python script out of a notebook.
86 If there were any magics in the notebook, this may only be executable from
81 If there were any magics in the notebook, this may only be executable from
87 an IPython session.
82 an IPython session.
88
83
89 .. note::
84 .. note::
90
85
91 nbconvert uses pandoc_ to convert between various markup languages,
86 nbconvert uses pandoc_ to convert between various markup languages,
92 so pandoc is a dependency of most nbconvert transforms,
87 so pandoc is a dependency of most nbconvert transforms,
93 excluding Markdown and Python.
88 excluding Markdown and Python.
94
89
95 .. _pandoc: http://johnmacfarlane.net/pandoc/
90 .. _pandoc: http://johnmacfarlane.net/pandoc/
96
91
97 The output file created by ``nbconvert`` will have the same base name as
92 The output file created by ``nbconvert`` will have the same base name as
98 the notebook and will be placed in the current working directory. Any
93 the notebook and will be placed in the current working directory. Any
99 supporting files (graphics, etc) will be placed in a new directory with the
94 supporting files (graphics, etc) will be placed in a new directory with the
100 same base name as the notebook, suffixed with ``_files``::
95 same base name as the notebook, suffixed with ``_files``::
101
96
102 $ ipython nbconvert notebook.ipynb
97 $ ipython nbconvert notebook.ipynb
103 $ ls
98 $ ls
104 notebook.ipynb notebook.html notebook_files/
99 notebook.ipynb notebook.html notebook_files/
105
100
106 For simple single-file output, such as html, markdown, etc.,
101 For simple single-file output, such as html, markdown, etc.,
107 the output may be sent to standard output with::
102 the output may be sent to standard output with::
108
103
109 $ ipython nbconvert --to markdown notebook.ipynb --stdout
104 $ ipython nbconvert --to markdown notebook.ipynb --stdout
110
105
111 Multiple notebooks can be specified from the command line::
106 Multiple notebooks can be specified from the command line::
112
107
113 $ ipython nbconvert notebook*.ipynb
108 $ ipython nbconvert notebook*.ipynb
114 $ ipython nbconvert notebook1.ipynb notebook2.ipynb
109 $ ipython nbconvert notebook1.ipynb notebook2.ipynb
115
110
116 or via a list in a configuration file, say ``mycfg.py``, containing the text::
111 or via a list in a configuration file, say ``mycfg.py``, containing the text::
117
112
118 c = get_config()
113 c = get_config()
119 c.NbConvertApp.notebooks = ["notebook1.ipynb", "notebook2.ipynb"]
114 c.NbConvertApp.notebooks = ["notebook1.ipynb", "notebook2.ipynb"]
120
115
121 and using the command::
116 and using the command::
122
117
123 $ ipython nbconvert --config mycfg.py
118 $ ipython nbconvert --config mycfg.py
124
119
125
120
126 .. _notebook_format:
121 .. _notebook_format:
127
122
128 LaTeX citations
123 LaTeX citations
129 ---------------
124 ---------------
130
125
131 ``nbconvert`` now has support for LaTeX citations. With this capability you
126 ``nbconvert`` now has support for LaTeX citations. With this capability you
132 can:
127 can:
133
128
134 * Manage citations using BibTeX.
129 * Manage citations using BibTeX.
135 * Cite those citations in Markdown cells using HTML data attributes.
130 * Cite those citations in Markdown cells using HTML data attributes.
136 * Have ``nbconvert`` generate proper LaTeX citations and run BibTeX.
131 * Have ``nbconvert`` generate proper LaTeX citations and run BibTeX.
137
132
138 For an example of how this works, please see the citations example in
133 For an example of how this works, please see the citations example in
139 the nbconvert-examples_ repository.
134 the nbconvert-examples_ repository.
140
135
141 .. _nbconvert-examples: https://github.com/ipython/nbconvert-examples
136 .. _nbconvert-examples: https://github.com/ipython/nbconvert-examples
142
137
143 Notebook JSON file format
138 Notebook JSON file format
144 -------------------------
139 -------------------------
145
140
146 Notebook documents are JSON files with an ``.ipynb`` extension, formatted
141 Notebook documents are JSON files with an ``.ipynb`` extension, formatted
147 as legibly as possible with minimal extra indentation and cell content broken
142 as legibly as possible with minimal extra indentation and cell content broken
148 across lines to make them reasonably friendly to use in version-control
143 across lines to make them reasonably friendly to use in version-control
149 workflows. You should be very careful if you ever manually edit this JSON
144 workflows. You should be very careful if you ever manually edit this JSON
150 data, as it is extremely easy to corrupt its internal structure and make the
145 data, as it is extremely easy to corrupt its internal structure and make the
151 file impossible to load. In general, you should consider the notebook as a
146 file impossible to load. In general, you should consider the notebook as a
152 file meant only to be edited by the IPython Notebook app itself, not for
147 file meant only to be edited by the IPython Notebook app itself, not for
153 hand-editing.
148 hand-editing.
154
149
155 .. note::
150 .. note::
156
151
157 Binary data such as figures are also saved directly in the JSON file.
152 Binary data such as figures are also saved directly in the JSON file.
158 This provides convenient single-file portability, but means that the
153 This provides convenient single-file portability, but means that the
159 files can be large; a ``diff`` of binary data is also not very
154 files can be large; a ``diff`` of binary data is also not very
160 meaningful. Since the binary blobs are encoded in a single line, they
155 meaningful. Since the binary blobs are encoded in a single line, they
161 affect only one line of the ``diff`` output, but they are typically very
156 affect only one line of the ``diff`` output, but they are typically very
162 long lines. You can use the ``Cell | All Output | Clear`` menu option to
157 long lines. You can use the ``Cell | All Output | Clear`` menu option to
163 remove all output from a notebook prior to committing it to version
158 remove all output from a notebook prior to committing it to version
164 control, if this is a concern.
159 control, if this is a concern.
165
160
166 The notebook server can also generate a pure Python version of your notebook,
161 The notebook server can also generate a pure Python version of your notebook,
167 using the ``File | Download as`` menu option. The resulting ``.py`` file will
162 using the ``File | Download as`` menu option. The resulting ``.py`` file will
168 contain all the code cells from your notebook verbatim, and all Markdown cells
163 contain all the code cells from your notebook verbatim, and all Markdown cells
169 prepended with a comment marker. The separation between code and Markdown
164 prepended with a comment marker. The separation between code and Markdown
170 cells is indicated with special comments and there is a header indicating the
165 cells is indicated with special comments and there is a header indicating the
171 format version. All output is removed when exporting to Python.
166 format version. All output is removed when exporting to Python.
172
167
173 As an example, consider a simple notebook called ``simple.ipynb`` which
168 As an example, consider a simple notebook called ``simple.ipynb`` which
174 contains one Markdown cell, with the content ``The simplest notebook.``, one
169 contains one Markdown cell, with the content ``The simplest notebook.``, one
175 code input cell with the content ``print "Hello, IPython!"``, and the
170 code input cell with the content ``print "Hello, IPython!"``, and the
176 corresponding output.
171 corresponding output.
177
172
178 The contents of the notebook document ``simple.ipynb`` is the following JSON
173 The contents of the notebook document ``simple.ipynb`` is the following JSON
179 container::
174 container::
180
175
181 {
176 {
182 "metadata": {
177 "metadata": {
183 "name": "simple"
178 "name": "simple"
184 },
179 },
185 "nbformat": 3,
180 "nbformat": 3,
186 "nbformat_minor": 0,
181 "nbformat_minor": 0,
187 "worksheets": [
182 "worksheets": [
188 {
183 {
189 "cells": [
184 "cells": [
190 {
185 {
191 "cell_type": "markdown",
186 "cell_type": "markdown",
192 "metadata": {},
187 "metadata": {},
193 "source": "The simplest notebook."
188 "source": "The simplest notebook."
194 },
189 },
195 {
190 {
196 "cell_type": "code",
191 "cell_type": "code",
197 "collapsed": false,
192 "collapsed": false,
198 "input": "print \"Hello, IPython\"",
193 "input": "print \"Hello, IPython\"",
199 "language": "python",
194 "language": "python",
200 "metadata": {},
195 "metadata": {},
201 "outputs": [
196 "outputs": [
202 {
197 {
203 "output_type": "stream",
198 "output_type": "stream",
204 "stream": "stdout",
199 "stream": "stdout",
205 "text": "Hello, IPython\n"
200 "text": "Hello, IPython\n"
206 }
201 }
207 ],
202 ],
208 "prompt_number": 1
203 "prompt_number": 1
209 }
204 }
210 ],
205 ],
211 "metadata": {}
206 "metadata": {}
212 }
207 }
213 ]
208 ]
214 }
209 }
215
210
216
211
217 The corresponding Python script is::
212 The corresponding Python script is::
218
213
219 # -*- coding: utf-8 -*-
214 # -*- coding: utf-8 -*-
220 # <nbformat>3.0</nbformat>
215 # <nbformat>3.0</nbformat>
221
216
222 # <markdowncell>
217 # <markdowncell>
223
218
224 # The simplest notebook.
219 # The simplest notebook.
225
220
226 # <codecell>
221 # <codecell>
227
222
228 print "Hello, IPython"
223 print "Hello, IPython"
229
224
230 Note that indeed the output of the code cell, which is present in the JSON
225 Note that indeed the output of the code cell, which is present in the JSON
231 container, has been removed in the ``.py`` script.
226 container, has been removed in the ``.py`` script.
232
227
General Comments 0
You need to be logged in to leave comments. Login now