##// END OF EJS Templates
Merge pull request #5586 from minrk/pdf-exporter...
Thomas Kluyver -
r16421:fee8aced merge
parent child Browse files
Show More
@@ -0,0 +1,36 b''
1 """Tests for PDF export"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import logging
7 import os
8
9 from IPython.testing import decorators as dec
10
11 from .base import ExportersTestsBase
12 from ..pdf import PDFExporter
13
14
15 #-----------------------------------------------------------------------------
16 # Class
17 #-----------------------------------------------------------------------------
18
19 class TestPDF(ExportersTestsBase):
20 """Test PDF export"""
21
22 exporter_class = PDFExporter
23
24 def test_constructor(self):
25 """Can a PDFExporter be constructed?"""
26 self.exporter_class()
27
28
29 @dec.onlyif_cmds_exist('pdflatex')
30 @dec.onlyif_cmds_exist('pandoc')
31 def test_export(self):
32 """Smoke test PDFExporter"""
33 (output, resources) = self.exporter_class(latex_count=1).from_filename(self._get_notebook())
34 self.assertIsInstance(output, bytes)
35 assert len(output) > 0
36
@@ -0,0 +1,2 b''
1 Creating PDFs with LaTeX no longer uses a post processor.
2 Use `nbconvert --to pdf` instead of `nbconvert --to latex --post pdf`.
@@ -1,140 +1,140 b''
1 1 import io
2 2 import os
3 3 import zipfile
4 4
5 5 from tornado import web
6 6
7 7 from ..base.handlers import IPythonHandler, notebook_path_regex
8 8 from IPython.nbformat.current import to_notebook_json
9 9
10 10 from IPython.utils import tz
11 11 from IPython.utils.py3compat import cast_bytes
12 12
13 13 import sys
14 14
15 15 def find_resource_files(output_files_dir):
16 16 files = []
17 17 for dirpath, dirnames, filenames in os.walk(output_files_dir):
18 18 files.extend([os.path.join(dirpath, f) for f in filenames])
19 19 return files
20 20
21 21 def respond_zip(handler, name, output, resources):
22 22 """Zip up the output and resource files and respond with the zip file.
23 23
24 24 Returns True if it has served a zip file, False if there are no resource
25 25 files, in which case we serve the plain output file.
26 26 """
27 27 # Check if we have resource files we need to zip
28 28 output_files = resources.get('outputs', None)
29 29 if not output_files:
30 30 return False
31 31
32 32 # Headers
33 33 zip_filename = os.path.splitext(name)[0] + '.zip'
34 34 handler.set_header('Content-Disposition',
35 35 'attachment; filename="%s"' % zip_filename)
36 36 handler.set_header('Content-Type', 'application/zip')
37 37
38 38 # Prepare the zip file
39 39 buffer = io.BytesIO()
40 40 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
41 41 output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
42 42 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
43 43 for filename, data in output_files.items():
44 44 zipf.writestr(os.path.basename(filename), data)
45 45 zipf.close()
46 46
47 47 handler.finish(buffer.getvalue())
48 48 return True
49 49
50 50 def get_exporter(format, **kwargs):
51 51 """get an exporter, raising appropriate errors"""
52 52 # if this fails, will raise 500
53 53 try:
54 54 from IPython.nbconvert.exporters.export import exporter_map
55 55 except ImportError as e:
56 56 raise web.HTTPError(500, "Could not import nbconvert: %s" % e)
57 57
58 58 try:
59 59 Exporter = exporter_map[format]
60 60 except KeyError:
61 61 # should this be 400?
62 62 raise web.HTTPError(404, u"No exporter for format: %s" % format)
63 63
64 64 try:
65 65 return Exporter(**kwargs)
66 66 except Exception as e:
67 67 raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
68 68
69 69 class NbconvertFileHandler(IPythonHandler):
70 70
71 71 SUPPORTED_METHODS = ('GET',)
72 72
73 73 @web.authenticated
74 74 def get(self, format, path='', name=None):
75 75
76 exporter = get_exporter(format, config=self.config)
76 exporter = get_exporter(format, config=self.config, log=self.log)
77 77
78 78 path = path.strip('/')
79 79 model = self.notebook_manager.get_notebook(name=name, path=path)
80 80
81 81 self.set_header('Last-Modified', model['last_modified'])
82 82
83 83 try:
84 84 output, resources = exporter.from_notebook_node(model['content'])
85 85 except Exception as e:
86 86 raise web.HTTPError(500, "nbconvert failed: %s" % e)
87 87
88 88 if respond_zip(self, name, output, resources):
89 89 return
90 90
91 91 # Force download if requested
92 92 if self.get_argument('download', 'false').lower() == 'true':
93 93 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
94 94 self.set_header('Content-Disposition',
95 95 'attachment; filename="%s"' % filename)
96 96
97 97 # MIME type
98 98 if exporter.output_mimetype:
99 99 self.set_header('Content-Type',
100 100 '%s; charset=utf-8' % exporter.output_mimetype)
101 101
102 102 self.finish(output)
103 103
104 104 class NbconvertPostHandler(IPythonHandler):
105 105 SUPPORTED_METHODS = ('POST',)
106 106
107 107 @web.authenticated
108 108 def post(self, format):
109 109 exporter = get_exporter(format, config=self.config)
110 110
111 111 model = self.get_json_body()
112 112 nbnode = to_notebook_json(model['content'])
113 113
114 114 try:
115 115 output, resources = exporter.from_notebook_node(nbnode)
116 116 except Exception as e:
117 117 raise web.HTTPError(500, "nbconvert failed: %s" % e)
118 118
119 119 if respond_zip(self, nbnode.metadata.name, output, resources):
120 120 return
121 121
122 122 # MIME type
123 123 if exporter.output_mimetype:
124 124 self.set_header('Content-Type',
125 125 '%s; charset=utf-8' % exporter.output_mimetype)
126 126
127 127 self.finish(output)
128 128
129 129 #-----------------------------------------------------------------------------
130 130 # URL to handler mappings
131 131 #-----------------------------------------------------------------------------
132 132
133 133 _format_regex = r"(?P<format>\w+)"
134 134
135 135
136 136 default_handlers = [
137 137 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
138 138 NbconvertFileHandler),
139 139 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
140 140 ] No newline at end of file
@@ -1,335 +1,335 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 4 //============================================================================
9 5 // MenuBar
10 6 //============================================================================
11 7
12 8 /**
13 9 * @module IPython
14 10 * @namespace IPython
15 11 * @submodule MenuBar
16 12 */
17 13
18 14
19 15 var IPython = (function (IPython) {
20 16 "use strict";
21 17
22 18 var utils = IPython.utils;
23 19
24 20 /**
25 21 * A MenuBar Class to generate the menubar of IPython notebook
26 22 * @Class MenuBar
27 23 *
28 24 * @constructor
29 25 *
30 26 *
31 27 * @param selector {string} selector for the menubar element in DOM
32 28 * @param {object} [options]
33 29 * @param [options.base_url] {String} String to use for the
34 30 * base project url. Default is to inspect
35 31 * $('body').data('baseUrl');
36 32 * does not support change for now is set through this option
37 33 */
38 34 var MenuBar = function (selector, options) {
39 35 options = options || {};
40 36 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
41 37 this.selector = selector;
42 38 if (this.selector !== undefined) {
43 39 this.element = $(selector);
44 40 this.style();
45 41 this.bind_events();
46 42 }
47 43 };
48 44
49 45 MenuBar.prototype.style = function () {
50 46 this.element.addClass('border-box-sizing');
51 47 this.element.find("li").click(function (event, ui) {
52 48 // The selected cell loses focus when the menu is entered, so we
53 49 // re-select it upon selection.
54 50 var i = IPython.notebook.get_selected_index();
55 51 IPython.notebook.select(i);
56 52 }
57 53 );
58 54 };
59 55
60 56 MenuBar.prototype._nbconvert = function (format, download) {
61 57 download = download || false;
62 58 var notebook_path = IPython.notebook.notebook_path;
63 59 var notebook_name = IPython.notebook.notebook_name;
64 60 if (IPython.notebook.dirty) {
65 61 IPython.notebook.save_notebook({async : false});
66 62 }
67 63 var url = utils.url_join_encode(
68 64 this.base_url,
69 65 'nbconvert',
70 66 format,
71 67 notebook_path,
72 68 notebook_name
73 69 ) + "?download=" + download.toString();
74 70
75 71 window.open(url);
76 72 };
77 73
78 74 MenuBar.prototype.bind_events = function () {
79 75 // File
80 76 var that = this;
81 77 this.element.find('#new_notebook').click(function () {
82 78 IPython.notebook.new_notebook();
83 79 });
84 80 this.element.find('#open_notebook').click(function () {
85 81 window.open(utils.url_join_encode(
86 82 IPython.notebook.base_url,
87 83 'tree',
88 84 IPython.notebook.notebook_path
89 85 ));
90 86 });
91 87 this.element.find('#copy_notebook').click(function () {
92 88 IPython.notebook.copy_notebook();
93 89 return false;
94 90 });
95 91 this.element.find('#download_ipynb').click(function () {
96 92 var base_url = IPython.notebook.base_url;
97 93 var notebook_path = IPython.notebook.notebook_path;
98 94 var notebook_name = IPython.notebook.notebook_name;
99 95 if (IPython.notebook.dirty) {
100 96 IPython.notebook.save_notebook({async : false});
101 97 }
102 98
103 99 var url = utils.url_join_encode(
104 100 base_url,
105 101 'files',
106 102 notebook_path,
107 103 notebook_name
108 104 );
109 105 window.location.assign(url);
110 106 });
111 107
112 108 this.element.find('#print_preview').click(function () {
113 109 that._nbconvert('html', false);
114 110 });
115 111
116 112 this.element.find('#download_py').click(function () {
117 113 that._nbconvert('python', true);
118 114 });
119 115
120 116 this.element.find('#download_html').click(function () {
121 117 that._nbconvert('html', true);
122 118 });
123 119
124 120 this.element.find('#download_rst').click(function () {
125 121 that._nbconvert('rst', true);
126 122 });
127 123
124 this.element.find('#download_pdf').click(function () {
125 that._nbconvert('pdf', true);
126 });
127
128 128 this.element.find('#rename_notebook').click(function () {
129 129 IPython.save_widget.rename_notebook();
130 130 });
131 131 this.element.find('#save_checkpoint').click(function () {
132 132 IPython.notebook.save_checkpoint();
133 133 });
134 134 this.element.find('#restore_checkpoint').click(function () {
135 135 });
136 136 this.element.find('#trust_notebook').click(function () {
137 137 IPython.notebook.trust_notebook();
138 138 });
139 139 $([IPython.events]).on('trust_changed.Notebook', function (event, trusted) {
140 140 if (trusted) {
141 141 that.element.find('#trust_notebook')
142 142 .addClass("disabled")
143 143 .find("a").text("Trusted Notebook");
144 144 } else {
145 145 that.element.find('#trust_notebook')
146 146 .removeClass("disabled")
147 147 .find("a").text("Trust Notebook");
148 148 }
149 149 });
150 150 this.element.find('#kill_and_exit').click(function () {
151 151 IPython.notebook.session.delete();
152 152 setTimeout(function(){
153 153 // allow closing of new tabs in Chromium, impossible in FF
154 154 window.open('', '_self', '');
155 155 window.close();
156 156 }, 500);
157 157 });
158 158 // Edit
159 159 this.element.find('#cut_cell').click(function () {
160 160 IPython.notebook.cut_cell();
161 161 });
162 162 this.element.find('#copy_cell').click(function () {
163 163 IPython.notebook.copy_cell();
164 164 });
165 165 this.element.find('#delete_cell').click(function () {
166 166 IPython.notebook.delete_cell();
167 167 });
168 168 this.element.find('#undelete_cell').click(function () {
169 169 IPython.notebook.undelete_cell();
170 170 });
171 171 this.element.find('#split_cell').click(function () {
172 172 IPython.notebook.split_cell();
173 173 });
174 174 this.element.find('#merge_cell_above').click(function () {
175 175 IPython.notebook.merge_cell_above();
176 176 });
177 177 this.element.find('#merge_cell_below').click(function () {
178 178 IPython.notebook.merge_cell_below();
179 179 });
180 180 this.element.find('#move_cell_up').click(function () {
181 181 IPython.notebook.move_cell_up();
182 182 });
183 183 this.element.find('#move_cell_down').click(function () {
184 184 IPython.notebook.move_cell_down();
185 185 });
186 186 this.element.find('#edit_nb_metadata').click(function () {
187 187 IPython.notebook.edit_metadata();
188 188 });
189 189
190 190 // View
191 191 this.element.find('#toggle_header').click(function () {
192 192 $('div#header').toggle();
193 193 IPython.layout_manager.do_resize();
194 194 });
195 195 this.element.find('#toggle_toolbar').click(function () {
196 196 $('div#maintoolbar').toggle();
197 197 IPython.layout_manager.do_resize();
198 198 });
199 199 // Insert
200 200 this.element.find('#insert_cell_above').click(function () {
201 201 IPython.notebook.insert_cell_above('code');
202 202 IPython.notebook.select_prev();
203 203 });
204 204 this.element.find('#insert_cell_below').click(function () {
205 205 IPython.notebook.insert_cell_below('code');
206 206 IPython.notebook.select_next();
207 207 });
208 208 // Cell
209 209 this.element.find('#run_cell').click(function () {
210 210 IPython.notebook.execute_cell();
211 211 });
212 212 this.element.find('#run_cell_select_below').click(function () {
213 213 IPython.notebook.execute_cell_and_select_below();
214 214 });
215 215 this.element.find('#run_cell_insert_below').click(function () {
216 216 IPython.notebook.execute_cell_and_insert_below();
217 217 });
218 218 this.element.find('#run_all_cells').click(function () {
219 219 IPython.notebook.execute_all_cells();
220 220 });
221 221 this.element.find('#run_all_cells_above').click(function () {
222 222 IPython.notebook.execute_cells_above();
223 223 });
224 224 this.element.find('#run_all_cells_below').click(function () {
225 225 IPython.notebook.execute_cells_below();
226 226 });
227 227 this.element.find('#to_code').click(function () {
228 228 IPython.notebook.to_code();
229 229 });
230 230 this.element.find('#to_markdown').click(function () {
231 231 IPython.notebook.to_markdown();
232 232 });
233 233 this.element.find('#to_raw').click(function () {
234 234 IPython.notebook.to_raw();
235 235 });
236 236 this.element.find('#to_heading1').click(function () {
237 237 IPython.notebook.to_heading(undefined, 1);
238 238 });
239 239 this.element.find('#to_heading2').click(function () {
240 240 IPython.notebook.to_heading(undefined, 2);
241 241 });
242 242 this.element.find('#to_heading3').click(function () {
243 243 IPython.notebook.to_heading(undefined, 3);
244 244 });
245 245 this.element.find('#to_heading4').click(function () {
246 246 IPython.notebook.to_heading(undefined, 4);
247 247 });
248 248 this.element.find('#to_heading5').click(function () {
249 249 IPython.notebook.to_heading(undefined, 5);
250 250 });
251 251 this.element.find('#to_heading6').click(function () {
252 252 IPython.notebook.to_heading(undefined, 6);
253 253 });
254 254
255 255 this.element.find('#toggle_current_output').click(function () {
256 256 IPython.notebook.toggle_output();
257 257 });
258 258 this.element.find('#toggle_current_output_scroll').click(function () {
259 259 IPython.notebook.toggle_output_scroll();
260 260 });
261 261 this.element.find('#clear_current_output').click(function () {
262 262 IPython.notebook.clear_output();
263 263 });
264 264
265 265 this.element.find('#toggle_all_output').click(function () {
266 266 IPython.notebook.toggle_all_output();
267 267 });
268 268 this.element.find('#toggle_all_output_scroll').click(function () {
269 269 IPython.notebook.toggle_all_output_scroll();
270 270 });
271 271 this.element.find('#clear_all_output').click(function () {
272 272 IPython.notebook.clear_all_output();
273 273 });
274 274
275 275 // Kernel
276 276 this.element.find('#int_kernel').click(function () {
277 277 IPython.notebook.session.interrupt_kernel();
278 278 });
279 279 this.element.find('#restart_kernel').click(function () {
280 280 IPython.notebook.restart_kernel();
281 281 });
282 282 // Help
283 283 this.element.find('#notebook_tour').click(function () {
284 284 IPython.tour.start();
285 285 });
286 286 this.element.find('#keyboard_shortcuts').click(function () {
287 287 IPython.quick_help.show_keyboard_shortcuts();
288 288 });
289 289
290 290 this.update_restore_checkpoint(null);
291 291
292 292 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
293 293 that.update_restore_checkpoint(IPython.notebook.checkpoints);
294 294 });
295 295
296 296 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
297 297 that.update_restore_checkpoint(IPython.notebook.checkpoints);
298 298 });
299 299 };
300 300
301 301 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
302 302 var ul = this.element.find("#restore_checkpoint").find("ul");
303 303 ul.empty();
304 304 if (!checkpoints || checkpoints.length === 0) {
305 305 ul.append(
306 306 $("<li/>")
307 307 .addClass("disabled")
308 308 .append(
309 309 $("<a/>")
310 310 .text("No checkpoints")
311 311 )
312 312 );
313 313 return;
314 314 }
315 315
316 316 checkpoints.map(function (checkpoint) {
317 317 var d = new Date(checkpoint.last_modified);
318 318 ul.append(
319 319 $("<li/>").append(
320 320 $("<a/>")
321 321 .attr("href", "#")
322 322 .text(d.format("mmm dd HH:MM:ss"))
323 323 .click(function () {
324 324 IPython.notebook.restore_checkpoint_dialog(checkpoint);
325 325 })
326 326 )
327 327 );
328 328 });
329 329 };
330 330
331 331 IPython.MenuBar = MenuBar;
332 332
333 333 return IPython;
334 334
335 335 }(IPython));
@@ -1,361 +1,362 b''
1 1 {% extends "page.html" %}
2 2
3 3 {% block stylesheet %}
4 4
5 5 {% if mathjax_url %}
6 6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 7 {% endif %}
8 8 <script type="text/javascript">
9 9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 10 // where it will be undefined, and should prompt a dialog later.
11 11 window.mathjax_url = "{{mathjax_url}}";
12 12 </script>
13 13
14 14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16 16
17 17 {{super()}}
18 18
19 19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20 20
21 21 {% endblock %}
22 22
23 23 {% block params %}
24 24
25 25 data-project="{{project}}"
26 26 data-base-url="{{base_url}}"
27 27 data-notebook-name="{{notebook_name}}"
28 28 data-notebook-path="{{notebook_path}}"
29 29 class="notebook_app"
30 30
31 31 {% endblock %}
32 32
33 33
34 34 {% block header %}
35 35
36 36 <span id="save_widget" class="nav pull-left">
37 37 <span id="notebook_name"></span>
38 38 <span id="checkpoint_status"></span>
39 39 <span id="autosave_status"></span>
40 40 </span>
41 41
42 42 {% endblock %}
43 43
44 44
45 45 {% block site %}
46 46
47 47 <div id="menubar-container" class="container">
48 48 <div id="menubar">
49 49 <div class="navbar">
50 50 <div class="navbar-inner">
51 51 <div class="container">
52 52 <ul id="menus" class="nav">
53 53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 54 <ul id="file_menu" class="dropdown-menu">
55 55 <li id="new_notebook"
56 56 title="Make a new notebook (Opens a new window)">
57 57 <a href="#">New</a></li>
58 58 <li id="open_notebook"
59 59 title="Opens a new window with the Dashboard view">
60 60 <a href="#">Open...</a></li>
61 61 <!-- <hr/> -->
62 62 <li class="divider"></li>
63 63 <li id="copy_notebook"
64 64 title="Open a copy of this notebook's contents and start a new kernel">
65 65 <a href="#">Make a Copy...</a></li>
66 66 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 68 <!-- <hr/> -->
69 69 <li class="divider"></li>
70 70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 71 <ul class="dropdown-menu">
72 72 <li><a href="#"></a></li>
73 73 <li><a href="#"></a></li>
74 74 <li><a href="#"></a></li>
75 75 <li><a href="#"></a></li>
76 76 <li><a href="#"></a></li>
77 77 </ul>
78 78 </li>
79 79 <li class="divider"></li>
80 80 <li id="print_preview"><a href="#">Print Preview</a></li>
81 81 <li class="dropdown-submenu"><a href="#">Download as</a>
82 82 <ul class="dropdown-menu">
83 83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
84 84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
87 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
87 88 </ul>
88 89 </li>
89 90 <li class="divider"></li>
90 91 <li id="trust_notebook"
91 92 title="Trust the output of this notebook">
92 93 <a href="#" >Trust Notebook</a></li>
93 94 <li class="divider"></li>
94 95 <li id="kill_and_exit"
95 96 title="Shutdown this notebook's kernel, and close this window">
96 97 <a href="#" >Close and halt</a></li>
97 98 </ul>
98 99 </li>
99 100 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
100 101 <ul id="edit_menu" class="dropdown-menu">
101 102 <li id="cut_cell"><a href="#">Cut Cell</a></li>
102 103 <li id="copy_cell"><a href="#">Copy Cell</a></li>
103 104 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
104 105 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
105 106 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
106 107 <li id="delete_cell"><a href="#">Delete Cell</a></li>
107 108 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
108 109 <li class="divider"></li>
109 110 <li id="split_cell"><a href="#">Split Cell</a></li>
110 111 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
111 112 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
112 113 <li class="divider"></li>
113 114 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
114 115 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
115 116 <li class="divider"></li>
116 117 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 118 </ul>
118 119 </li>
119 120 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 121 <ul id="view_menu" class="dropdown-menu">
121 122 <li id="toggle_header"
122 123 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 124 <a href="#">Toggle Header</a></li>
124 125 <li id="toggle_toolbar"
125 126 title="Show/Hide the action icons (below menu bar)">
126 127 <a href="#">Toggle Toolbar</a></li>
127 128 </ul>
128 129 </li>
129 130 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 131 <ul id="insert_menu" class="dropdown-menu">
131 132 <li id="insert_cell_above"
132 133 title="Insert an empty Code cell above the currently active cell">
133 134 <a href="#">Insert Cell Above</a></li>
134 135 <li id="insert_cell_below"
135 136 title="Insert an empty Code cell below the currently active cell">
136 137 <a href="#">Insert Cell Below</a></li>
137 138 </ul>
138 139 </li>
139 140 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 141 <ul id="cell_menu" class="dropdown-menu">
141 142 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 143 <a href="#">Run</a></li>
143 144 <li id="run_cell_select_below" title="Run this cell, select below">
144 145 <a href="#">Run and Select Below</a></li>
145 146 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 147 <a href="#">Run and Insert Below</a></li>
147 148 <li id="run_all_cells" title="Run all cells in the notebook">
148 149 <a href="#">Run All</a></li>
149 150 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 151 <a href="#">Run All Above</a></li>
151 152 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 153 <a href="#">Run All Below</a></li>
153 154 <li class="divider"></li>
154 155 <li id="change_cell_type" class="dropdown-submenu"
155 156 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 157 <a href="#">Cell Type</a>
157 158 <ul class="dropdown-menu">
158 159 <li id="to_code"
159 160 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
160 161 <a href="#">Code</a></li>
161 162 <li id="to_markdown"
162 163 title="Contents will be rendered as HTML and serve as explanatory text">
163 164 <a href="#">Markdown</a></li>
164 165 <li id="to_raw"
165 166 title="Contents will pass through nbconvert unmodified">
166 167 <a href="#">Raw NBConvert</a></li>
167 168 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 169 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 170 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 171 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 172 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 173 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 174 </ul>
174 175 </li>
175 176 <li class="divider"></li>
176 177 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
177 178 <ul class="dropdown-menu">
178 179 <li id="toggle_current_output"
179 180 title="Hide/Show the output of the current cell">
180 181 <a href="#">Toggle</a>
181 182 </li>
182 183 <li id="toggle_current_output_scroll"
183 184 title="Scroll the output of the current cell">
184 185 <a href="#">Toggle Scrolling</a>
185 186 </li>
186 187 <li id="clear_current_output"
187 188 title="Clear the output of the current cell">
188 189 <a href="#">Clear</a>
189 190 </li>
190 191 </ul>
191 192 </li>
192 193 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
193 194 <ul class="dropdown-menu">
194 195 <li id="toggle_all_output"
195 196 title="Hide/Show the output of all cells">
196 197 <a href="#">Toggle</a>
197 198 </li>
198 199 <li id="toggle_all_output_scroll"
199 200 title="Scroll the output of all cells">
200 201 <a href="#">Toggle Scrolling</a>
201 202 </li>
202 203 <li id="clear_all_output"
203 204 title="Clear the output of all cells">
204 205 <a href="#">Clear</a>
205 206 </li>
206 207 </ul>
207 208 </li>
208 209 </ul>
209 210 </li>
210 211 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
211 212 <ul id="kernel_menu" class="dropdown-menu">
212 213 <li id="int_kernel"
213 214 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
214 215 <a href="#">Interrupt</a></li>
215 216 <li id="restart_kernel"
216 217 title="Restart the Kernel">
217 218 <a href="#">Restart</a></li>
218 219 </ul>
219 220 </li>
220 221 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
221 222 <ul id="help_menu" class="dropdown-menu">
222 223 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
223 224 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
224 225 <li class="divider"></li>
225 226 {% set
226 227 sections = (
227 228 (
228 229 ("http://ipython.org/documentation.html","IPython Help",True),
229 230 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
230 231 ),(
231 232 ("http://docs.python.org","Python",True),
232 233 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
233 234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 236 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 237 ("http://docs.sympy.org/latest/index.html","SymPy",True),
237 238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 239 )
239 240 )
240 241 %}
241 242
242 243 {% for helplinks in sections %}
243 244 {% for link in helplinks %}
244 245 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
245 246 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
246 247 {{link[1]}}
247 248 </a></li>
248 249 {% endfor %}
249 250 {% if not loop.last %}
250 251 <li class="divider"></li>
251 252 {% endif %}
252 253 {% endfor %}
253 254 </li>
254 255 </ul>
255 256 </li>
256 257 </ul>
257 258 <div id="kernel_indicator" class="indicator_area pull-right">
258 259 <i id="kernel_indicator_icon"></i>
259 260 </div>
260 261 <div id="modal_indicator" class="indicator_area pull-right">
261 262 <i id="modal_indicator_icon"></i>
262 263 </div>
263 264 <div id="notification_area"></div>
264 265 </div>
265 266 </div>
266 267 </div>
267 268 </div>
268 269 <div id="maintoolbar" class="navbar">
269 270 <div class="toolbar-inner navbar-inner navbar-nobg">
270 271 <div id="maintoolbar-container" class="container"></div>
271 272 </div>
272 273 </div>
273 274 </div>
274 275
275 276 <div id="ipython-main-app">
276 277
277 278 <div id="notebook_panel">
278 279 <div id="notebook"></div>
279 280 <div id="pager_splitter"></div>
280 281 <div id="pager">
281 282 <div id='pager_button_area'>
282 283 </div>
283 284 <div id="pager-container" class="container"></div>
284 285 </div>
285 286 </div>
286 287
287 288 </div>
288 289 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
289 290
290 291
291 292 {% endblock %}
292 293
293 294
294 295 {% block script %}
295 296
296 297 {{super()}}
297 298
298 299 <script src="{{ static_url("components/google-caja/html-css-sanitizer-minified.js") }}" charset="utf-8"></script>
299 300 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
300 301 <script type="text/javascript">
301 302 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
302 303 </script>
303 304 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
304 305 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
305 306 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
306 307 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
307 308 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
308 309 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
309 310 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
310 311 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
311 312 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
312 313 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
313 314 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
314 315 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
315 316 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
316 317 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
317 318 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
318 319
319 320 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
320 321
321 322 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
322 323
323 324 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
324 325 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
325 326 <script src="{{ static_url("base/js/keyboard.js") }}" type="text/javascript" charset="utf-8"></script>
326 327 <script src="{{ static_url("base/js/security.js") }}" type="text/javascript" charset="utf-8"></script>
327 328 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
328 329 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
329 330 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
330 331 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
331 332 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
332 333 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
333 334 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
334 335 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
335 336 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
336 337 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
337 338 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
338 339 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
339 340 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
340 341 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
341 342 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
342 343 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
343 344 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
344 345 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
345 346 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
346 347 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
347 348 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
348 349 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
349 350 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
350 351 <script src="{{ static_url("notebook/js/tour.js") }}" type="text/javascript" charset="utf-8"></script>
351 352
352 353 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
353 354 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
354 355
355 356 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
356 357
357 358 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
358 359 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
359 360 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
360 361
361 362 {% endblock %}
@@ -1,9 +1,10 b''
1 1 from .export import *
2 2 from .html import HTMLExporter
3 3 from .slides import SlidesExporter
4 4 from .templateexporter import TemplateExporter
5 5 from .latex import LatexExporter
6 6 from .markdown import MarkdownExporter
7 from .pdf import PDFExporter
7 8 from .python import PythonExporter
8 9 from .rst import RSTExporter
9 10 from .exporter import Exporter
@@ -1,179 +1,172 b''
1 """
2 Module containing single call export functions.
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
6 #
7 # Distributed under the terms of the Modified BSD License.
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
1 """Module containing single call export functions."""
11 2
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
15 5
16 6 from functools import wraps
17 7
18 8 from IPython.nbformat.v3.nbbase import NotebookNode
19 9 from IPython.utils.decorators import undoc
20 10 from IPython.utils.py3compat import string_types
21 11
22 12 from .exporter import Exporter
23 13 from .templateexporter import TemplateExporter
24 14 from .html import HTMLExporter
25 15 from .slides import SlidesExporter
26 16 from .latex import LatexExporter
17 from .pdf import PDFExporter
27 18 from .markdown import MarkdownExporter
28 19 from .python import PythonExporter
29 20 from .rst import RSTExporter
30 21
31 22 #-----------------------------------------------------------------------------
32 23 # Classes
33 24 #-----------------------------------------------------------------------------
34 25
35 26 @undoc
36 27 def DocDecorator(f):
37 28
38 29 #Set docstring of function
39 30 f.__doc__ = f.__doc__ + """
40 31 nb : :class:`~{nbnode_mod}.NotebookNode`
41 32 The notebook to export.
42 33 config : config (optional, keyword arg)
43 34 User configuration instance.
44 35 resources : dict (optional, keyword arg)
45 36 Resources used in the conversion process.
46 37
47 38 Returns
48 39 -------
49 40 tuple- output, resources, exporter_instance
50 41 output : str
51 42 Jinja 2 output. This is the resulting converted notebook.
52 43 resources : dictionary
53 44 Dictionary of resources used prior to and during the conversion
54 45 process.
55 46 exporter_instance : Exporter
56 47 Instance of the Exporter class used to export the document. Useful
57 48 to caller because it provides a 'file_extension' property which
58 49 specifies what extension the output should be saved as.
59 50
60 51 Notes
61 52 -----
62 53 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT
63 54 """.format(nbnode_mod=NotebookNode.__module__)
64 55
65 56 @wraps(f)
66 57 def decorator(*args, **kwargs):
67 58 return f(*args, **kwargs)
68 59
69 60 return decorator
70 61
71 62
72 63 #-----------------------------------------------------------------------------
73 64 # Functions
74 65 #-----------------------------------------------------------------------------
75 66
76 67 __all__ = [
77 68 'export',
78 69 'export_html',
79 70 'export_custom',
80 71 'export_slides',
81 72 'export_latex',
73 'export_pdf',
82 74 'export_markdown',
83 75 'export_python',
84 76 'export_rst',
85 77 'export_by_name',
86 78 'get_export_names',
87 79 'ExporterNameError'
88 80 ]
89 81
90 82
91 83 class ExporterNameError(NameError):
92 84 pass
93 85
94 86 @DocDecorator
95 87 def export(exporter, nb, **kw):
96 88 """
97 89 Export a notebook object using specific exporter class.
98 90
99 91 Parameters
100 92 ----------
101 93 exporter : class:`~IPython.nbconvert.exporters.exporter.Exporter` class or instance
102 94 Class type or instance of the exporter that should be used. If the
103 95 method initializes it's own instance of the class, it is ASSUMED that
104 96 the class type provided exposes a constructor (``__init__``) with the same
105 97 signature as the base Exporter class.
106 98 """
107 99
108 100 #Check arguments
109 101 if exporter is None:
110 102 raise TypeError("Exporter is None")
111 103 elif not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
112 104 raise TypeError("exporter does not inherit from Exporter (base)")
113 105 if nb is None:
114 106 raise TypeError("nb is None")
115 107
116 108 #Create the exporter
117 109 resources = kw.pop('resources', None)
118 110 if isinstance(exporter, Exporter):
119 111 exporter_instance = exporter
120 112 else:
121 113 exporter_instance = exporter(**kw)
122 114
123 115 #Try to convert the notebook using the appropriate conversion function.
124 116 if isinstance(nb, NotebookNode):
125 117 output, resources = exporter_instance.from_notebook_node(nb, resources)
126 118 elif isinstance(nb, string_types):
127 119 output, resources = exporter_instance.from_filename(nb, resources)
128 120 else:
129 121 output, resources = exporter_instance.from_file(nb, resources)
130 122 return output, resources
131 123
132 124 exporter_map = dict(
133 125 custom=TemplateExporter,
134 126 html=HTMLExporter,
135 127 slides=SlidesExporter,
136 128 latex=LatexExporter,
129 pdf=PDFExporter,
137 130 markdown=MarkdownExporter,
138 131 python=PythonExporter,
139 132 rst=RSTExporter,
140 133 )
141 134
142 135 def _make_exporter(name, E):
143 136 """make an export_foo function from a short key and Exporter class E"""
144 137 def _export(nb, **kw):
145 138 return export(E, nb, **kw)
146 139 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
147 140 return _export
148 141
149 142 g = globals()
150 143
151 144 for name, E in exporter_map.items():
152 145 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
153 146
154 147 @DocDecorator
155 148 def export_by_name(format_name, nb, **kw):
156 149 """
157 150 Export a notebook object to a template type by its name. Reflection
158 151 (Inspect) is used to find the template's corresponding explicit export
159 152 method defined in this module. That method is then called directly.
160 153
161 154 Parameters
162 155 ----------
163 156 format_name : str
164 157 Name of the template style to export to.
165 158 """
166 159
167 160 function_name = "export_" + format_name.lower()
168 161
169 162 if function_name in globals():
170 163 return globals()[function_name](nb, **kw)
171 164 else:
172 165 raise ExporterNameError("template for `%s` not found" % function_name)
173 166
174 167
175 168 def get_export_names():
176 169 """Return a list of the currently supported export targets
177 170
178 171 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
179 172 return sorted(exporter_map.keys())
@@ -1,156 +1,141 b''
1 """
2 Contains writer for writing nbconvert output to PDF.
3 """
4 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
6 #
7 #Distributed under the terms of the Modified BSD License.
8 #
9 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
1 """Export to PDF via latex"""
11 2
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
15 5
16 6 import subprocess
17 7 import os
18 8 import sys
19 9
20 from IPython.utils.traitlets import Integer, List, Bool
10 from IPython.utils.traitlets import Integer, List, Bool, Instance
11 from IPython.utils.tempdir import TemporaryWorkingDirectory
12 from .latex import LatexExporter
21 13
22 from .base import PostProcessorBase
23 14
24 #-----------------------------------------------------------------------------
25 # Classes
26 #-----------------------------------------------------------------------------
27 class PDFPostProcessor(PostProcessorBase):
15 class PDFExporter(LatexExporter):
28 16 """Writer designed to write to PDF files"""
29 17
30 latex_count = Integer(3, config=True, help="""
31 How many times pdflatex will be called.
32 """)
18 latex_count = Integer(3, config=True,
19 help="How many times latex will be called."
20 )
21
22 latex_command = List([u"pdflatex", u"{filename}"], config=True,
23 help="Shell command used to compile latex."
24 )
33 25
34 latex_command = List([u"pdflatex", u"{filename}"], config=True, help="""
35 Shell command used to compile PDF.""")
26 bib_command = List([u"bibtex", u"{filename}"], config=True,
27 help="Shell command used to run bibtex."
28 )
36 29
37 bib_command = List([u"bibtex", u"{filename}"], config=True, help="""
38 Shell command used to run bibtex.""")
30 verbose = Bool(False, config=True,
31 help="Whether to display the output of latex commands."
32 )
39 33
40 verbose = Bool(False, config=True, help="""
41 Whether or not to display the output of the compile call.
42 """)
34 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
35 help="File extensions of temp files to remove after running."
36 )
43 37
44 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'],
45 config=True, help="""
46 Filename extensions of temp files to remove after running.
47 """)
48 pdf_open = Bool(False, config=True, help="""
49 Whether or not to open the pdf after the compile call.
50 """)
38 writer = Instance("IPython.nbconvert.writers.FilesWriter", args=())
51 39
52 40 def run_command(self, command_list, filename, count, log_function):
53 41 """Run command_list count times.
54 42
55 43 Parameters
56 44 ----------
57 45 command_list : list
58 46 A list of args to provide to Popen. Each element of this
59 47 list will be interpolated with the filename to convert.
60 48 filename : unicode
61 49 The name of the file to convert.
62 50 count : int
63 51 How many times to run the command.
64 52
65 53 Returns
66 54 -------
67 continue : bool
55 success : bool
68 56 A boolean indicating if the command was successful (True)
69 57 or failed (False).
70 58 """
71 59 command = [c.format(filename=filename) for c in command_list]
72 60 #In windows and python 2.x there is a bug in subprocess.Popen and
73 61 # unicode commands are not supported
74 62 if sys.platform == 'win32' and sys.version_info < (3,0):
75 63 #We must use cp1252 encoding for calling subprocess.Popen
76 64 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
77 65 # could be different (cp437 in case of dos console)
78 66 command = [c.encode('cp1252') for c in command]
79 67 times = 'time' if count == 1 else 'times'
80 68 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
81 69 with open(os.devnull, 'rb') as null:
82 70 stdout = subprocess.PIPE if not self.verbose else None
83 71 for index in range(count):
84 72 p = subprocess.Popen(command, stdout=stdout, stdin=null)
85 73 out, err = p.communicate()
86 74 if p.returncode:
87 75 if self.verbose:
88 76 # verbose means I didn't capture stdout with PIPE,
89 77 # so it's already been displayed and `out` is None.
90 78 out = u''
91 79 else:
92 80 out = out.decode('utf-8', 'replace')
93 81 log_function(command, out)
94 82 return False # failure
95 83 return True # success
96 84
97 85 def run_latex(self, filename):
98 86 """Run pdflatex self.latex_count times."""
99 87
100 88 def log_error(command, out):
101 89 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
102 90
103 91 return self.run_command(self.latex_command, filename,
104 92 self.latex_count, log_error)
105 93
106 94 def run_bib(self, filename):
107 95 """Run bibtex self.latex_count times."""
108 96 filename = os.path.splitext(filename)[0]
109 97
110 98 def log_error(command, out):
111 99 self.log.warn('%s had problems, most likely because there were no citations',
112 100 command[0])
113 101 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
114 102
115 103 return self.run_command(self.bib_command, filename, 1, log_error)
116 104
117 105 def clean_temp_files(self, filename):
118 106 """Remove temporary files created by pdflatex/bibtex."""
119 107 self.log.info("Removing temporary LaTeX files")
120 108 filename = os.path.splitext(filename)[0]
121 109 for ext in self.temp_file_exts:
122 110 try:
123 111 os.remove(filename+ext)
124 112 except OSError:
125 113 pass
126 114
127 def open_pdf(self, filename):
128 """Open the pdf in the default viewer."""
129 if sys.platform.startswith('darwin'):
130 subprocess.call(('open', filename))
131 elif os.name == 'nt':
132 os.startfile(filename)
133 elif os.name == 'posix':
134 subprocess.call(('xdg-open', filename))
135 return
136
137 def postprocess(self, filename):
138 """Build a PDF by running pdflatex and bibtex"""
115 def from_notebook_node(self, nb, resources=None, **kw):
116 latex, resources = super(PDFExporter, self).from_notebook_node(
117 nb, resources=resources, **kw
118 )
119 with TemporaryWorkingDirectory() as td:
120 notebook_name = "notebook"
121 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
139 122 self.log.info("Building PDF")
140 cont = self.run_latex(filename)
141 if cont:
142 cont = self.run_bib(filename)
143 else:
144 self.clean_temp_files(filename)
145 return
146 if cont:
147 cont = self.run_latex(filename)
148 self.clean_temp_files(filename)
149 filename = os.path.splitext(filename)[0]
150 if os.path.isfile(filename+'.pdf'):
123 rc = self.run_latex(tex_file)
124 if not rc:
125 rc = self.run_bib(tex_file)
126 if not rc:
127 rc = self.run_latex(tex_file)
128
129 pdf_file = notebook_name + '.pdf'
130 if not os.path.isfile(pdf_file):
131 raise RuntimeError("PDF creating failed")
151 132 self.log.info('PDF successfully created')
152 if self.pdf_open:
153 self.log.info('Viewer called')
154 self.open_pdf(filename+'.pdf')
155 return
133 with open(pdf_file, 'rb') as f:
134 pdf_data = f.read()
135
136 # convert output extension to pdf
137 # the writer above required it to be tex
138 resources['output_extension'] = 'pdf'
139
140 return pdf_data, resources
156 141
@@ -1,323 +1,312 b''
1 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 4 Command-line interface for the NbConvert conversion utility.
5 5 """
6 #-----------------------------------------------------------------------------
7 #Copyright (c) 2013, the IPython Development Team.
8 #
9 #Distributed under the terms of the Modified BSD License.
10 #
11 #The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
13 6
14 #-----------------------------------------------------------------------------
15 #Imports
16 #-----------------------------------------------------------------------------
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
17 9
18 # Stdlib imports
19 10 from __future__ import print_function
20 11
21 12 import logging
22 13 import sys
23 14 import os
24 15 import glob
25 16
26 # From IPython
27 17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
28 18 from IPython.core.profiledir import ProfileDir
29 19 from IPython.config import catch_config_error, Configurable
30 20 from IPython.utils.traitlets import (
31 21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
32 22 )
33 23 from IPython.utils.importstring import import_item
34 24 from IPython.utils.text import dedent
35 25
36 26 from .exporters.export import get_export_names, exporter_map
37 27 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
38 28 from .utils.base import NbConvertBase
39 29 from .utils.exceptions import ConversionException
40 30
41 31 #-----------------------------------------------------------------------------
42 32 #Classes and functions
43 33 #-----------------------------------------------------------------------------
44 34
45 35 class DottedOrNone(DottedObjectName):
46 36 """
47 37 A string holding a valid dotted object name in Python, such as A.b3._c
48 38 Also allows for None type."""
49 39
50 40 default_value = u''
51 41
52 42 def validate(self, obj, value):
53 43 if value is not None and len(value) > 0:
54 44 return super(DottedOrNone, self).validate(obj, value)
55 45 else:
56 46 return value
57 47
58 48 nbconvert_aliases = {}
59 49 nbconvert_aliases.update(base_aliases)
60 50 nbconvert_aliases.update({
61 51 'to' : 'NbConvertApp.export_format',
62 52 'template' : 'TemplateExporter.template_file',
63 53 'writer' : 'NbConvertApp.writer_class',
64 54 'post': 'NbConvertApp.postprocessor_class',
65 55 'output': 'NbConvertApp.output_base',
66 56 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
67 57 })
68 58
69 59 nbconvert_flags = {}
70 60 nbconvert_flags.update(base_flags)
71 61 nbconvert_flags.update({
72 62 'stdout' : (
73 63 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
74 64 "Write notebook output to stdout instead of files."
75 65 )
76 66 })
77 67
78 68
79 69 class NbConvertApp(BaseIPythonApplication):
80 70 """Application used to convert from notebook file type (``*.ipynb``)"""
81 71
82 72 name = 'ipython-nbconvert'
83 73 aliases = nbconvert_aliases
84 74 flags = nbconvert_flags
85 75
86 76 def _log_level_default(self):
87 77 return logging.INFO
88 78
89 79 def _classes_default(self):
90 80 classes = [NbConvertBase, ProfileDir]
91 81 for pkg in (exporters, preprocessors, writers, postprocessors):
92 82 for name in dir(pkg):
93 83 cls = getattr(pkg, name)
94 84 if isinstance(cls, type) and issubclass(cls, Configurable):
95 85 classes.append(cls)
96 86
97 87 return classes
98 88
99 89 description = Unicode(
100 90 u"""This application is used to convert notebook files (*.ipynb)
101 91 to various other formats.
102 92
103 93 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
104 94
105 95 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
106 96 can only be use when converting one notebook at a time.
107 97 ''')
108 98
109 99 examples = Unicode(u"""
110 100 The simplest way to use nbconvert is
111 101
112 102 > ipython nbconvert mynotebook.ipynb
113 103
114 104 which will convert mynotebook.ipynb to the default format (probably HTML).
115 105
116 106 You can specify the export format with `--to`.
117 107 Options include {0}
118 108
119 109 > ipython nbconvert --to latex mynotebook.ipnynb
120 110
121 111 Both HTML and LaTeX support multiple output templates. LaTeX includes
122 112 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
123 113 can specify the flavor of the format used.
124 114
125 115 > ipython nbconvert --to html --template basic mynotebook.ipynb
126 116
127 117 You can also pipe the output to stdout, rather than a file
128 118
129 119 > ipython nbconvert mynotebook.ipynb --stdout
130 120
131 A post-processor can be used to compile a PDF
121 PDF is generated via latex
132 122
133 > ipython nbconvert mynotebook.ipynb --to latex --post PDF
123 > ipython nbconvert mynotebook.ipynb --to pdf
134 124
135 125 You can get (and serve) a Reveal.js-powered slideshow
136 126
137 127 > ipython nbconvert myslides.ipynb --to slides --post serve
138 128
139 129 Multiple notebooks can be given at the command line in a couple of
140 130 different ways:
141 131
142 132 > ipython nbconvert notebook*.ipynb
143 133 > ipython nbconvert notebook1.ipynb notebook2.ipynb
144 134
145 135 or you can specify the notebooks list in a config file, containing::
146 136
147 137 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
148 138
149 139 > ipython nbconvert --config mycfg.py
150 140 """.format(get_export_names()))
151 141
152 142 # Writer specific variables
153 143 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
154 144 help="""Instance of the writer class used to write the
155 145 results of the conversion.""")
156 146 writer_class = DottedObjectName('FilesWriter', config=True,
157 147 help="""Writer class used to write the
158 148 results of the conversion""")
159 149 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
160 150 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
161 151 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
162 152 writer_factory = Type()
163 153
164 154 def _writer_class_changed(self, name, old, new):
165 155 if new.lower() in self.writer_aliases:
166 156 new = self.writer_aliases[new.lower()]
167 157 self.writer_factory = import_item(new)
168 158
169 159 # Post-processor specific variables
170 160 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
171 161 help="""Instance of the PostProcessor class used to write the
172 162 results of the conversion.""")
173 163
174 164 postprocessor_class = DottedOrNone(config=True,
175 165 help="""PostProcessor class used to write the
176 166 results of the conversion""")
177 postprocessor_aliases = {'pdf': 'IPython.nbconvert.postprocessors.pdf.PDFPostProcessor',
178 'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
167 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
179 168 postprocessor_factory = Type()
180 169
181 170 def _postprocessor_class_changed(self, name, old, new):
182 171 if new.lower() in self.postprocessor_aliases:
183 172 new = self.postprocessor_aliases[new.lower()]
184 173 if new:
185 174 self.postprocessor_factory = import_item(new)
186 175
187 176
188 177 # Other configurable variables
189 178 export_format = CaselessStrEnum(get_export_names(),
190 179 default_value="html",
191 180 config=True,
192 181 help="""The export format to be used."""
193 182 )
194 183
195 184 notebooks = List([], config=True, help="""List of notebooks to convert.
196 185 Wildcards are supported.
197 186 Filenames passed positionally will be added to the list.
198 187 """)
199 188
200 189 @catch_config_error
201 190 def initialize(self, argv=None):
202 191 self.init_syspath()
203 192 super(NbConvertApp, self).initialize(argv)
204 193 self.init_notebooks()
205 194 self.init_writer()
206 195 self.init_postprocessor()
207 196
208 197
209 198
210 199 def init_syspath(self):
211 200 """
212 201 Add the cwd to the sys.path ($PYTHONPATH)
213 202 """
214 203 sys.path.insert(0, os.getcwd())
215 204
216 205
217 206 def init_notebooks(self):
218 207 """Construct the list of notebooks.
219 208 If notebooks are passed on the command-line,
220 209 they override notebooks specified in config files.
221 210 Glob each notebook to replace notebook patterns with filenames.
222 211 """
223 212
224 213 # Specifying notebooks on the command-line overrides (rather than adds)
225 214 # the notebook list
226 215 if self.extra_args:
227 216 patterns = self.extra_args
228 217 else:
229 218 patterns = self.notebooks
230 219
231 220 # Use glob to replace all the notebook patterns with filenames.
232 221 filenames = []
233 222 for pattern in patterns:
234 223
235 224 # Use glob to find matching filenames. Allow the user to convert
236 225 # notebooks without having to type the extension.
237 226 globbed_files = glob.glob(pattern)
238 227 globbed_files.extend(glob.glob(pattern + '.ipynb'))
239 228 if not globbed_files:
240 229 self.log.warn("pattern %r matched no files", pattern)
241 230
242 231 for filename in globbed_files:
243 232 if not filename in filenames:
244 233 filenames.append(filename)
245 234 self.notebooks = filenames
246 235
247 236 def init_writer(self):
248 237 """
249 238 Initialize the writer (which is stateless)
250 239 """
251 240 self._writer_class_changed(None, self.writer_class, self.writer_class)
252 241 self.writer = self.writer_factory(parent=self)
253 242
254 243 def init_postprocessor(self):
255 244 """
256 245 Initialize the postprocessor (which is stateless)
257 246 """
258 247 self._postprocessor_class_changed(None, self.postprocessor_class,
259 248 self.postprocessor_class)
260 249 if self.postprocessor_factory:
261 250 self.postprocessor = self.postprocessor_factory(parent=self)
262 251
263 252 def start(self):
264 253 """
265 254 Ran after initialization completed
266 255 """
267 256 super(NbConvertApp, self).start()
268 257 self.convert_notebooks()
269 258
270 259 def convert_notebooks(self):
271 260 """
272 261 Convert the notebooks in the self.notebook traitlet
273 262 """
274 263 # Export each notebook
275 264 conversion_success = 0
276 265
277 266 if self.output_base != '' and len(self.notebooks) > 1:
278 267 self.log.error(
279 268 """UsageError: --output flag or `NbConvertApp.output_base` config option
280 269 cannot be used when converting multiple notebooks.
281 270 """)
282 271 self.exit(1)
283 272
284 273 exporter = exporter_map[self.export_format](config=self.config)
285 274
286 275 for notebook_filename in self.notebooks:
287 276 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
288 277
289 278 # Get a unique key for the notebook and set it in the resources object.
290 279 basename = os.path.basename(notebook_filename)
291 280 notebook_name = basename[:basename.rfind('.')]
292 281 if self.output_base:
293 282 notebook_name = self.output_base
294 283 resources = {}
295 284 resources['unique_key'] = notebook_name
296 285 resources['output_files_dir'] = '%s_files' % notebook_name
297 286 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
298 287
299 288 # Try to export
300 289 try:
301 290 output, resources = exporter.from_filename(notebook_filename, resources=resources)
302 291 except ConversionException as e:
303 292 self.log.error("Error while converting '%s'", notebook_filename,
304 293 exc_info=True)
305 294 self.exit(1)
306 295 else:
307 296 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
308 297
309 298 #Post-process if post processor has been defined.
310 299 if hasattr(self, 'postprocessor') and self.postprocessor:
311 300 self.postprocessor(write_resultes)
312 301 conversion_success += 1
313 302
314 303 # If nothing was converted successfully, help the user.
315 304 if conversion_success == 0:
316 305 self.print_help()
317 306 sys.exit(-1)
318 307
319 308 #-----------------------------------------------------------------------------
320 309 # Main entry point
321 310 #-----------------------------------------------------------------------------
322 311
323 312 launch_new_instance = NbConvertApp.launch_instance
@@ -1,8 +1,7 b''
1 1 from .base import PostProcessorBase
2 from .pdf import PDFPostProcessor
3 2
4 3 # protect against unavailable tornado
5 4 try:
6 5 from .serve import ServePostProcessor
7 6 except ImportError:
8 7 pass
@@ -1,211 +1,202 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Test NbConvertApp"""
3 3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
14 6
15 7 import os
16 8 import glob
17 9 import sys
18 10
19 11 from .base import TestsBase
12 from ..postprocessors import PostProcessorBase
20 13
21 14 import IPython.testing.tools as tt
22 15 from IPython.testing import decorators as dec
23 16
24 17 #-----------------------------------------------------------------------------
25 18 # Classes and functions
26 19 #-----------------------------------------------------------------------------
27 20
21 class DummyPost(PostProcessorBase):
22 def postprocess(self, filename):
23 print("Dummy:%s" % filename)
24
28 25 class TestNbConvertApp(TestsBase):
29 26 """Collection of NbConvertApp tests"""
30 27
31 28
32 29 def test_notebook_help(self):
33 30 """Will help show if no notebooks are specified?"""
34 31 with self.create_temp_cwd():
35 32 out, err = self.call('nbconvert --log-level 0', ignore_return_code=True)
36 33 self.assertIn("see '--help-all'", out)
37 34
38 35 def test_help_output(self):
39 36 """ipython nbconvert --help-all works"""
40 37 tt.help_all_output_test('nbconvert')
41 38
42 39 def test_glob(self):
43 40 """
44 41 Do search patterns work for notebook names?
45 42 """
46 43 with self.create_temp_cwd(['notebook*.ipynb']):
47 44 self.call('nbconvert --to python *.ipynb --log-level 0')
48 45 assert os.path.isfile('notebook1.py')
49 46 assert os.path.isfile('notebook2.py')
50 47
51 48
52 49 def test_glob_subdir(self):
53 50 """
54 51 Do search patterns work for subdirectory notebook names?
55 52 """
56 53 with self.create_temp_cwd():
57 54 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
58 55 self.call('nbconvert --to python --log-level 0 ' +
59 56 os.path.join('subdir', '*.ipynb'))
60 57 assert os.path.isfile('notebook1.py')
61 58 assert os.path.isfile('notebook2.py')
62 59
63 60
64 61 def test_explicit(self):
65 62 """
66 63 Do explicit notebook names work?
67 64 """
68 65 with self.create_temp_cwd(['notebook*.ipynb']):
69 66 self.call('nbconvert --log-level 0 --to python notebook2')
70 67 assert not os.path.isfile('notebook1.py')
71 68 assert os.path.isfile('notebook2.py')
72 69
73 70
74 71 @dec.onlyif_cmds_exist('pdflatex')
75 72 @dec.onlyif_cmds_exist('pandoc')
76 73 def test_filename_spaces(self):
77 74 """
78 75 Generate PDFs with graphics if notebooks have spaces in the name?
79 76 """
80 77 with self.create_temp_cwd(['notebook2.ipynb']):
81 78 os.rename('notebook2.ipynb', 'notebook with spaces.ipynb')
82 self.call('nbconvert --log-level 0 --to latex '
83 '"notebook with spaces" --post PDF '
84 '--PDFPostProcessor.verbose=True')
85 assert os.path.isfile('notebook with spaces.tex')
86 assert os.path.isdir('notebook with spaces_files')
79 self.call('nbconvert --log-level 0 --to pdf'
80 ' "notebook with spaces"'
81 ' --PDFExporter.latex_count=1'
82 ' --PDFExporter.verbose=True'
83 )
87 84 assert os.path.isfile('notebook with spaces.pdf')
88 85
89 @dec.onlyif_cmds_exist('pdflatex')
90 @dec.onlyif_cmds_exist('pandoc')
91 86 def test_post_processor(self):
92 """
93 Do post processors work?
94 """
87 """Do post processors work?"""
95 88 with self.create_temp_cwd(['notebook1.ipynb']):
96 self.call('nbconvert --log-level 0 --to latex notebook1 '
97 '--post PDF --PDFPostProcessor.verbose=True')
98 assert os.path.isfile('notebook1.tex')
99 assert os.path.isfile('notebook1.pdf')
89 out, err = self.call('nbconvert --log-level 0 --to python notebook1 '
90 '--post IPython.nbconvert.tests.test_nbconvertapp.DummyPost')
91 self.assertIn('Dummy:notebook1.py', out)
100 92
101 93 @dec.onlyif_cmds_exist('pandoc')
102 94 def test_spurious_cr(self):
103 95 """Check for extra CR characters"""
104 96 with self.create_temp_cwd(['notebook2.ipynb']):
105 97 self.call('nbconvert --log-level 0 --to latex notebook2')
106 98 assert os.path.isfile('notebook2.tex')
107 99 with open('notebook2.tex') as f:
108 100 tex = f.read()
109 101 self.call('nbconvert --log-level 0 --to html notebook2')
110 102 assert os.path.isfile('notebook2.html')
111 103 with open('notebook2.html') as f:
112 104 html = f.read()
113 105 self.assertEqual(tex.count('\r'), tex.count('\r\n'))
114 106 self.assertEqual(html.count('\r'), html.count('\r\n'))
115 107
116 108 @dec.onlyif_cmds_exist('pandoc')
117 109 def test_png_base64_html_ok(self):
118 110 """Is embedded png data well formed in HTML?"""
119 111 with self.create_temp_cwd(['notebook2.ipynb']):
120 112 self.call('nbconvert --log-level 0 --to HTML '
121 113 'notebook2.ipynb --template full')
122 114 assert os.path.isfile('notebook2.html')
123 115 with open('notebook2.html') as f:
124 116 assert "data:image/png;base64,b'" not in f.read()
125 117
126 118 @dec.onlyif_cmds_exist('pandoc')
127 119 def test_template(self):
128 120 """
129 121 Do export templates work?
130 122 """
131 123 with self.create_temp_cwd(['notebook2.ipynb']):
132 124 self.call('nbconvert --log-level 0 --to slides '
133 125 'notebook2.ipynb')
134 126 assert os.path.isfile('notebook2.slides.html')
135 127 with open('notebook2.slides.html') as f:
136 128 assert '/reveal.css' in f.read()
137 129
138 130
139 131 def test_glob_explicit(self):
140 132 """
141 133 Can a search pattern be used along with matching explicit notebook names?
142 134 """
143 135 with self.create_temp_cwd(['notebook*.ipynb']):
144 136 self.call('nbconvert --log-level 0 --to python '
145 137 '*.ipynb notebook1.ipynb notebook2.ipynb')
146 138 assert os.path.isfile('notebook1.py')
147 139 assert os.path.isfile('notebook2.py')
148 140
149 141
150 142 def test_explicit_glob(self):
151 143 """
152 144 Can explicit notebook names be used and then a matching search pattern?
153 145 """
154 146 with self.create_temp_cwd(['notebook*.ipynb']):
155 147 self.call('nbconvert --log-level 0 --to=python '
156 148 'notebook1.ipynb notebook2.ipynb *.ipynb')
157 149 assert os.path.isfile('notebook1.py')
158 150 assert os.path.isfile('notebook2.py')
159 151
160 152
161 153 def test_default_config(self):
162 154 """
163 155 Does the default config work?
164 156 """
165 157 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py']):
166 158 self.call('nbconvert --log-level 0')
167 159 assert os.path.isfile('notebook1.py')
168 160 assert not os.path.isfile('notebook2.py')
169 161
170 162
171 163 def test_override_config(self):
172 164 """
173 165 Can the default config be overriden?
174 166 """
175 167 with self.create_temp_cwd(['notebook*.ipynb',
176 168 'ipython_nbconvert_config.py',
177 169 'override.py']):
178 170 self.call('nbconvert --log-level 0 --config="override.py"')
179 171 assert not os.path.isfile('notebook1.py')
180 172 assert os.path.isfile('notebook2.py')
181 173
182 174 def test_accents_in_filename(self):
183 175 """
184 176 Can notebook names include accents?
185 177 """
186 178 with self.create_temp_cwd():
187 179 self.create_empty_notebook(u'nb1_análisis.ipynb')
188 180 self.call('nbconvert --log-level 0 --to python nb1_*')
189 181 assert os.path.isfile(u'nb1_análisis.py')
190 182
191 183 @dec.onlyif_cmds_exist('pdflatex', 'pandoc')
192 184 def test_filename_accent_pdf(self):
193 185 """
194 186 Generate PDFs if notebooks have an accent in their name?
195 187 """
196 188 with self.create_temp_cwd():
197 189 self.create_empty_notebook(u'nb1_análisis.ipynb')
198 self.call('nbconvert --log-level 0 --to latex '
199 '"nb1_*" --post PDF '
200 '--PDFPostProcessor.verbose=True')
201 assert os.path.isfile(u'nb1_análisis.tex')
190 self.call('nbconvert --log-level 0 --to pdf "nb1_*"'
191 ' --PDFExporter.latex_count=1'
192 ' --PDFExporter.verbose=True')
202 193 assert os.path.isfile(u'nb1_análisis.pdf')
203 194
204 195 def test_cwd_plugin(self):
205 196 """
206 197 Verify that an extension in the cwd can be imported.
207 198 """
208 199 with self.create_temp_cwd(['hello.py']):
209 200 self.create_empty_notebook(u'empty.ipynb')
210 201 self.call('nbconvert empty --to html --NbConvertApp.writer_class=\'hello.HelloWriter\'')
211 202 assert os.path.isfile(u'hello.txt')
@@ -1,115 +1,111 b''
1 """
2 Contains writer for writing nbconvert output to filesystem.
3 """
4 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
6 #
7 #Distributed under the terms of the Modified BSD License.
8 #
9 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
1 """Contains writer for writing nbconvert output to filesystem."""
11 2
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
15 5
16 6 import io
17 7 import os
18 8 import glob
19 9
20 10 from IPython.utils.traitlets import Unicode
21 11 from IPython.utils.path import link_or_copy
12 from IPython.utils.py3compat import unicode_type
22 13
23 14 from .base import WriterBase
24 15
25 16 #-----------------------------------------------------------------------------
26 17 # Classes
27 18 #-----------------------------------------------------------------------------
28 19
29 20 class FilesWriter(WriterBase):
30 21 """Consumes nbconvert output and produces files."""
31 22
32 23
33 24 build_directory = Unicode("", config=True,
34 25 help="""Directory to write output to. Leave blank
35 26 to output to the current directory""")
36 27
37 28
38 29 # Make sure that the output directory exists.
39 30 def _build_directory_changed(self, name, old, new):
40 31 if new and not os.path.isdir(new):
41 32 os.makedirs(new)
42 33
43 34
44 35 def __init__(self, **kw):
45 36 super(FilesWriter, self).__init__(**kw)
46 37 self._build_directory_changed('build_directory', self.build_directory,
47 38 self.build_directory)
48 39
49 40 def _makedir(self, path):
50 41 """Make a directory if it doesn't already exist"""
51 42 if path and not os.path.isdir(path):
52 43 self.log.info("Making directory %s", path)
53 44 os.makedirs(path)
54 45
55 46 def write(self, output, resources, notebook_name=None, **kw):
56 47 """
57 48 Consume and write Jinja output to the file system. Output directory
58 49 is set via the 'build_directory' variable of this instance (a
59 50 configurable).
60 51
61 52 See base for more...
62 53 """
63 54
64 55 # Verify that a notebook name is provided.
65 56 if notebook_name is None:
66 57 raise TypeError('notebook_name')
67 58
68 59 # Pull the extension and subdir from the resources dict.
69 60 output_extension = resources.get('output_extension', None)
70 61
71 62 # Write all of the extracted resources to the destination directory.
72 63 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
73 64 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
74 65 for filename, data in resources.get('outputs', {}).items():
75 66
76 67 # Determine where to write the file to
77 68 dest = os.path.join(self.build_directory, filename)
78 69 path = os.path.dirname(dest)
79 70 self._makedir(path)
80 71
81 72 # Write file
82 73 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
83 74 with io.open(dest, 'wb') as f:
84 75 f.write(data)
85 76
86 77 # Copy referenced files to output directory
87 78 if self.build_directory:
88 79 for filename in self.files:
89 80
90 81 # Copy files that match search pattern
91 82 for matching_filename in glob.glob(filename):
92 83
93 84 # Make sure folder exists.
94 85 dest = os.path.join(self.build_directory, filename)
95 86 path = os.path.dirname(dest)
96 87 self._makedir(path)
97 88
98 89 # Copy if destination is different.
99 90 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
100 91 self.log.info("Linking %s -> %s", matching_filename, dest)
101 92 link_or_copy(matching_filename, dest)
102 93
103 94 # Determine where to write conversion results.
104 95 if output_extension is not None:
105 96 dest = notebook_name + '.' + output_extension
106 97 else:
107 98 dest = notebook_name
108 99 if self.build_directory:
109 100 dest = os.path.join(self.build_directory, dest)
110 101
111 102 # Write conversion results.
112 103 self.log.info("Writing %i bytes to %s", len(output), dest)
104 if isinstance(output, unicode_type):
113 105 with io.open(dest, 'w', encoding='utf-8') as f:
114 106 f.write(output)
107 else:
108 with io.open(dest, 'wb') as f:
109 f.write(output)
110
115 111 return dest
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now