##// END OF EJS Templates
Merge pull request #6978 from takluyver/nbconvert-script...
Min RK -
r19053:38e12544 merge
parent child Browse files
Show More
@@ -0,0 +1,14 b''
1 """Generic script exporter class for any kernel language"""
2
3 from .templateexporter import TemplateExporter
4
5 class ScriptExporter(TemplateExporter):
6 def _template_file_default(self):
7 return 'script'
8
9 def from_notebook_node(self, nb, resources=None, **kw):
10 langinfo = nb.metadata.get('language_info', {})
11 self.file_extension = langinfo.get('file_extension', '.txt')
12 self.output_mimetype = langinfo.get('mimetype', 'text/plain')
13
14 return super(ScriptExporter, self).from_notebook_node(nb, resources, **kw)
@@ -0,0 +1,5 b''
1 {%- extends 'null.tpl' -%}
2
3 {% block input %}
4 {{ cell.source }}
5 {% endblock input %}
@@ -1,149 +1,149 b''
1 """Tornado handlers for nbconvert."""
1 """Tornado handlers for nbconvert."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import io
6 import io
7 import os
7 import os
8 import zipfile
8 import zipfile
9
9
10 from tornado import web
10 from tornado import web
11
11
12 from ..base.handlers import (
12 from ..base.handlers import (
13 IPythonHandler, FilesRedirectHandler,
13 IPythonHandler, FilesRedirectHandler,
14 notebook_path_regex, path_regex,
14 notebook_path_regex, path_regex,
15 )
15 )
16 from IPython.nbformat import from_dict
16 from IPython.nbformat import from_dict
17
17
18 from IPython.utils.py3compat import cast_bytes
18 from IPython.utils.py3compat import cast_bytes
19
19
20 def find_resource_files(output_files_dir):
20 def find_resource_files(output_files_dir):
21 files = []
21 files = []
22 for dirpath, dirnames, filenames in os.walk(output_files_dir):
22 for dirpath, dirnames, filenames in os.walk(output_files_dir):
23 files.extend([os.path.join(dirpath, f) for f in filenames])
23 files.extend([os.path.join(dirpath, f) for f in filenames])
24 return files
24 return files
25
25
26 def respond_zip(handler, name, output, resources):
26 def respond_zip(handler, name, output, resources):
27 """Zip up the output and resource files and respond with the zip file.
27 """Zip up the output and resource files and respond with the zip file.
28
28
29 Returns True if it has served a zip file, False if there are no resource
29 Returns True if it has served a zip file, False if there are no resource
30 files, in which case we serve the plain output file.
30 files, in which case we serve the plain output file.
31 """
31 """
32 # Check if we have resource files we need to zip
32 # Check if we have resource files we need to zip
33 output_files = resources.get('outputs', None)
33 output_files = resources.get('outputs', None)
34 if not output_files:
34 if not output_files:
35 return False
35 return False
36
36
37 # Headers
37 # Headers
38 zip_filename = os.path.splitext(name)[0] + '.zip'
38 zip_filename = os.path.splitext(name)[0] + '.zip'
39 handler.set_header('Content-Disposition',
39 handler.set_header('Content-Disposition',
40 'attachment; filename="%s"' % zip_filename)
40 'attachment; filename="%s"' % zip_filename)
41 handler.set_header('Content-Type', 'application/zip')
41 handler.set_header('Content-Type', 'application/zip')
42
42
43 # Prepare the zip file
43 # Prepare the zip file
44 buffer = io.BytesIO()
44 buffer = io.BytesIO()
45 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
45 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
46 output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
46 output_filename = os.path.splitext(name)[0] + resources['output_extension']
47 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
47 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
48 for filename, data in output_files.items():
48 for filename, data in output_files.items():
49 zipf.writestr(os.path.basename(filename), data)
49 zipf.writestr(os.path.basename(filename), data)
50 zipf.close()
50 zipf.close()
51
51
52 handler.finish(buffer.getvalue())
52 handler.finish(buffer.getvalue())
53 return True
53 return True
54
54
55 def get_exporter(format, **kwargs):
55 def get_exporter(format, **kwargs):
56 """get an exporter, raising appropriate errors"""
56 """get an exporter, raising appropriate errors"""
57 # if this fails, will raise 500
57 # if this fails, will raise 500
58 try:
58 try:
59 from IPython.nbconvert.exporters.export import exporter_map
59 from IPython.nbconvert.exporters.export import exporter_map
60 except ImportError as e:
60 except ImportError as e:
61 raise web.HTTPError(500, "Could not import nbconvert: %s" % e)
61 raise web.HTTPError(500, "Could not import nbconvert: %s" % e)
62
62
63 try:
63 try:
64 Exporter = exporter_map[format]
64 Exporter = exporter_map[format]
65 except KeyError:
65 except KeyError:
66 # should this be 400?
66 # should this be 400?
67 raise web.HTTPError(404, u"No exporter for format: %s" % format)
67 raise web.HTTPError(404, u"No exporter for format: %s" % format)
68
68
69 try:
69 try:
70 return Exporter(**kwargs)
70 return Exporter(**kwargs)
71 except Exception as e:
71 except Exception as e:
72 raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
72 raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
73
73
74 class NbconvertFileHandler(IPythonHandler):
74 class NbconvertFileHandler(IPythonHandler):
75
75
76 SUPPORTED_METHODS = ('GET',)
76 SUPPORTED_METHODS = ('GET',)
77
77
78 @web.authenticated
78 @web.authenticated
79 def get(self, format, path):
79 def get(self, format, path):
80
80
81 exporter = get_exporter(format, config=self.config, log=self.log)
81 exporter = get_exporter(format, config=self.config, log=self.log)
82
82
83 path = path.strip('/')
83 path = path.strip('/')
84 model = self.contents_manager.get(path=path)
84 model = self.contents_manager.get(path=path)
85 name = model['name']
85 name = model['name']
86
86
87 self.set_header('Last-Modified', model['last_modified'])
87 self.set_header('Last-Modified', model['last_modified'])
88
88
89 try:
89 try:
90 output, resources = exporter.from_notebook_node(model['content'])
90 output, resources = exporter.from_notebook_node(model['content'])
91 except Exception as e:
91 except Exception as e:
92 raise web.HTTPError(500, "nbconvert failed: %s" % e)
92 raise web.HTTPError(500, "nbconvert failed: %s" % e)
93
93
94 if respond_zip(self, name, output, resources):
94 if respond_zip(self, name, output, resources):
95 return
95 return
96
96
97 # Force download if requested
97 # Force download if requested
98 if self.get_argument('download', 'false').lower() == 'true':
98 if self.get_argument('download', 'false').lower() == 'true':
99 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
99 filename = os.path.splitext(name)[0] + resources['output_extension']
100 self.set_header('Content-Disposition',
100 self.set_header('Content-Disposition',
101 'attachment; filename="%s"' % filename)
101 'attachment; filename="%s"' % filename)
102
102
103 # MIME type
103 # MIME type
104 if exporter.output_mimetype:
104 if exporter.output_mimetype:
105 self.set_header('Content-Type',
105 self.set_header('Content-Type',
106 '%s; charset=utf-8' % exporter.output_mimetype)
106 '%s; charset=utf-8' % exporter.output_mimetype)
107
107
108 self.finish(output)
108 self.finish(output)
109
109
110 class NbconvertPostHandler(IPythonHandler):
110 class NbconvertPostHandler(IPythonHandler):
111 SUPPORTED_METHODS = ('POST',)
111 SUPPORTED_METHODS = ('POST',)
112
112
113 @web.authenticated
113 @web.authenticated
114 def post(self, format):
114 def post(self, format):
115 exporter = get_exporter(format, config=self.config)
115 exporter = get_exporter(format, config=self.config)
116
116
117 model = self.get_json_body()
117 model = self.get_json_body()
118 name = model.get('name', 'notebook.ipynb')
118 name = model.get('name', 'notebook.ipynb')
119 nbnode = from_dict(model['content'])
119 nbnode = from_dict(model['content'])
120
120
121 try:
121 try:
122 output, resources = exporter.from_notebook_node(nbnode)
122 output, resources = exporter.from_notebook_node(nbnode)
123 except Exception as e:
123 except Exception as e:
124 raise web.HTTPError(500, "nbconvert failed: %s" % e)
124 raise web.HTTPError(500, "nbconvert failed: %s" % e)
125
125
126 if respond_zip(self, name, output, resources):
126 if respond_zip(self, name, output, resources):
127 return
127 return
128
128
129 # MIME type
129 # MIME type
130 if exporter.output_mimetype:
130 if exporter.output_mimetype:
131 self.set_header('Content-Type',
131 self.set_header('Content-Type',
132 '%s; charset=utf-8' % exporter.output_mimetype)
132 '%s; charset=utf-8' % exporter.output_mimetype)
133
133
134 self.finish(output)
134 self.finish(output)
135
135
136
136
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138 # URL to handler mappings
138 # URL to handler mappings
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140
140
141 _format_regex = r"(?P<format>\w+)"
141 _format_regex = r"(?P<format>\w+)"
142
142
143
143
144 default_handlers = [
144 default_handlers = [
145 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
145 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
146 NbconvertFileHandler),
146 NbconvertFileHandler),
147 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
147 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
148 (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
148 (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
149 ]
149 ]
@@ -1,348 +1,383 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 // Constructor
16 // Constructor
17 //
17 //
18 // A MenuBar Class to generate the menubar of IPython notebook
18 // A MenuBar Class to generate the menubar of IPython notebook
19 //
19 //
20 // Parameters:
20 // Parameters:
21 // selector: string
21 // selector: string
22 // options: dictionary
22 // options: dictionary
23 // Dictionary of keyword arguments.
23 // Dictionary of keyword arguments.
24 // notebook: Notebook instance
24 // notebook: Notebook instance
25 // contents: ContentManager instance
25 // contents: ContentManager instance
26 // layout_manager: LayoutManager instance
26 // layout_manager: LayoutManager instance
27 // events: $(Events) instance
27 // events: $(Events) instance
28 // save_widget: SaveWidget instance
28 // save_widget: SaveWidget instance
29 // quick_help: QuickHelp instance
29 // quick_help: QuickHelp instance
30 // base_url : string
30 // base_url : string
31 // notebook_path : string
31 // notebook_path : string
32 // notebook_name : string
32 // notebook_name : string
33 options = options || {};
33 options = options || {};
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.selector = selector;
35 this.selector = selector;
36 this.notebook = options.notebook;
36 this.notebook = options.notebook;
37 this.contents = options.contents;
37 this.contents = options.contents;
38 this.layout_manager = options.layout_manager;
38 this.layout_manager = options.layout_manager;
39 this.events = options.events;
39 this.events = options.events;
40 this.save_widget = options.save_widget;
40 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
41 this.quick_help = options.quick_help;
42
42
43 try {
43 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
44 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
45 } catch (e) {
46 this.tour = undefined;
46 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
47 console.log("Failed to instantiate Notebook Tour", e);
48 }
48 }
49
49
50 if (this.selector !== undefined) {
50 if (this.selector !== undefined) {
51 this.element = $(selector);
51 this.element = $(selector);
52 this.style();
52 this.style();
53 this.bind_events();
53 this.bind_events();
54 }
54 }
55 };
55 };
56
56
57 // TODO: This has definitively nothing to do with style ...
57 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
58 MenuBar.prototype.style = function () {
59 var that = this;
59 var that = this;
60 this.element.find("li").click(function (event, ui) {
60 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
61 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
62 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
63 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
64 that.notebook.select(i);
65 }
65 }
66 );
66 );
67 };
67 };
68
68
69 MenuBar.prototype._nbconvert = function (format, download) {
69 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
70 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
71 var notebook_path = this.notebook.notebook_path;
72 if (this.notebook.dirty) {
73 this.notebook.save_notebook({async : false});
74 }
75 var url = utils.url_join_encode(
72 var url = utils.url_join_encode(
76 this.base_url,
73 this.base_url,
77 'nbconvert',
74 'nbconvert',
78 format,
75 format,
79 notebook_path
76 notebook_path
80 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
81
78
82 window.open(url);
79 var w = window.open()
80 if (this.notebook.dirty) {
81 this.notebook.save_notebook().then(function() {
82 w.location = url;
83 });
84 } else {
85 w.location = url;
86 }
83 };
87 };
84
88
85 MenuBar.prototype.bind_events = function () {
89 MenuBar.prototype.bind_events = function () {
86 // File
90 // File
87 var that = this;
91 var that = this;
88 this.element.find('#new_notebook').click(function () {
92 this.element.find('#new_notebook').click(function () {
89 var w = window.open();
93 var w = window.open();
90 // Create a new notebook in the same path as the current
94 // Create a new notebook in the same path as the current
91 // notebook's path.
95 // notebook's path.
92 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
96 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
93 that.contents.new_untitled(parent, {type: "notebook"}).then(
97 that.contents.new_untitled(parent, {type: "notebook"}).then(
94 function (data) {
98 function (data) {
95 w.location = utils.url_join_encode(
99 w.location = utils.url_join_encode(
96 that.base_url, 'notebooks', data.path
100 that.base_url, 'notebooks', data.path
97 );
101 );
98 },
102 },
99 function(error) {
103 function(error) {
100 w.close();
104 w.close();
101 dialog.modal({
105 dialog.modal({
102 title : 'Creating Notebook Failed',
106 title : 'Creating Notebook Failed',
103 body : "The error was: " + error.message,
107 body : "The error was: " + error.message,
104 buttons : {'OK' : {'class' : 'btn-primary'}}
108 buttons : {'OK' : {'class' : 'btn-primary'}}
105 });
109 });
106 }
110 }
107 );
111 );
108 });
112 });
109 this.element.find('#open_notebook').click(function () {
113 this.element.find('#open_notebook').click(function () {
110 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
114 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
111 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
115 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
112 });
116 });
113 this.element.find('#copy_notebook').click(function () {
117 this.element.find('#copy_notebook').click(function () {
114 that.notebook.copy_notebook();
118 that.notebook.copy_notebook();
115 return false;
119 return false;
116 });
120 });
117 this.element.find('#download_ipynb').click(function () {
121 this.element.find('#download_ipynb').click(function () {
118 var base_url = that.notebook.base_url;
122 var base_url = that.notebook.base_url;
119 var notebook_path = that.notebook.notebook_path;
123 var notebook_path = that.notebook.notebook_path;
120 if (that.notebook.dirty) {
124 if (that.notebook.dirty) {
121 that.notebook.save_notebook({async : false});
125 that.notebook.save_notebook({async : false});
122 }
126 }
123
127
124 var url = utils.url_join_encode(base_url, 'files', notebook_path);
128 var url = utils.url_join_encode(base_url, 'files', notebook_path);
125 window.open(url + '?download=1');
129 window.open(url + '?download=1');
126 });
130 });
127
131
128 this.element.find('#print_preview').click(function () {
132 this.element.find('#print_preview').click(function () {
129 that._nbconvert('html', false);
133 that._nbconvert('html', false);
130 });
134 });
131
135
132 this.element.find('#download_py').click(function () {
133 that._nbconvert('python', true);
134 });
135
136 this.element.find('#download_html').click(function () {
136 this.element.find('#download_html').click(function () {
137 that._nbconvert('html', true);
137 that._nbconvert('html', true);
138 });
138 });
139
139
140 this.element.find('#download_rst').click(function () {
140 this.element.find('#download_rst').click(function () {
141 that._nbconvert('rst', true);
141 that._nbconvert('rst', true);
142 });
142 });
143
143
144 this.element.find('#download_pdf').click(function () {
144 this.element.find('#download_pdf').click(function () {
145 that._nbconvert('pdf', true);
145 that._nbconvert('pdf', true);
146 });
146 });
147
147
148 this.element.find('#rename_notebook').click(function () {
148 this.element.find('#rename_notebook').click(function () {
149 that.save_widget.rename_notebook({notebook: that.notebook});
149 that.save_widget.rename_notebook({notebook: that.notebook});
150 });
150 });
151 this.element.find('#save_checkpoint').click(function () {
151 this.element.find('#save_checkpoint').click(function () {
152 that.notebook.save_checkpoint();
152 that.notebook.save_checkpoint();
153 });
153 });
154 this.element.find('#restore_checkpoint').click(function () {
154 this.element.find('#restore_checkpoint').click(function () {
155 });
155 });
156 this.element.find('#trust_notebook').click(function () {
156 this.element.find('#trust_notebook').click(function () {
157 that.notebook.trust_notebook();
157 that.notebook.trust_notebook();
158 });
158 });
159 this.events.on('trust_changed.Notebook', function (event, trusted) {
159 this.events.on('trust_changed.Notebook', function (event, trusted) {
160 if (trusted) {
160 if (trusted) {
161 that.element.find('#trust_notebook')
161 that.element.find('#trust_notebook')
162 .addClass("disabled")
162 .addClass("disabled")
163 .find("a").text("Trusted Notebook");
163 .find("a").text("Trusted Notebook");
164 } else {
164 } else {
165 that.element.find('#trust_notebook')
165 that.element.find('#trust_notebook')
166 .removeClass("disabled")
166 .removeClass("disabled")
167 .find("a").text("Trust Notebook");
167 .find("a").text("Trust Notebook");
168 }
168 }
169 });
169 });
170 this.element.find('#kill_and_exit').click(function () {
170 this.element.find('#kill_and_exit').click(function () {
171 var close_window = function () {
171 var close_window = function () {
172 // allow closing of new tabs in Chromium, impossible in FF
172 // allow closing of new tabs in Chromium, impossible in FF
173 window.open('', '_self', '');
173 window.open('', '_self', '');
174 window.close();
174 window.close();
175 };
175 };
176 // finish with close on success or failure
176 // finish with close on success or failure
177 that.notebook.session.delete(close_window, close_window);
177 that.notebook.session.delete(close_window, close_window);
178 });
178 });
179 // Edit
179 // Edit
180 this.element.find('#cut_cell').click(function () {
180 this.element.find('#cut_cell').click(function () {
181 that.notebook.cut_cell();
181 that.notebook.cut_cell();
182 });
182 });
183 this.element.find('#copy_cell').click(function () {
183 this.element.find('#copy_cell').click(function () {
184 that.notebook.copy_cell();
184 that.notebook.copy_cell();
185 });
185 });
186 this.element.find('#delete_cell').click(function () {
186 this.element.find('#delete_cell').click(function () {
187 that.notebook.delete_cell();
187 that.notebook.delete_cell();
188 });
188 });
189 this.element.find('#undelete_cell').click(function () {
189 this.element.find('#undelete_cell').click(function () {
190 that.notebook.undelete_cell();
190 that.notebook.undelete_cell();
191 });
191 });
192 this.element.find('#split_cell').click(function () {
192 this.element.find('#split_cell').click(function () {
193 that.notebook.split_cell();
193 that.notebook.split_cell();
194 });
194 });
195 this.element.find('#merge_cell_above').click(function () {
195 this.element.find('#merge_cell_above').click(function () {
196 that.notebook.merge_cell_above();
196 that.notebook.merge_cell_above();
197 });
197 });
198 this.element.find('#merge_cell_below').click(function () {
198 this.element.find('#merge_cell_below').click(function () {
199 that.notebook.merge_cell_below();
199 that.notebook.merge_cell_below();
200 });
200 });
201 this.element.find('#move_cell_up').click(function () {
201 this.element.find('#move_cell_up').click(function () {
202 that.notebook.move_cell_up();
202 that.notebook.move_cell_up();
203 });
203 });
204 this.element.find('#move_cell_down').click(function () {
204 this.element.find('#move_cell_down').click(function () {
205 that.notebook.move_cell_down();
205 that.notebook.move_cell_down();
206 });
206 });
207 this.element.find('#edit_nb_metadata').click(function () {
207 this.element.find('#edit_nb_metadata').click(function () {
208 that.notebook.edit_metadata({
208 that.notebook.edit_metadata({
209 notebook: that.notebook,
209 notebook: that.notebook,
210 keyboard_manager: that.notebook.keyboard_manager});
210 keyboard_manager: that.notebook.keyboard_manager});
211 });
211 });
212
212
213 // View
213 // View
214 this.element.find('#toggle_header').click(function () {
214 this.element.find('#toggle_header').click(function () {
215 $('div#header').toggle();
215 $('div#header').toggle();
216 that.layout_manager.do_resize();
216 that.layout_manager.do_resize();
217 });
217 });
218 this.element.find('#toggle_toolbar').click(function () {
218 this.element.find('#toggle_toolbar').click(function () {
219 $('div#maintoolbar').toggle();
219 $('div#maintoolbar').toggle();
220 that.layout_manager.do_resize();
220 that.layout_manager.do_resize();
221 });
221 });
222 // Insert
222 // Insert
223 this.element.find('#insert_cell_above').click(function () {
223 this.element.find('#insert_cell_above').click(function () {
224 that.notebook.insert_cell_above('code');
224 that.notebook.insert_cell_above('code');
225 that.notebook.select_prev();
225 that.notebook.select_prev();
226 });
226 });
227 this.element.find('#insert_cell_below').click(function () {
227 this.element.find('#insert_cell_below').click(function () {
228 that.notebook.insert_cell_below('code');
228 that.notebook.insert_cell_below('code');
229 that.notebook.select_next();
229 that.notebook.select_next();
230 });
230 });
231 // Cell
231 // Cell
232 this.element.find('#run_cell').click(function () {
232 this.element.find('#run_cell').click(function () {
233 that.notebook.execute_cell();
233 that.notebook.execute_cell();
234 });
234 });
235 this.element.find('#run_cell_select_below').click(function () {
235 this.element.find('#run_cell_select_below').click(function () {
236 that.notebook.execute_cell_and_select_below();
236 that.notebook.execute_cell_and_select_below();
237 });
237 });
238 this.element.find('#run_cell_insert_below').click(function () {
238 this.element.find('#run_cell_insert_below').click(function () {
239 that.notebook.execute_cell_and_insert_below();
239 that.notebook.execute_cell_and_insert_below();
240 });
240 });
241 this.element.find('#run_all_cells').click(function () {
241 this.element.find('#run_all_cells').click(function () {
242 that.notebook.execute_all_cells();
242 that.notebook.execute_all_cells();
243 });
243 });
244 this.element.find('#run_all_cells_above').click(function () {
244 this.element.find('#run_all_cells_above').click(function () {
245 that.notebook.execute_cells_above();
245 that.notebook.execute_cells_above();
246 });
246 });
247 this.element.find('#run_all_cells_below').click(function () {
247 this.element.find('#run_all_cells_below').click(function () {
248 that.notebook.execute_cells_below();
248 that.notebook.execute_cells_below();
249 });
249 });
250 this.element.find('#to_code').click(function () {
250 this.element.find('#to_code').click(function () {
251 that.notebook.to_code();
251 that.notebook.to_code();
252 });
252 });
253 this.element.find('#to_markdown').click(function () {
253 this.element.find('#to_markdown').click(function () {
254 that.notebook.to_markdown();
254 that.notebook.to_markdown();
255 });
255 });
256 this.element.find('#to_raw').click(function () {
256 this.element.find('#to_raw').click(function () {
257 that.notebook.to_raw();
257 that.notebook.to_raw();
258 });
258 });
259
259
260 this.element.find('#toggle_current_output').click(function () {
260 this.element.find('#toggle_current_output').click(function () {
261 that.notebook.toggle_output();
261 that.notebook.toggle_output();
262 });
262 });
263 this.element.find('#toggle_current_output_scroll').click(function () {
263 this.element.find('#toggle_current_output_scroll').click(function () {
264 that.notebook.toggle_output_scroll();
264 that.notebook.toggle_output_scroll();
265 });
265 });
266 this.element.find('#clear_current_output').click(function () {
266 this.element.find('#clear_current_output').click(function () {
267 that.notebook.clear_output();
267 that.notebook.clear_output();
268 });
268 });
269
269
270 this.element.find('#toggle_all_output').click(function () {
270 this.element.find('#toggle_all_output').click(function () {
271 that.notebook.toggle_all_output();
271 that.notebook.toggle_all_output();
272 });
272 });
273 this.element.find('#toggle_all_output_scroll').click(function () {
273 this.element.find('#toggle_all_output_scroll').click(function () {
274 that.notebook.toggle_all_output_scroll();
274 that.notebook.toggle_all_output_scroll();
275 });
275 });
276 this.element.find('#clear_all_output').click(function () {
276 this.element.find('#clear_all_output').click(function () {
277 that.notebook.clear_all_output();
277 that.notebook.clear_all_output();
278 });
278 });
279
279
280 // Kernel
280 // Kernel
281 this.element.find('#int_kernel').click(function () {
281 this.element.find('#int_kernel').click(function () {
282 that.notebook.kernel.interrupt();
282 that.notebook.kernel.interrupt();
283 });
283 });
284 this.element.find('#restart_kernel').click(function () {
284 this.element.find('#restart_kernel').click(function () {
285 that.notebook.restart_kernel();
285 that.notebook.restart_kernel();
286 });
286 });
287 this.element.find('#reconnect_kernel').click(function () {
287 this.element.find('#reconnect_kernel').click(function () {
288 that.notebook.kernel.reconnect();
288 that.notebook.kernel.reconnect();
289 });
289 });
290 // Help
290 // Help
291 if (this.tour) {
291 if (this.tour) {
292 this.element.find('#notebook_tour').click(function () {
292 this.element.find('#notebook_tour').click(function () {
293 that.tour.start();
293 that.tour.start();
294 });
294 });
295 } else {
295 } else {
296 this.element.find('#notebook_tour').addClass("disabled");
296 this.element.find('#notebook_tour').addClass("disabled");
297 }
297 }
298 this.element.find('#keyboard_shortcuts').click(function () {
298 this.element.find('#keyboard_shortcuts').click(function () {
299 that.quick_help.show_keyboard_shortcuts();
299 that.quick_help.show_keyboard_shortcuts();
300 });
300 });
301
301
302 this.update_restore_checkpoint(null);
302 this.update_restore_checkpoint(null);
303
303
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
305 that.update_restore_checkpoint(that.notebook.checkpoints);
305 that.update_restore_checkpoint(that.notebook.checkpoints);
306 });
306 });
307
307
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
309 that.update_restore_checkpoint(that.notebook.checkpoints);
309 that.update_restore_checkpoint(that.notebook.checkpoints);
310 });
310 });
311
312 this.events.on('notebook_loaded.Notebook', function() {
313 var langinfo = that.notebook.metadata.language_info || {};
314 that.update_nbconvert_script(langinfo);
315 });
316
317 this.events.on('kernel_ready.Kernel', function(event, data) {
318 var langinfo = data.kernel.info_reply.language_info || {};
319 that.update_nbconvert_script(langinfo);
320 });
311 };
321 };
312
322
313 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
323 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
314 var ul = this.element.find("#restore_checkpoint").find("ul");
324 var ul = this.element.find("#restore_checkpoint").find("ul");
315 ul.empty();
325 ul.empty();
316 if (!checkpoints || checkpoints.length === 0) {
326 if (!checkpoints || checkpoints.length === 0) {
317 ul.append(
327 ul.append(
318 $("<li/>")
328 $("<li/>")
319 .addClass("disabled")
329 .addClass("disabled")
320 .append(
330 .append(
321 $("<a/>")
331 $("<a/>")
322 .text("No checkpoints")
332 .text("No checkpoints")
323 )
333 )
324 );
334 );
325 return;
335 return;
326 }
336 }
327
337
328 var that = this;
338 var that = this;
329 checkpoints.map(function (checkpoint) {
339 checkpoints.map(function (checkpoint) {
330 var d = new Date(checkpoint.last_modified);
340 var d = new Date(checkpoint.last_modified);
331 ul.append(
341 ul.append(
332 $("<li/>").append(
342 $("<li/>").append(
333 $("<a/>")
343 $("<a/>")
334 .attr("href", "#")
344 .attr("href", "#")
335 .text(moment(d).format("LLLL"))
345 .text(moment(d).format("LLLL"))
336 .click(function () {
346 .click(function () {
337 that.notebook.restore_checkpoint_dialog(checkpoint);
347 that.notebook.restore_checkpoint_dialog(checkpoint);
338 })
348 })
339 )
349 )
340 );
350 );
341 });
351 });
342 };
352 };
353
354 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
355 // Set the 'Download as foo' menu option for the relevant language.
356 var el = this.element.find('#download_script');
357 var that = this;
358
359 // Set menu entry text to e.g. "Python (.py)"
360 var langname = (langinfo.name || 'Script')
361 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
362 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
363
364 // Unregister any previously registered handlers
365 el.off('click');
366 if (langinfo.nbconvert_exporter) {
367 // Metadata specifies a specific exporter, e.g. 'python'
368 el.click(function() {
369 that._nbconvert(langinfo.nbconvert_exporter, true);
370 });
371 } else {
372 // Use generic 'script' exporter
373 el.click(function() {
374 that._nbconvert('script', true);
375 });
376 }
377 };
343
378
344 // Backwards compatability.
379 // Backwards compatability.
345 IPython.MenuBar = MenuBar;
380 IPython.MenuBar = MenuBar;
346
381
347 return {'MenuBar': MenuBar};
382 return {'MenuBar': MenuBar};
348 });
383 });
@@ -1,2495 +1,2495 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'codemirror/lib/codemirror',
14 'codemirror/lib/codemirror',
15 'codemirror/addon/runmode/runmode',
15 'codemirror/addon/runmode/runmode',
16 'notebook/js/mathjaxutils',
16 'notebook/js/mathjaxutils',
17 'base/js/keyboard',
17 'base/js/keyboard',
18 'notebook/js/tooltip',
18 'notebook/js/tooltip',
19 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/default',
20 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/rawcell',
21 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/celltoolbarpresets/slideshow',
22 'notebook/js/scrollmanager'
22 'notebook/js/scrollmanager'
23 ], function (
23 ], function (
24 IPython,
24 IPython,
25 $,
25 $,
26 utils,
26 utils,
27 dialog,
27 dialog,
28 textcell,
28 textcell,
29 codecell,
29 codecell,
30 session,
30 session,
31 celltoolbar,
31 celltoolbar,
32 marked,
32 marked,
33 CodeMirror,
33 CodeMirror,
34 runMode,
34 runMode,
35 mathjaxutils,
35 mathjaxutils,
36 keyboard,
36 keyboard,
37 tooltip,
37 tooltip,
38 default_celltoolbar,
38 default_celltoolbar,
39 rawcell_celltoolbar,
39 rawcell_celltoolbar,
40 slideshow_celltoolbar,
40 slideshow_celltoolbar,
41 scrollmanager
41 scrollmanager
42 ) {
42 ) {
43 "use strict";
43 "use strict";
44
44
45 var Notebook = function (selector, options) {
45 var Notebook = function (selector, options) {
46 // Constructor
46 // Constructor
47 //
47 //
48 // A notebook contains and manages cells.
48 // A notebook contains and manages cells.
49 //
49 //
50 // Parameters:
50 // Parameters:
51 // selector: string
51 // selector: string
52 // options: dictionary
52 // options: dictionary
53 // Dictionary of keyword arguments.
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
54 // events: $(Events) instance
55 // keyboard_manager: KeyboardManager instance
55 // keyboard_manager: KeyboardManager instance
56 // contents: Contents instance
56 // contents: Contents instance
57 // save_widget: SaveWidget instance
57 // save_widget: SaveWidget instance
58 // config: dictionary
58 // config: dictionary
59 // base_url : string
59 // base_url : string
60 // notebook_path : string
60 // notebook_path : string
61 // notebook_name : string
61 // notebook_name : string
62 this.config = utils.mergeopt(Notebook, options.config);
62 this.config = utils.mergeopt(Notebook, options.config);
63 this.base_url = options.base_url;
63 this.base_url = options.base_url;
64 this.notebook_path = options.notebook_path;
64 this.notebook_path = options.notebook_path;
65 this.notebook_name = options.notebook_name;
65 this.notebook_name = options.notebook_name;
66 this.events = options.events;
66 this.events = options.events;
67 this.keyboard_manager = options.keyboard_manager;
67 this.keyboard_manager = options.keyboard_manager;
68 this.contents = options.contents;
68 this.contents = options.contents;
69 this.save_widget = options.save_widget;
69 this.save_widget = options.save_widget;
70 this.tooltip = new tooltip.Tooltip(this.events);
70 this.tooltip = new tooltip.Tooltip(this.events);
71 this.ws_url = options.ws_url;
71 this.ws_url = options.ws_url;
72 this._session_starting = false;
72 this._session_starting = false;
73 this.default_cell_type = this.config.default_cell_type || 'code';
73 this.default_cell_type = this.config.default_cell_type || 'code';
74
74
75 // Create default scroll manager.
75 // Create default scroll manager.
76 this.scroll_manager = new scrollmanager.ScrollManager(this);
76 this.scroll_manager = new scrollmanager.ScrollManager(this);
77
77
78 // TODO: This code smells (and the other `= this` line a couple lines down)
78 // TODO: This code smells (and the other `= this` line a couple lines down)
79 // We need a better way to deal with circular instance references.
79 // We need a better way to deal with circular instance references.
80 this.keyboard_manager.notebook = this;
80 this.keyboard_manager.notebook = this;
81 this.save_widget.notebook = this;
81 this.save_widget.notebook = this;
82
82
83 mathjaxutils.init();
83 mathjaxutils.init();
84
84
85 if (marked) {
85 if (marked) {
86 marked.setOptions({
86 marked.setOptions({
87 gfm : true,
87 gfm : true,
88 tables: true,
88 tables: true,
89 // FIXME: probably want central config for CodeMirror theme when we have js config
89 // FIXME: probably want central config for CodeMirror theme when we have js config
90 langPrefix: "cm-s-ipython language-",
90 langPrefix: "cm-s-ipython language-",
91 highlight: function(code, lang, callback) {
91 highlight: function(code, lang, callback) {
92 if (!lang) {
92 if (!lang) {
93 // no language, no highlight
93 // no language, no highlight
94 if (callback) {
94 if (callback) {
95 callback(null, code);
95 callback(null, code);
96 return;
96 return;
97 } else {
97 } else {
98 return code;
98 return code;
99 }
99 }
100 }
100 }
101 utils.requireCodeMirrorMode(lang, function () {
101 utils.requireCodeMirrorMode(lang, function () {
102 var el = document.createElement("div");
102 var el = document.createElement("div");
103 var mode = CodeMirror.getMode({}, lang);
103 var mode = CodeMirror.getMode({}, lang);
104 if (!mode) {
104 if (!mode) {
105 console.log("No CodeMirror mode: " + lang);
105 console.log("No CodeMirror mode: " + lang);
106 callback(null, code);
106 callback(null, code);
107 return;
107 return;
108 }
108 }
109 try {
109 try {
110 CodeMirror.runMode(code, mode, el);
110 CodeMirror.runMode(code, mode, el);
111 callback(null, el.innerHTML);
111 callback(null, el.innerHTML);
112 } catch (err) {
112 } catch (err) {
113 console.log("Failed to highlight " + lang + " code", err);
113 console.log("Failed to highlight " + lang + " code", err);
114 callback(err, code);
114 callback(err, code);
115 }
115 }
116 }, function (err) {
116 }, function (err) {
117 console.log("No CodeMirror mode: " + lang);
117 console.log("No CodeMirror mode: " + lang);
118 callback(err, code);
118 callback(err, code);
119 });
119 });
120 }
120 }
121 });
121 });
122 }
122 }
123
123
124 this.element = $(selector);
124 this.element = $(selector);
125 this.element.scroll();
125 this.element.scroll();
126 this.element.data("notebook", this);
126 this.element.data("notebook", this);
127 this.next_prompt_number = 1;
127 this.next_prompt_number = 1;
128 this.session = null;
128 this.session = null;
129 this.kernel = null;
129 this.kernel = null;
130 this.clipboard = null;
130 this.clipboard = null;
131 this.undelete_backup = null;
131 this.undelete_backup = null;
132 this.undelete_index = null;
132 this.undelete_index = null;
133 this.undelete_below = false;
133 this.undelete_below = false;
134 this.paste_enabled = false;
134 this.paste_enabled = false;
135 this.writable = false;
135 this.writable = false;
136 // It is important to start out in command mode to match the intial mode
136 // It is important to start out in command mode to match the intial mode
137 // of the KeyboardManager.
137 // of the KeyboardManager.
138 this.mode = 'command';
138 this.mode = 'command';
139 this.set_dirty(false);
139 this.set_dirty(false);
140 this.metadata = {};
140 this.metadata = {};
141 this._checkpoint_after_save = false;
141 this._checkpoint_after_save = false;
142 this.last_checkpoint = null;
142 this.last_checkpoint = null;
143 this.checkpoints = [];
143 this.checkpoints = [];
144 this.autosave_interval = 0;
144 this.autosave_interval = 0;
145 this.autosave_timer = null;
145 this.autosave_timer = null;
146 // autosave *at most* every two minutes
146 // autosave *at most* every two minutes
147 this.minimum_autosave_interval = 120000;
147 this.minimum_autosave_interval = 120000;
148 this.notebook_name_blacklist_re = /[\/\\:]/;
148 this.notebook_name_blacklist_re = /[\/\\:]/;
149 this.nbformat = 4; // Increment this when changing the nbformat
149 this.nbformat = 4; // Increment this when changing the nbformat
150 this.nbformat_minor = 0; // Increment this when changing the nbformat
150 this.nbformat_minor = 0; // Increment this when changing the nbformat
151 this.codemirror_mode = 'ipython';
151 this.codemirror_mode = 'ipython';
152 this.create_elements();
152 this.create_elements();
153 this.bind_events();
153 this.bind_events();
154 this.kernel_selector = null;
154 this.kernel_selector = null;
155 this.dirty = null;
155 this.dirty = null;
156 this.trusted = null;
156 this.trusted = null;
157 this._fully_loaded = false;
157 this._fully_loaded = false;
158
158
159 // Trigger cell toolbar registration.
159 // Trigger cell toolbar registration.
160 default_celltoolbar.register(this);
160 default_celltoolbar.register(this);
161 rawcell_celltoolbar.register(this);
161 rawcell_celltoolbar.register(this);
162 slideshow_celltoolbar.register(this);
162 slideshow_celltoolbar.register(this);
163
163
164 // prevent assign to miss-typed properties.
164 // prevent assign to miss-typed properties.
165 Object.seal(this);
165 Object.seal(this);
166 };
166 };
167
167
168 Notebook.options_default = {
168 Notebook.options_default = {
169 // can be any cell type, or the special values of
169 // can be any cell type, or the special values of
170 // 'above', 'below', or 'selected' to get the value from another cell.
170 // 'above', 'below', or 'selected' to get the value from another cell.
171 Notebook: {
171 Notebook: {
172 default_cell_type: 'code'
172 default_cell_type: 'code'
173 }
173 }
174 };
174 };
175
175
176
176
177 /**
177 /**
178 * Create an HTML and CSS representation of the notebook.
178 * Create an HTML and CSS representation of the notebook.
179 *
179 *
180 * @method create_elements
180 * @method create_elements
181 */
181 */
182 Notebook.prototype.create_elements = function () {
182 Notebook.prototype.create_elements = function () {
183 var that = this;
183 var that = this;
184 this.element.attr('tabindex','-1');
184 this.element.attr('tabindex','-1');
185 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
185 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
186 // We add this end_space div to the end of the notebook div to:
186 // We add this end_space div to the end of the notebook div to:
187 // i) provide a margin between the last cell and the end of the notebook
187 // i) provide a margin between the last cell and the end of the notebook
188 // ii) to prevent the div from scrolling up when the last cell is being
188 // ii) to prevent the div from scrolling up when the last cell is being
189 // edited, but is too low on the page, which browsers will do automatically.
189 // edited, but is too low on the page, which browsers will do automatically.
190 var end_space = $('<div/>').addClass('end_space');
190 var end_space = $('<div/>').addClass('end_space');
191 end_space.dblclick(function (e) {
191 end_space.dblclick(function (e) {
192 var ncells = that.ncells();
192 var ncells = that.ncells();
193 that.insert_cell_below('code',ncells-1);
193 that.insert_cell_below('code',ncells-1);
194 });
194 });
195 this.element.append(this.container);
195 this.element.append(this.container);
196 this.container.append(end_space);
196 this.container.append(end_space);
197 };
197 };
198
198
199 /**
199 /**
200 * Bind JavaScript events: key presses and custom IPython events.
200 * Bind JavaScript events: key presses and custom IPython events.
201 *
201 *
202 * @method bind_events
202 * @method bind_events
203 */
203 */
204 Notebook.prototype.bind_events = function () {
204 Notebook.prototype.bind_events = function () {
205 var that = this;
205 var that = this;
206
206
207 this.events.on('set_next_input.Notebook', function (event, data) {
207 this.events.on('set_next_input.Notebook', function (event, data) {
208 var index = that.find_cell_index(data.cell);
208 var index = that.find_cell_index(data.cell);
209 var new_cell = that.insert_cell_below('code',index);
209 var new_cell = that.insert_cell_below('code',index);
210 new_cell.set_text(data.text);
210 new_cell.set_text(data.text);
211 that.dirty = true;
211 that.dirty = true;
212 });
212 });
213
213
214 this.events.on('set_dirty.Notebook', function (event, data) {
214 this.events.on('set_dirty.Notebook', function (event, data) {
215 that.dirty = data.value;
215 that.dirty = data.value;
216 });
216 });
217
217
218 this.events.on('trust_changed.Notebook', function (event, trusted) {
218 this.events.on('trust_changed.Notebook', function (event, trusted) {
219 that.trusted = trusted;
219 that.trusted = trusted;
220 });
220 });
221
221
222 this.events.on('select.Cell', function (event, data) {
222 this.events.on('select.Cell', function (event, data) {
223 var index = that.find_cell_index(data.cell);
223 var index = that.find_cell_index(data.cell);
224 that.select(index);
224 that.select(index);
225 });
225 });
226
226
227 this.events.on('edit_mode.Cell', function (event, data) {
227 this.events.on('edit_mode.Cell', function (event, data) {
228 that.handle_edit_mode(data.cell);
228 that.handle_edit_mode(data.cell);
229 });
229 });
230
230
231 this.events.on('command_mode.Cell', function (event, data) {
231 this.events.on('command_mode.Cell', function (event, data) {
232 that.handle_command_mode(data.cell);
232 that.handle_command_mode(data.cell);
233 });
233 });
234
234
235 this.events.on('spec_changed.Kernel', function(event, data) {
235 this.events.on('spec_changed.Kernel', function(event, data) {
236 that.metadata.kernelspec =
236 that.metadata.kernelspec =
237 {name: data.name, display_name: data.display_name};
237 {name: data.name, display_name: data.display_name};
238 });
238 });
239
239
240 this.events.on('kernel_ready.Kernel', function(event, data) {
240 this.events.on('kernel_ready.Kernel', function(event, data) {
241 var kinfo = data.kernel.info_reply;
241 var kinfo = data.kernel.info_reply;
242 var langinfo = kinfo.language_info || {};
242 var langinfo = kinfo.language_info || {};
243 if (!langinfo.name) langinfo.name = kinfo.language;
243 if (!langinfo.name) langinfo.name = kinfo.language;
244
244
245 that.metadata.language_info = langinfo;
245 that.metadata.language_info = langinfo;
246 // Mode 'null' should be plain, unhighlighted text.
246 // Mode 'null' should be plain, unhighlighted text.
247 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
247 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
248 that.set_codemirror_mode(cm_mode);
248 that.set_codemirror_mode(cm_mode);
249 });
249 });
250
250
251 var collapse_time = function (time) {
251 var collapse_time = function (time) {
252 var app_height = $('#ipython-main-app').height(); // content height
252 var app_height = $('#ipython-main-app').height(); // content height
253 var splitter_height = $('div#pager_splitter').outerHeight(true);
253 var splitter_height = $('div#pager_splitter').outerHeight(true);
254 var new_height = app_height - splitter_height;
254 var new_height = app_height - splitter_height;
255 that.element.animate({height : new_height + 'px'}, time);
255 that.element.animate({height : new_height + 'px'}, time);
256 };
256 };
257
257
258 this.element.bind('collapse_pager', function (event, extrap) {
258 this.element.bind('collapse_pager', function (event, extrap) {
259 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
259 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
260 collapse_time(time);
260 collapse_time(time);
261 });
261 });
262
262
263 var expand_time = function (time) {
263 var expand_time = function (time) {
264 var app_height = $('#ipython-main-app').height(); // content height
264 var app_height = $('#ipython-main-app').height(); // content height
265 var splitter_height = $('div#pager_splitter').outerHeight(true);
265 var splitter_height = $('div#pager_splitter').outerHeight(true);
266 var pager_height = $('div#pager').outerHeight(true);
266 var pager_height = $('div#pager').outerHeight(true);
267 var new_height = app_height - pager_height - splitter_height;
267 var new_height = app_height - pager_height - splitter_height;
268 that.element.animate({height : new_height + 'px'}, time);
268 that.element.animate({height : new_height + 'px'}, time);
269 };
269 };
270
270
271 this.element.bind('expand_pager', function (event, extrap) {
271 this.element.bind('expand_pager', function (event, extrap) {
272 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
272 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
273 expand_time(time);
273 expand_time(time);
274 });
274 });
275
275
276 // Firefox 22 broke $(window).on("beforeunload")
276 // Firefox 22 broke $(window).on("beforeunload")
277 // I'm not sure why or how.
277 // I'm not sure why or how.
278 window.onbeforeunload = function (e) {
278 window.onbeforeunload = function (e) {
279 // TODO: Make killing the kernel configurable.
279 // TODO: Make killing the kernel configurable.
280 var kill_kernel = false;
280 var kill_kernel = false;
281 if (kill_kernel) {
281 if (kill_kernel) {
282 that.session.delete();
282 that.session.delete();
283 }
283 }
284 // if we are autosaving, trigger an autosave on nav-away.
284 // if we are autosaving, trigger an autosave on nav-away.
285 // still warn, because if we don't the autosave may fail.
285 // still warn, because if we don't the autosave may fail.
286 if (that.dirty) {
286 if (that.dirty) {
287 if ( that.autosave_interval ) {
287 if ( that.autosave_interval ) {
288 // schedule autosave in a timeout
288 // schedule autosave in a timeout
289 // this gives you a chance to forcefully discard changes
289 // this gives you a chance to forcefully discard changes
290 // by reloading the page if you *really* want to.
290 // by reloading the page if you *really* want to.
291 // the timer doesn't start until you *dismiss* the dialog.
291 // the timer doesn't start until you *dismiss* the dialog.
292 setTimeout(function () {
292 setTimeout(function () {
293 if (that.dirty) {
293 if (that.dirty) {
294 that.save_notebook();
294 that.save_notebook();
295 }
295 }
296 }, 1000);
296 }, 1000);
297 return "Autosave in progress, latest changes may be lost.";
297 return "Autosave in progress, latest changes may be lost.";
298 } else {
298 } else {
299 return "Unsaved changes will be lost.";
299 return "Unsaved changes will be lost.";
300 }
300 }
301 }
301 }
302 // Null is the *only* return value that will make the browser not
302 // Null is the *only* return value that will make the browser not
303 // pop up the "don't leave" dialog.
303 // pop up the "don't leave" dialog.
304 return null;
304 return null;
305 };
305 };
306 };
306 };
307
307
308 /**
308 /**
309 * Set the dirty flag, and trigger the set_dirty.Notebook event
309 * Set the dirty flag, and trigger the set_dirty.Notebook event
310 *
310 *
311 * @method set_dirty
311 * @method set_dirty
312 */
312 */
313 Notebook.prototype.set_dirty = function (value) {
313 Notebook.prototype.set_dirty = function (value) {
314 if (value === undefined) {
314 if (value === undefined) {
315 value = true;
315 value = true;
316 }
316 }
317 if (this.dirty == value) {
317 if (this.dirty == value) {
318 return;
318 return;
319 }
319 }
320 this.events.trigger('set_dirty.Notebook', {value: value});
320 this.events.trigger('set_dirty.Notebook', {value: value});
321 };
321 };
322
322
323 /**
323 /**
324 * Scroll the top of the page to a given cell.
324 * Scroll the top of the page to a given cell.
325 *
325 *
326 * @method scroll_to_cell
326 * @method scroll_to_cell
327 * @param {Number} cell_number An index of the cell to view
327 * @param {Number} cell_number An index of the cell to view
328 * @param {Number} time Animation time in milliseconds
328 * @param {Number} time Animation time in milliseconds
329 * @return {Number} Pixel offset from the top of the container
329 * @return {Number} Pixel offset from the top of the container
330 */
330 */
331 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
331 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
332 var cells = this.get_cells();
332 var cells = this.get_cells();
333 time = time || 0;
333 time = time || 0;
334 cell_number = Math.min(cells.length-1,cell_number);
334 cell_number = Math.min(cells.length-1,cell_number);
335 cell_number = Math.max(0 ,cell_number);
335 cell_number = Math.max(0 ,cell_number);
336 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
336 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
337 this.element.animate({scrollTop:scroll_value}, time);
337 this.element.animate({scrollTop:scroll_value}, time);
338 return scroll_value;
338 return scroll_value;
339 };
339 };
340
340
341 /**
341 /**
342 * Scroll to the bottom of the page.
342 * Scroll to the bottom of the page.
343 *
343 *
344 * @method scroll_to_bottom
344 * @method scroll_to_bottom
345 */
345 */
346 Notebook.prototype.scroll_to_bottom = function () {
346 Notebook.prototype.scroll_to_bottom = function () {
347 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
347 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
348 };
348 };
349
349
350 /**
350 /**
351 * Scroll to the top of the page.
351 * Scroll to the top of the page.
352 *
352 *
353 * @method scroll_to_top
353 * @method scroll_to_top
354 */
354 */
355 Notebook.prototype.scroll_to_top = function () {
355 Notebook.prototype.scroll_to_top = function () {
356 this.element.animate({scrollTop:0}, 0);
356 this.element.animate({scrollTop:0}, 0);
357 };
357 };
358
358
359 // Edit Notebook metadata
359 // Edit Notebook metadata
360
360
361 Notebook.prototype.edit_metadata = function () {
361 Notebook.prototype.edit_metadata = function () {
362 var that = this;
362 var that = this;
363 dialog.edit_metadata({
363 dialog.edit_metadata({
364 md: this.metadata,
364 md: this.metadata,
365 callback: function (md) {
365 callback: function (md) {
366 that.metadata = md;
366 that.metadata = md;
367 },
367 },
368 name: 'Notebook',
368 name: 'Notebook',
369 notebook: this,
369 notebook: this,
370 keyboard_manager: this.keyboard_manager});
370 keyboard_manager: this.keyboard_manager});
371 };
371 };
372
372
373 // Cell indexing, retrieval, etc.
373 // Cell indexing, retrieval, etc.
374
374
375 /**
375 /**
376 * Get all cell elements in the notebook.
376 * Get all cell elements in the notebook.
377 *
377 *
378 * @method get_cell_elements
378 * @method get_cell_elements
379 * @return {jQuery} A selector of all cell elements
379 * @return {jQuery} A selector of all cell elements
380 */
380 */
381 Notebook.prototype.get_cell_elements = function () {
381 Notebook.prototype.get_cell_elements = function () {
382 return this.container.children("div.cell");
382 return this.container.children("div.cell");
383 };
383 };
384
384
385 /**
385 /**
386 * Get a particular cell element.
386 * Get a particular cell element.
387 *
387 *
388 * @method get_cell_element
388 * @method get_cell_element
389 * @param {Number} index An index of a cell to select
389 * @param {Number} index An index of a cell to select
390 * @return {jQuery} A selector of the given cell.
390 * @return {jQuery} A selector of the given cell.
391 */
391 */
392 Notebook.prototype.get_cell_element = function (index) {
392 Notebook.prototype.get_cell_element = function (index) {
393 var result = null;
393 var result = null;
394 var e = this.get_cell_elements().eq(index);
394 var e = this.get_cell_elements().eq(index);
395 if (e.length !== 0) {
395 if (e.length !== 0) {
396 result = e;
396 result = e;
397 }
397 }
398 return result;
398 return result;
399 };
399 };
400
400
401 /**
401 /**
402 * Try to get a particular cell by msg_id.
402 * Try to get a particular cell by msg_id.
403 *
403 *
404 * @method get_msg_cell
404 * @method get_msg_cell
405 * @param {String} msg_id A message UUID
405 * @param {String} msg_id A message UUID
406 * @return {Cell} Cell or null if no cell was found.
406 * @return {Cell} Cell or null if no cell was found.
407 */
407 */
408 Notebook.prototype.get_msg_cell = function (msg_id) {
408 Notebook.prototype.get_msg_cell = function (msg_id) {
409 return codecell.CodeCell.msg_cells[msg_id] || null;
409 return codecell.CodeCell.msg_cells[msg_id] || null;
410 };
410 };
411
411
412 /**
412 /**
413 * Count the cells in this notebook.
413 * Count the cells in this notebook.
414 *
414 *
415 * @method ncells
415 * @method ncells
416 * @return {Number} The number of cells in this notebook
416 * @return {Number} The number of cells in this notebook
417 */
417 */
418 Notebook.prototype.ncells = function () {
418 Notebook.prototype.ncells = function () {
419 return this.get_cell_elements().length;
419 return this.get_cell_elements().length;
420 };
420 };
421
421
422 /**
422 /**
423 * Get all Cell objects in this notebook.
423 * Get all Cell objects in this notebook.
424 *
424 *
425 * @method get_cells
425 * @method get_cells
426 * @return {Array} This notebook's Cell objects
426 * @return {Array} This notebook's Cell objects
427 */
427 */
428 // TODO: we are often calling cells as cells()[i], which we should optimize
428 // TODO: we are often calling cells as cells()[i], which we should optimize
429 // to cells(i) or a new method.
429 // to cells(i) or a new method.
430 Notebook.prototype.get_cells = function () {
430 Notebook.prototype.get_cells = function () {
431 return this.get_cell_elements().toArray().map(function (e) {
431 return this.get_cell_elements().toArray().map(function (e) {
432 return $(e).data("cell");
432 return $(e).data("cell");
433 });
433 });
434 };
434 };
435
435
436 /**
436 /**
437 * Get a Cell object from this notebook.
437 * Get a Cell object from this notebook.
438 *
438 *
439 * @method get_cell
439 * @method get_cell
440 * @param {Number} index An index of a cell to retrieve
440 * @param {Number} index An index of a cell to retrieve
441 * @return {Cell} Cell or null if no cell was found.
441 * @return {Cell} Cell or null if no cell was found.
442 */
442 */
443 Notebook.prototype.get_cell = function (index) {
443 Notebook.prototype.get_cell = function (index) {
444 var result = null;
444 var result = null;
445 var ce = this.get_cell_element(index);
445 var ce = this.get_cell_element(index);
446 if (ce !== null) {
446 if (ce !== null) {
447 result = ce.data('cell');
447 result = ce.data('cell');
448 }
448 }
449 return result;
449 return result;
450 };
450 };
451
451
452 /**
452 /**
453 * Get the cell below a given cell.
453 * Get the cell below a given cell.
454 *
454 *
455 * @method get_next_cell
455 * @method get_next_cell
456 * @param {Cell} cell The provided cell
456 * @param {Cell} cell The provided cell
457 * @return {Cell} the next cell or null if no cell was found.
457 * @return {Cell} the next cell or null if no cell was found.
458 */
458 */
459 Notebook.prototype.get_next_cell = function (cell) {
459 Notebook.prototype.get_next_cell = function (cell) {
460 var result = null;
460 var result = null;
461 var index = this.find_cell_index(cell);
461 var index = this.find_cell_index(cell);
462 if (this.is_valid_cell_index(index+1)) {
462 if (this.is_valid_cell_index(index+1)) {
463 result = this.get_cell(index+1);
463 result = this.get_cell(index+1);
464 }
464 }
465 return result;
465 return result;
466 };
466 };
467
467
468 /**
468 /**
469 * Get the cell above a given cell.
469 * Get the cell above a given cell.
470 *
470 *
471 * @method get_prev_cell
471 * @method get_prev_cell
472 * @param {Cell} cell The provided cell
472 * @param {Cell} cell The provided cell
473 * @return {Cell} The previous cell or null if no cell was found.
473 * @return {Cell} The previous cell or null if no cell was found.
474 */
474 */
475 Notebook.prototype.get_prev_cell = function (cell) {
475 Notebook.prototype.get_prev_cell = function (cell) {
476 var result = null;
476 var result = null;
477 var index = this.find_cell_index(cell);
477 var index = this.find_cell_index(cell);
478 if (index !== null && index > 0) {
478 if (index !== null && index > 0) {
479 result = this.get_cell(index-1);
479 result = this.get_cell(index-1);
480 }
480 }
481 return result;
481 return result;
482 };
482 };
483
483
484 /**
484 /**
485 * Get the numeric index of a given cell.
485 * Get the numeric index of a given cell.
486 *
486 *
487 * @method find_cell_index
487 * @method find_cell_index
488 * @param {Cell} cell The provided cell
488 * @param {Cell} cell The provided cell
489 * @return {Number} The cell's numeric index or null if no cell was found.
489 * @return {Number} The cell's numeric index or null if no cell was found.
490 */
490 */
491 Notebook.prototype.find_cell_index = function (cell) {
491 Notebook.prototype.find_cell_index = function (cell) {
492 var result = null;
492 var result = null;
493 this.get_cell_elements().filter(function (index) {
493 this.get_cell_elements().filter(function (index) {
494 if ($(this).data("cell") === cell) {
494 if ($(this).data("cell") === cell) {
495 result = index;
495 result = index;
496 }
496 }
497 });
497 });
498 return result;
498 return result;
499 };
499 };
500
500
501 /**
501 /**
502 * Get a given index , or the selected index if none is provided.
502 * Get a given index , or the selected index if none is provided.
503 *
503 *
504 * @method index_or_selected
504 * @method index_or_selected
505 * @param {Number} index A cell's index
505 * @param {Number} index A cell's index
506 * @return {Number} The given index, or selected index if none is provided.
506 * @return {Number} The given index, or selected index if none is provided.
507 */
507 */
508 Notebook.prototype.index_or_selected = function (index) {
508 Notebook.prototype.index_or_selected = function (index) {
509 var i;
509 var i;
510 if (index === undefined || index === null) {
510 if (index === undefined || index === null) {
511 i = this.get_selected_index();
511 i = this.get_selected_index();
512 if (i === null) {
512 if (i === null) {
513 i = 0;
513 i = 0;
514 }
514 }
515 } else {
515 } else {
516 i = index;
516 i = index;
517 }
517 }
518 return i;
518 return i;
519 };
519 };
520
520
521 /**
521 /**
522 * Get the currently selected cell.
522 * Get the currently selected cell.
523 * @method get_selected_cell
523 * @method get_selected_cell
524 * @return {Cell} The selected cell
524 * @return {Cell} The selected cell
525 */
525 */
526 Notebook.prototype.get_selected_cell = function () {
526 Notebook.prototype.get_selected_cell = function () {
527 var index = this.get_selected_index();
527 var index = this.get_selected_index();
528 return this.get_cell(index);
528 return this.get_cell(index);
529 };
529 };
530
530
531 /**
531 /**
532 * Check whether a cell index is valid.
532 * Check whether a cell index is valid.
533 *
533 *
534 * @method is_valid_cell_index
534 * @method is_valid_cell_index
535 * @param {Number} index A cell index
535 * @param {Number} index A cell index
536 * @return True if the index is valid, false otherwise
536 * @return True if the index is valid, false otherwise
537 */
537 */
538 Notebook.prototype.is_valid_cell_index = function (index) {
538 Notebook.prototype.is_valid_cell_index = function (index) {
539 if (index !== null && index >= 0 && index < this.ncells()) {
539 if (index !== null && index >= 0 && index < this.ncells()) {
540 return true;
540 return true;
541 } else {
541 } else {
542 return false;
542 return false;
543 }
543 }
544 };
544 };
545
545
546 /**
546 /**
547 * Get the index of the currently selected cell.
547 * Get the index of the currently selected cell.
548
548
549 * @method get_selected_index
549 * @method get_selected_index
550 * @return {Number} The selected cell's numeric index
550 * @return {Number} The selected cell's numeric index
551 */
551 */
552 Notebook.prototype.get_selected_index = function () {
552 Notebook.prototype.get_selected_index = function () {
553 var result = null;
553 var result = null;
554 this.get_cell_elements().filter(function (index) {
554 this.get_cell_elements().filter(function (index) {
555 if ($(this).data("cell").selected === true) {
555 if ($(this).data("cell").selected === true) {
556 result = index;
556 result = index;
557 }
557 }
558 });
558 });
559 return result;
559 return result;
560 };
560 };
561
561
562
562
563 // Cell selection.
563 // Cell selection.
564
564
565 /**
565 /**
566 * Programmatically select a cell.
566 * Programmatically select a cell.
567 *
567 *
568 * @method select
568 * @method select
569 * @param {Number} index A cell's index
569 * @param {Number} index A cell's index
570 * @return {Notebook} This notebook
570 * @return {Notebook} This notebook
571 */
571 */
572 Notebook.prototype.select = function (index) {
572 Notebook.prototype.select = function (index) {
573 if (this.is_valid_cell_index(index)) {
573 if (this.is_valid_cell_index(index)) {
574 var sindex = this.get_selected_index();
574 var sindex = this.get_selected_index();
575 if (sindex !== null && index !== sindex) {
575 if (sindex !== null && index !== sindex) {
576 // If we are about to select a different cell, make sure we are
576 // If we are about to select a different cell, make sure we are
577 // first in command mode.
577 // first in command mode.
578 if (this.mode !== 'command') {
578 if (this.mode !== 'command') {
579 this.command_mode();
579 this.command_mode();
580 }
580 }
581 this.get_cell(sindex).unselect();
581 this.get_cell(sindex).unselect();
582 }
582 }
583 var cell = this.get_cell(index);
583 var cell = this.get_cell(index);
584 cell.select();
584 cell.select();
585 if (cell.cell_type === 'heading') {
585 if (cell.cell_type === 'heading') {
586 this.events.trigger('selected_cell_type_changed.Notebook',
586 this.events.trigger('selected_cell_type_changed.Notebook',
587 {'cell_type':cell.cell_type,level:cell.level}
587 {'cell_type':cell.cell_type,level:cell.level}
588 );
588 );
589 } else {
589 } else {
590 this.events.trigger('selected_cell_type_changed.Notebook',
590 this.events.trigger('selected_cell_type_changed.Notebook',
591 {'cell_type':cell.cell_type}
591 {'cell_type':cell.cell_type}
592 );
592 );
593 }
593 }
594 }
594 }
595 return this;
595 return this;
596 };
596 };
597
597
598 /**
598 /**
599 * Programmatically select the next cell.
599 * Programmatically select the next cell.
600 *
600 *
601 * @method select_next
601 * @method select_next
602 * @return {Notebook} This notebook
602 * @return {Notebook} This notebook
603 */
603 */
604 Notebook.prototype.select_next = function () {
604 Notebook.prototype.select_next = function () {
605 var index = this.get_selected_index();
605 var index = this.get_selected_index();
606 this.select(index+1);
606 this.select(index+1);
607 return this;
607 return this;
608 };
608 };
609
609
610 /**
610 /**
611 * Programmatically select the previous cell.
611 * Programmatically select the previous cell.
612 *
612 *
613 * @method select_prev
613 * @method select_prev
614 * @return {Notebook} This notebook
614 * @return {Notebook} This notebook
615 */
615 */
616 Notebook.prototype.select_prev = function () {
616 Notebook.prototype.select_prev = function () {
617 var index = this.get_selected_index();
617 var index = this.get_selected_index();
618 this.select(index-1);
618 this.select(index-1);
619 return this;
619 return this;
620 };
620 };
621
621
622
622
623 // Edit/Command mode
623 // Edit/Command mode
624
624
625 /**
625 /**
626 * Gets the index of the cell that is in edit mode.
626 * Gets the index of the cell that is in edit mode.
627 *
627 *
628 * @method get_edit_index
628 * @method get_edit_index
629 *
629 *
630 * @return index {int}
630 * @return index {int}
631 **/
631 **/
632 Notebook.prototype.get_edit_index = function () {
632 Notebook.prototype.get_edit_index = function () {
633 var result = null;
633 var result = null;
634 this.get_cell_elements().filter(function (index) {
634 this.get_cell_elements().filter(function (index) {
635 if ($(this).data("cell").mode === 'edit') {
635 if ($(this).data("cell").mode === 'edit') {
636 result = index;
636 result = index;
637 }
637 }
638 });
638 });
639 return result;
639 return result;
640 };
640 };
641
641
642 /**
642 /**
643 * Handle when a a cell blurs and the notebook should enter command mode.
643 * Handle when a a cell blurs and the notebook should enter command mode.
644 *
644 *
645 * @method handle_command_mode
645 * @method handle_command_mode
646 * @param [cell] {Cell} Cell to enter command mode on.
646 * @param [cell] {Cell} Cell to enter command mode on.
647 **/
647 **/
648 Notebook.prototype.handle_command_mode = function (cell) {
648 Notebook.prototype.handle_command_mode = function (cell) {
649 if (this.mode !== 'command') {
649 if (this.mode !== 'command') {
650 cell.command_mode();
650 cell.command_mode();
651 this.mode = 'command';
651 this.mode = 'command';
652 this.events.trigger('command_mode.Notebook');
652 this.events.trigger('command_mode.Notebook');
653 this.keyboard_manager.command_mode();
653 this.keyboard_manager.command_mode();
654 }
654 }
655 };
655 };
656
656
657 /**
657 /**
658 * Make the notebook enter command mode.
658 * Make the notebook enter command mode.
659 *
659 *
660 * @method command_mode
660 * @method command_mode
661 **/
661 **/
662 Notebook.prototype.command_mode = function () {
662 Notebook.prototype.command_mode = function () {
663 var cell = this.get_cell(this.get_edit_index());
663 var cell = this.get_cell(this.get_edit_index());
664 if (cell && this.mode !== 'command') {
664 if (cell && this.mode !== 'command') {
665 // We don't call cell.command_mode, but rather call cell.focus_cell()
665 // We don't call cell.command_mode, but rather call cell.focus_cell()
666 // which will blur and CM editor and trigger the call to
666 // which will blur and CM editor and trigger the call to
667 // handle_command_mode.
667 // handle_command_mode.
668 cell.focus_cell();
668 cell.focus_cell();
669 }
669 }
670 };
670 };
671
671
672 /**
672 /**
673 * Handle when a cell fires it's edit_mode event.
673 * Handle when a cell fires it's edit_mode event.
674 *
674 *
675 * @method handle_edit_mode
675 * @method handle_edit_mode
676 * @param [cell] {Cell} Cell to enter edit mode on.
676 * @param [cell] {Cell} Cell to enter edit mode on.
677 **/
677 **/
678 Notebook.prototype.handle_edit_mode = function (cell) {
678 Notebook.prototype.handle_edit_mode = function (cell) {
679 if (cell && this.mode !== 'edit') {
679 if (cell && this.mode !== 'edit') {
680 cell.edit_mode();
680 cell.edit_mode();
681 this.mode = 'edit';
681 this.mode = 'edit';
682 this.events.trigger('edit_mode.Notebook');
682 this.events.trigger('edit_mode.Notebook');
683 this.keyboard_manager.edit_mode();
683 this.keyboard_manager.edit_mode();
684 }
684 }
685 };
685 };
686
686
687 /**
687 /**
688 * Make a cell enter edit mode.
688 * Make a cell enter edit mode.
689 *
689 *
690 * @method edit_mode
690 * @method edit_mode
691 **/
691 **/
692 Notebook.prototype.edit_mode = function () {
692 Notebook.prototype.edit_mode = function () {
693 var cell = this.get_selected_cell();
693 var cell = this.get_selected_cell();
694 if (cell && this.mode !== 'edit') {
694 if (cell && this.mode !== 'edit') {
695 cell.unrender();
695 cell.unrender();
696 cell.focus_editor();
696 cell.focus_editor();
697 }
697 }
698 };
698 };
699
699
700 /**
700 /**
701 * Focus the currently selected cell.
701 * Focus the currently selected cell.
702 *
702 *
703 * @method focus_cell
703 * @method focus_cell
704 **/
704 **/
705 Notebook.prototype.focus_cell = function () {
705 Notebook.prototype.focus_cell = function () {
706 var cell = this.get_selected_cell();
706 var cell = this.get_selected_cell();
707 if (cell === null) {return;} // No cell is selected
707 if (cell === null) {return;} // No cell is selected
708 cell.focus_cell();
708 cell.focus_cell();
709 };
709 };
710
710
711 // Cell movement
711 // Cell movement
712
712
713 /**
713 /**
714 * Move given (or selected) cell up and select it.
714 * Move given (or selected) cell up and select it.
715 *
715 *
716 * @method move_cell_up
716 * @method move_cell_up
717 * @param [index] {integer} cell index
717 * @param [index] {integer} cell index
718 * @return {Notebook} This notebook
718 * @return {Notebook} This notebook
719 **/
719 **/
720 Notebook.prototype.move_cell_up = function (index) {
720 Notebook.prototype.move_cell_up = function (index) {
721 var i = this.index_or_selected(index);
721 var i = this.index_or_selected(index);
722 if (this.is_valid_cell_index(i) && i > 0) {
722 if (this.is_valid_cell_index(i) && i > 0) {
723 var pivot = this.get_cell_element(i-1);
723 var pivot = this.get_cell_element(i-1);
724 var tomove = this.get_cell_element(i);
724 var tomove = this.get_cell_element(i);
725 if (pivot !== null && tomove !== null) {
725 if (pivot !== null && tomove !== null) {
726 tomove.detach();
726 tomove.detach();
727 pivot.before(tomove);
727 pivot.before(tomove);
728 this.select(i-1);
728 this.select(i-1);
729 var cell = this.get_selected_cell();
729 var cell = this.get_selected_cell();
730 cell.focus_cell();
730 cell.focus_cell();
731 }
731 }
732 this.set_dirty(true);
732 this.set_dirty(true);
733 }
733 }
734 return this;
734 return this;
735 };
735 };
736
736
737
737
738 /**
738 /**
739 * Move given (or selected) cell down and select it
739 * Move given (or selected) cell down and select it
740 *
740 *
741 * @method move_cell_down
741 * @method move_cell_down
742 * @param [index] {integer} cell index
742 * @param [index] {integer} cell index
743 * @return {Notebook} This notebook
743 * @return {Notebook} This notebook
744 **/
744 **/
745 Notebook.prototype.move_cell_down = function (index) {
745 Notebook.prototype.move_cell_down = function (index) {
746 var i = this.index_or_selected(index);
746 var i = this.index_or_selected(index);
747 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
747 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
748 var pivot = this.get_cell_element(i+1);
748 var pivot = this.get_cell_element(i+1);
749 var tomove = this.get_cell_element(i);
749 var tomove = this.get_cell_element(i);
750 if (pivot !== null && tomove !== null) {
750 if (pivot !== null && tomove !== null) {
751 tomove.detach();
751 tomove.detach();
752 pivot.after(tomove);
752 pivot.after(tomove);
753 this.select(i+1);
753 this.select(i+1);
754 var cell = this.get_selected_cell();
754 var cell = this.get_selected_cell();
755 cell.focus_cell();
755 cell.focus_cell();
756 }
756 }
757 }
757 }
758 this.set_dirty();
758 this.set_dirty();
759 return this;
759 return this;
760 };
760 };
761
761
762
762
763 // Insertion, deletion.
763 // Insertion, deletion.
764
764
765 /**
765 /**
766 * Delete a cell from the notebook.
766 * Delete a cell from the notebook.
767 *
767 *
768 * @method delete_cell
768 * @method delete_cell
769 * @param [index] A cell's numeric index
769 * @param [index] A cell's numeric index
770 * @return {Notebook} This notebook
770 * @return {Notebook} This notebook
771 */
771 */
772 Notebook.prototype.delete_cell = function (index) {
772 Notebook.prototype.delete_cell = function (index) {
773 var i = this.index_or_selected(index);
773 var i = this.index_or_selected(index);
774 var cell = this.get_cell(i);
774 var cell = this.get_cell(i);
775 if (!cell.is_deletable()) {
775 if (!cell.is_deletable()) {
776 return this;
776 return this;
777 }
777 }
778
778
779 this.undelete_backup = cell.toJSON();
779 this.undelete_backup = cell.toJSON();
780 $('#undelete_cell').removeClass('disabled');
780 $('#undelete_cell').removeClass('disabled');
781 if (this.is_valid_cell_index(i)) {
781 if (this.is_valid_cell_index(i)) {
782 var old_ncells = this.ncells();
782 var old_ncells = this.ncells();
783 var ce = this.get_cell_element(i);
783 var ce = this.get_cell_element(i);
784 ce.remove();
784 ce.remove();
785 if (i === 0) {
785 if (i === 0) {
786 // Always make sure we have at least one cell.
786 // Always make sure we have at least one cell.
787 if (old_ncells === 1) {
787 if (old_ncells === 1) {
788 this.insert_cell_below('code');
788 this.insert_cell_below('code');
789 }
789 }
790 this.select(0);
790 this.select(0);
791 this.undelete_index = 0;
791 this.undelete_index = 0;
792 this.undelete_below = false;
792 this.undelete_below = false;
793 } else if (i === old_ncells-1 && i !== 0) {
793 } else if (i === old_ncells-1 && i !== 0) {
794 this.select(i-1);
794 this.select(i-1);
795 this.undelete_index = i - 1;
795 this.undelete_index = i - 1;
796 this.undelete_below = true;
796 this.undelete_below = true;
797 } else {
797 } else {
798 this.select(i);
798 this.select(i);
799 this.undelete_index = i;
799 this.undelete_index = i;
800 this.undelete_below = false;
800 this.undelete_below = false;
801 }
801 }
802 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
802 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
803 this.set_dirty(true);
803 this.set_dirty(true);
804 }
804 }
805 return this;
805 return this;
806 };
806 };
807
807
808 /**
808 /**
809 * Restore the most recently deleted cell.
809 * Restore the most recently deleted cell.
810 *
810 *
811 * @method undelete
811 * @method undelete
812 */
812 */
813 Notebook.prototype.undelete_cell = function() {
813 Notebook.prototype.undelete_cell = function() {
814 if (this.undelete_backup !== null && this.undelete_index !== null) {
814 if (this.undelete_backup !== null && this.undelete_index !== null) {
815 var current_index = this.get_selected_index();
815 var current_index = this.get_selected_index();
816 if (this.undelete_index < current_index) {
816 if (this.undelete_index < current_index) {
817 current_index = current_index + 1;
817 current_index = current_index + 1;
818 }
818 }
819 if (this.undelete_index >= this.ncells()) {
819 if (this.undelete_index >= this.ncells()) {
820 this.select(this.ncells() - 1);
820 this.select(this.ncells() - 1);
821 }
821 }
822 else {
822 else {
823 this.select(this.undelete_index);
823 this.select(this.undelete_index);
824 }
824 }
825 var cell_data = this.undelete_backup;
825 var cell_data = this.undelete_backup;
826 var new_cell = null;
826 var new_cell = null;
827 if (this.undelete_below) {
827 if (this.undelete_below) {
828 new_cell = this.insert_cell_below(cell_data.cell_type);
828 new_cell = this.insert_cell_below(cell_data.cell_type);
829 } else {
829 } else {
830 new_cell = this.insert_cell_above(cell_data.cell_type);
830 new_cell = this.insert_cell_above(cell_data.cell_type);
831 }
831 }
832 new_cell.fromJSON(cell_data);
832 new_cell.fromJSON(cell_data);
833 if (this.undelete_below) {
833 if (this.undelete_below) {
834 this.select(current_index+1);
834 this.select(current_index+1);
835 } else {
835 } else {
836 this.select(current_index);
836 this.select(current_index);
837 }
837 }
838 this.undelete_backup = null;
838 this.undelete_backup = null;
839 this.undelete_index = null;
839 this.undelete_index = null;
840 }
840 }
841 $('#undelete_cell').addClass('disabled');
841 $('#undelete_cell').addClass('disabled');
842 };
842 };
843
843
844 /**
844 /**
845 * Insert a cell so that after insertion the cell is at given index.
845 * Insert a cell so that after insertion the cell is at given index.
846 *
846 *
847 * If cell type is not provided, it will default to the type of the
847 * If cell type is not provided, it will default to the type of the
848 * currently active cell.
848 * currently active cell.
849 *
849 *
850 * Similar to insert_above, but index parameter is mandatory
850 * Similar to insert_above, but index parameter is mandatory
851 *
851 *
852 * Index will be brought back into the accessible range [0,n]
852 * Index will be brought back into the accessible range [0,n]
853 *
853 *
854 * @method insert_cell_at_index
854 * @method insert_cell_at_index
855 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
855 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
856 * @param [index] {int} a valid index where to insert cell
856 * @param [index] {int} a valid index where to insert cell
857 *
857 *
858 * @return cell {cell|null} created cell or null
858 * @return cell {cell|null} created cell or null
859 **/
859 **/
860 Notebook.prototype.insert_cell_at_index = function(type, index){
860 Notebook.prototype.insert_cell_at_index = function(type, index){
861
861
862 var ncells = this.ncells();
862 var ncells = this.ncells();
863 index = Math.min(index, ncells);
863 index = Math.min(index, ncells);
864 index = Math.max(index, 0);
864 index = Math.max(index, 0);
865 var cell = null;
865 var cell = null;
866 type = type || this.default_cell_type;
866 type = type || this.default_cell_type;
867 if (type === 'above') {
867 if (type === 'above') {
868 if (index > 0) {
868 if (index > 0) {
869 type = this.get_cell(index-1).cell_type;
869 type = this.get_cell(index-1).cell_type;
870 } else {
870 } else {
871 type = 'code';
871 type = 'code';
872 }
872 }
873 } else if (type === 'below') {
873 } else if (type === 'below') {
874 if (index < ncells) {
874 if (index < ncells) {
875 type = this.get_cell(index).cell_type;
875 type = this.get_cell(index).cell_type;
876 } else {
876 } else {
877 type = 'code';
877 type = 'code';
878 }
878 }
879 } else if (type === 'selected') {
879 } else if (type === 'selected') {
880 type = this.get_selected_cell().cell_type;
880 type = this.get_selected_cell().cell_type;
881 }
881 }
882
882
883 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
883 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
884 var cell_options = {
884 var cell_options = {
885 events: this.events,
885 events: this.events,
886 config: this.config,
886 config: this.config,
887 keyboard_manager: this.keyboard_manager,
887 keyboard_manager: this.keyboard_manager,
888 notebook: this,
888 notebook: this,
889 tooltip: this.tooltip
889 tooltip: this.tooltip
890 };
890 };
891 switch(type) {
891 switch(type) {
892 case 'code':
892 case 'code':
893 cell = new codecell.CodeCell(this.kernel, cell_options);
893 cell = new codecell.CodeCell(this.kernel, cell_options);
894 cell.set_input_prompt();
894 cell.set_input_prompt();
895 break;
895 break;
896 case 'markdown':
896 case 'markdown':
897 cell = new textcell.MarkdownCell(cell_options);
897 cell = new textcell.MarkdownCell(cell_options);
898 break;
898 break;
899 case 'raw':
899 case 'raw':
900 cell = new textcell.RawCell(cell_options);
900 cell = new textcell.RawCell(cell_options);
901 break;
901 break;
902 default:
902 default:
903 console.log("invalid cell type: ", type);
903 console.log("invalid cell type: ", type);
904 }
904 }
905
905
906 if(this._insert_element_at_index(cell.element,index)) {
906 if(this._insert_element_at_index(cell.element,index)) {
907 cell.render();
907 cell.render();
908 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
908 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
909 cell.refresh();
909 cell.refresh();
910 // We used to select the cell after we refresh it, but there
910 // We used to select the cell after we refresh it, but there
911 // are now cases were this method is called where select is
911 // are now cases were this method is called where select is
912 // not appropriate. The selection logic should be handled by the
912 // not appropriate. The selection logic should be handled by the
913 // caller of the the top level insert_cell methods.
913 // caller of the the top level insert_cell methods.
914 this.set_dirty(true);
914 this.set_dirty(true);
915 }
915 }
916 }
916 }
917 return cell;
917 return cell;
918
918
919 };
919 };
920
920
921 /**
921 /**
922 * Insert an element at given cell index.
922 * Insert an element at given cell index.
923 *
923 *
924 * @method _insert_element_at_index
924 * @method _insert_element_at_index
925 * @param element {dom_element} a cell element
925 * @param element {dom_element} a cell element
926 * @param [index] {int} a valid index where to inser cell
926 * @param [index] {int} a valid index where to inser cell
927 * @private
927 * @private
928 *
928 *
929 * return true if everything whent fine.
929 * return true if everything whent fine.
930 **/
930 **/
931 Notebook.prototype._insert_element_at_index = function(element, index){
931 Notebook.prototype._insert_element_at_index = function(element, index){
932 if (element === undefined){
932 if (element === undefined){
933 return false;
933 return false;
934 }
934 }
935
935
936 var ncells = this.ncells();
936 var ncells = this.ncells();
937
937
938 if (ncells === 0) {
938 if (ncells === 0) {
939 // special case append if empty
939 // special case append if empty
940 this.element.find('div.end_space').before(element);
940 this.element.find('div.end_space').before(element);
941 } else if ( ncells === index ) {
941 } else if ( ncells === index ) {
942 // special case append it the end, but not empty
942 // special case append it the end, but not empty
943 this.get_cell_element(index-1).after(element);
943 this.get_cell_element(index-1).after(element);
944 } else if (this.is_valid_cell_index(index)) {
944 } else if (this.is_valid_cell_index(index)) {
945 // otherwise always somewhere to append to
945 // otherwise always somewhere to append to
946 this.get_cell_element(index).before(element);
946 this.get_cell_element(index).before(element);
947 } else {
947 } else {
948 return false;
948 return false;
949 }
949 }
950
950
951 if (this.undelete_index !== null && index <= this.undelete_index) {
951 if (this.undelete_index !== null && index <= this.undelete_index) {
952 this.undelete_index = this.undelete_index + 1;
952 this.undelete_index = this.undelete_index + 1;
953 this.set_dirty(true);
953 this.set_dirty(true);
954 }
954 }
955 return true;
955 return true;
956 };
956 };
957
957
958 /**
958 /**
959 * Insert a cell of given type above given index, or at top
959 * Insert a cell of given type above given index, or at top
960 * of notebook if index smaller than 0.
960 * of notebook if index smaller than 0.
961 *
961 *
962 * default index value is the one of currently selected cell
962 * default index value is the one of currently selected cell
963 *
963 *
964 * @method insert_cell_above
964 * @method insert_cell_above
965 * @param [type] {string} cell type
965 * @param [type] {string} cell type
966 * @param [index] {integer}
966 * @param [index] {integer}
967 *
967 *
968 * @return handle to created cell or null
968 * @return handle to created cell or null
969 **/
969 **/
970 Notebook.prototype.insert_cell_above = function (type, index) {
970 Notebook.prototype.insert_cell_above = function (type, index) {
971 index = this.index_or_selected(index);
971 index = this.index_or_selected(index);
972 return this.insert_cell_at_index(type, index);
972 return this.insert_cell_at_index(type, index);
973 };
973 };
974
974
975 /**
975 /**
976 * Insert a cell of given type below given index, or at bottom
976 * Insert a cell of given type below given index, or at bottom
977 * of notebook if index greater than number of cells
977 * of notebook if index greater than number of cells
978 *
978 *
979 * default index value is the one of currently selected cell
979 * default index value is the one of currently selected cell
980 *
980 *
981 * @method insert_cell_below
981 * @method insert_cell_below
982 * @param [type] {string} cell type
982 * @param [type] {string} cell type
983 * @param [index] {integer}
983 * @param [index] {integer}
984 *
984 *
985 * @return handle to created cell or null
985 * @return handle to created cell or null
986 *
986 *
987 **/
987 **/
988 Notebook.prototype.insert_cell_below = function (type, index) {
988 Notebook.prototype.insert_cell_below = function (type, index) {
989 index = this.index_or_selected(index);
989 index = this.index_or_selected(index);
990 return this.insert_cell_at_index(type, index+1);
990 return this.insert_cell_at_index(type, index+1);
991 };
991 };
992
992
993
993
994 /**
994 /**
995 * Insert cell at end of notebook
995 * Insert cell at end of notebook
996 *
996 *
997 * @method insert_cell_at_bottom
997 * @method insert_cell_at_bottom
998 * @param {String} type cell type
998 * @param {String} type cell type
999 *
999 *
1000 * @return the added cell; or null
1000 * @return the added cell; or null
1001 **/
1001 **/
1002 Notebook.prototype.insert_cell_at_bottom = function (type){
1002 Notebook.prototype.insert_cell_at_bottom = function (type){
1003 var len = this.ncells();
1003 var len = this.ncells();
1004 return this.insert_cell_below(type,len-1);
1004 return this.insert_cell_below(type,len-1);
1005 };
1005 };
1006
1006
1007 /**
1007 /**
1008 * Turn a cell into a code cell.
1008 * Turn a cell into a code cell.
1009 *
1009 *
1010 * @method to_code
1010 * @method to_code
1011 * @param {Number} [index] A cell's index
1011 * @param {Number} [index] A cell's index
1012 */
1012 */
1013 Notebook.prototype.to_code = function (index) {
1013 Notebook.prototype.to_code = function (index) {
1014 var i = this.index_or_selected(index);
1014 var i = this.index_or_selected(index);
1015 if (this.is_valid_cell_index(i)) {
1015 if (this.is_valid_cell_index(i)) {
1016 var source_cell = this.get_cell(i);
1016 var source_cell = this.get_cell(i);
1017 if (!(source_cell instanceof codecell.CodeCell)) {
1017 if (!(source_cell instanceof codecell.CodeCell)) {
1018 var target_cell = this.insert_cell_below('code',i);
1018 var target_cell = this.insert_cell_below('code',i);
1019 var text = source_cell.get_text();
1019 var text = source_cell.get_text();
1020 if (text === source_cell.placeholder) {
1020 if (text === source_cell.placeholder) {
1021 text = '';
1021 text = '';
1022 }
1022 }
1023 //metadata
1023 //metadata
1024 target_cell.metadata = source_cell.metadata;
1024 target_cell.metadata = source_cell.metadata;
1025
1025
1026 target_cell.set_text(text);
1026 target_cell.set_text(text);
1027 // make this value the starting point, so that we can only undo
1027 // make this value the starting point, so that we can only undo
1028 // to this state, instead of a blank cell
1028 // to this state, instead of a blank cell
1029 target_cell.code_mirror.clearHistory();
1029 target_cell.code_mirror.clearHistory();
1030 source_cell.element.remove();
1030 source_cell.element.remove();
1031 this.select(i);
1031 this.select(i);
1032 var cursor = source_cell.code_mirror.getCursor();
1032 var cursor = source_cell.code_mirror.getCursor();
1033 target_cell.code_mirror.setCursor(cursor);
1033 target_cell.code_mirror.setCursor(cursor);
1034 this.set_dirty(true);
1034 this.set_dirty(true);
1035 }
1035 }
1036 }
1036 }
1037 };
1037 };
1038
1038
1039 /**
1039 /**
1040 * Turn a cell into a Markdown cell.
1040 * Turn a cell into a Markdown cell.
1041 *
1041 *
1042 * @method to_markdown
1042 * @method to_markdown
1043 * @param {Number} [index] A cell's index
1043 * @param {Number} [index] A cell's index
1044 */
1044 */
1045 Notebook.prototype.to_markdown = function (index) {
1045 Notebook.prototype.to_markdown = function (index) {
1046 var i = this.index_or_selected(index);
1046 var i = this.index_or_selected(index);
1047 if (this.is_valid_cell_index(i)) {
1047 if (this.is_valid_cell_index(i)) {
1048 var source_cell = this.get_cell(i);
1048 var source_cell = this.get_cell(i);
1049
1049
1050 if (!(source_cell instanceof textcell.MarkdownCell)) {
1050 if (!(source_cell instanceof textcell.MarkdownCell)) {
1051 var target_cell = this.insert_cell_below('markdown',i);
1051 var target_cell = this.insert_cell_below('markdown',i);
1052 var text = source_cell.get_text();
1052 var text = source_cell.get_text();
1053
1053
1054 if (text === source_cell.placeholder) {
1054 if (text === source_cell.placeholder) {
1055 text = '';
1055 text = '';
1056 }
1056 }
1057 // metadata
1057 // metadata
1058 target_cell.metadata = source_cell.metadata;
1058 target_cell.metadata = source_cell.metadata;
1059 // We must show the editor before setting its contents
1059 // We must show the editor before setting its contents
1060 target_cell.unrender();
1060 target_cell.unrender();
1061 target_cell.set_text(text);
1061 target_cell.set_text(text);
1062 // make this value the starting point, so that we can only undo
1062 // make this value the starting point, so that we can only undo
1063 // to this state, instead of a blank cell
1063 // to this state, instead of a blank cell
1064 target_cell.code_mirror.clearHistory();
1064 target_cell.code_mirror.clearHistory();
1065 source_cell.element.remove();
1065 source_cell.element.remove();
1066 this.select(i);
1066 this.select(i);
1067 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1067 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1068 target_cell.render();
1068 target_cell.render();
1069 }
1069 }
1070 var cursor = source_cell.code_mirror.getCursor();
1070 var cursor = source_cell.code_mirror.getCursor();
1071 target_cell.code_mirror.setCursor(cursor);
1071 target_cell.code_mirror.setCursor(cursor);
1072 this.set_dirty(true);
1072 this.set_dirty(true);
1073 }
1073 }
1074 }
1074 }
1075 };
1075 };
1076
1076
1077 /**
1077 /**
1078 * Turn a cell into a raw text cell.
1078 * Turn a cell into a raw text cell.
1079 *
1079 *
1080 * @method to_raw
1080 * @method to_raw
1081 * @param {Number} [index] A cell's index
1081 * @param {Number} [index] A cell's index
1082 */
1082 */
1083 Notebook.prototype.to_raw = function (index) {
1083 Notebook.prototype.to_raw = function (index) {
1084 var i = this.index_or_selected(index);
1084 var i = this.index_or_selected(index);
1085 if (this.is_valid_cell_index(i)) {
1085 if (this.is_valid_cell_index(i)) {
1086 var target_cell = null;
1086 var target_cell = null;
1087 var source_cell = this.get_cell(i);
1087 var source_cell = this.get_cell(i);
1088
1088
1089 if (!(source_cell instanceof textcell.RawCell)) {
1089 if (!(source_cell instanceof textcell.RawCell)) {
1090 target_cell = this.insert_cell_below('raw',i);
1090 target_cell = this.insert_cell_below('raw',i);
1091 var text = source_cell.get_text();
1091 var text = source_cell.get_text();
1092 if (text === source_cell.placeholder) {
1092 if (text === source_cell.placeholder) {
1093 text = '';
1093 text = '';
1094 }
1094 }
1095 //metadata
1095 //metadata
1096 target_cell.metadata = source_cell.metadata;
1096 target_cell.metadata = source_cell.metadata;
1097 // We must show the editor before setting its contents
1097 // We must show the editor before setting its contents
1098 target_cell.unrender();
1098 target_cell.unrender();
1099 target_cell.set_text(text);
1099 target_cell.set_text(text);
1100 // make this value the starting point, so that we can only undo
1100 // make this value the starting point, so that we can only undo
1101 // to this state, instead of a blank cell
1101 // to this state, instead of a blank cell
1102 target_cell.code_mirror.clearHistory();
1102 target_cell.code_mirror.clearHistory();
1103 source_cell.element.remove();
1103 source_cell.element.remove();
1104 this.select(i);
1104 this.select(i);
1105 var cursor = source_cell.code_mirror.getCursor();
1105 var cursor = source_cell.code_mirror.getCursor();
1106 target_cell.code_mirror.setCursor(cursor);
1106 target_cell.code_mirror.setCursor(cursor);
1107 this.set_dirty(true);
1107 this.set_dirty(true);
1108 }
1108 }
1109 }
1109 }
1110 };
1110 };
1111
1111
1112 Notebook.prototype._warn_heading = function () {
1112 Notebook.prototype._warn_heading = function () {
1113 // warn about heading cells being removed
1113 // warn about heading cells being removed
1114 dialog.modal({
1114 dialog.modal({
1115 notebook: this,
1115 notebook: this,
1116 keyboard_manager: this.keyboard_manager,
1116 keyboard_manager: this.keyboard_manager,
1117 title : "Use markdown headings",
1117 title : "Use markdown headings",
1118 body : $("<p/>").text(
1118 body : $("<p/>").text(
1119 'IPython no longer uses special heading cells. ' +
1119 'IPython no longer uses special heading cells. ' +
1120 'Instead, write your headings in Markdown cells using # characters:'
1120 'Instead, write your headings in Markdown cells using # characters:'
1121 ).append($('<pre/>').text(
1121 ).append($('<pre/>').text(
1122 '## This is a level 2 heading'
1122 '## This is a level 2 heading'
1123 )),
1123 )),
1124 buttons : {
1124 buttons : {
1125 "OK" : {}
1125 "OK" : {}
1126 }
1126 }
1127 });
1127 });
1128 };
1128 };
1129
1129
1130 /**
1130 /**
1131 * Turn a cell into a markdown cell with a heading.
1131 * Turn a cell into a markdown cell with a heading.
1132 *
1132 *
1133 * @method to_heading
1133 * @method to_heading
1134 * @param {Number} [index] A cell's index
1134 * @param {Number} [index] A cell's index
1135 * @param {Number} [level] A heading level (e.g., 1 for h1)
1135 * @param {Number} [level] A heading level (e.g., 1 for h1)
1136 */
1136 */
1137 Notebook.prototype.to_heading = function (index, level) {
1137 Notebook.prototype.to_heading = function (index, level) {
1138 this.to_markdown(index);
1138 this.to_markdown(index);
1139 level = level || 1;
1139 level = level || 1;
1140 var i = this.index_or_selected(index);
1140 var i = this.index_or_selected(index);
1141 if (this.is_valid_cell_index(i)) {
1141 if (this.is_valid_cell_index(i)) {
1142 var cell = this.get_cell(i);
1142 var cell = this.get_cell(i);
1143 cell.set_heading_level(level);
1143 cell.set_heading_level(level);
1144 this.set_dirty(true);
1144 this.set_dirty(true);
1145 }
1145 }
1146 };
1146 };
1147
1147
1148
1148
1149 // Cut/Copy/Paste
1149 // Cut/Copy/Paste
1150
1150
1151 /**
1151 /**
1152 * Enable UI elements for pasting cells.
1152 * Enable UI elements for pasting cells.
1153 *
1153 *
1154 * @method enable_paste
1154 * @method enable_paste
1155 */
1155 */
1156 Notebook.prototype.enable_paste = function () {
1156 Notebook.prototype.enable_paste = function () {
1157 var that = this;
1157 var that = this;
1158 if (!this.paste_enabled) {
1158 if (!this.paste_enabled) {
1159 $('#paste_cell_replace').removeClass('disabled')
1159 $('#paste_cell_replace').removeClass('disabled')
1160 .on('click', function () {that.paste_cell_replace();});
1160 .on('click', function () {that.paste_cell_replace();});
1161 $('#paste_cell_above').removeClass('disabled')
1161 $('#paste_cell_above').removeClass('disabled')
1162 .on('click', function () {that.paste_cell_above();});
1162 .on('click', function () {that.paste_cell_above();});
1163 $('#paste_cell_below').removeClass('disabled')
1163 $('#paste_cell_below').removeClass('disabled')
1164 .on('click', function () {that.paste_cell_below();});
1164 .on('click', function () {that.paste_cell_below();});
1165 this.paste_enabled = true;
1165 this.paste_enabled = true;
1166 }
1166 }
1167 };
1167 };
1168
1168
1169 /**
1169 /**
1170 * Disable UI elements for pasting cells.
1170 * Disable UI elements for pasting cells.
1171 *
1171 *
1172 * @method disable_paste
1172 * @method disable_paste
1173 */
1173 */
1174 Notebook.prototype.disable_paste = function () {
1174 Notebook.prototype.disable_paste = function () {
1175 if (this.paste_enabled) {
1175 if (this.paste_enabled) {
1176 $('#paste_cell_replace').addClass('disabled').off('click');
1176 $('#paste_cell_replace').addClass('disabled').off('click');
1177 $('#paste_cell_above').addClass('disabled').off('click');
1177 $('#paste_cell_above').addClass('disabled').off('click');
1178 $('#paste_cell_below').addClass('disabled').off('click');
1178 $('#paste_cell_below').addClass('disabled').off('click');
1179 this.paste_enabled = false;
1179 this.paste_enabled = false;
1180 }
1180 }
1181 };
1181 };
1182
1182
1183 /**
1183 /**
1184 * Cut a cell.
1184 * Cut a cell.
1185 *
1185 *
1186 * @method cut_cell
1186 * @method cut_cell
1187 */
1187 */
1188 Notebook.prototype.cut_cell = function () {
1188 Notebook.prototype.cut_cell = function () {
1189 this.copy_cell();
1189 this.copy_cell();
1190 this.delete_cell();
1190 this.delete_cell();
1191 };
1191 };
1192
1192
1193 /**
1193 /**
1194 * Copy a cell.
1194 * Copy a cell.
1195 *
1195 *
1196 * @method copy_cell
1196 * @method copy_cell
1197 */
1197 */
1198 Notebook.prototype.copy_cell = function () {
1198 Notebook.prototype.copy_cell = function () {
1199 var cell = this.get_selected_cell();
1199 var cell = this.get_selected_cell();
1200 this.clipboard = cell.toJSON();
1200 this.clipboard = cell.toJSON();
1201 // remove undeletable status from the copied cell
1201 // remove undeletable status from the copied cell
1202 if (this.clipboard.metadata.deletable !== undefined) {
1202 if (this.clipboard.metadata.deletable !== undefined) {
1203 delete this.clipboard.metadata.deletable;
1203 delete this.clipboard.metadata.deletable;
1204 }
1204 }
1205 this.enable_paste();
1205 this.enable_paste();
1206 };
1206 };
1207
1207
1208 /**
1208 /**
1209 * Replace the selected cell with a cell in the clipboard.
1209 * Replace the selected cell with a cell in the clipboard.
1210 *
1210 *
1211 * @method paste_cell_replace
1211 * @method paste_cell_replace
1212 */
1212 */
1213 Notebook.prototype.paste_cell_replace = function () {
1213 Notebook.prototype.paste_cell_replace = function () {
1214 if (this.clipboard !== null && this.paste_enabled) {
1214 if (this.clipboard !== null && this.paste_enabled) {
1215 var cell_data = this.clipboard;
1215 var cell_data = this.clipboard;
1216 var new_cell = this.insert_cell_above(cell_data.cell_type);
1216 var new_cell = this.insert_cell_above(cell_data.cell_type);
1217 new_cell.fromJSON(cell_data);
1217 new_cell.fromJSON(cell_data);
1218 var old_cell = this.get_next_cell(new_cell);
1218 var old_cell = this.get_next_cell(new_cell);
1219 this.delete_cell(this.find_cell_index(old_cell));
1219 this.delete_cell(this.find_cell_index(old_cell));
1220 this.select(this.find_cell_index(new_cell));
1220 this.select(this.find_cell_index(new_cell));
1221 }
1221 }
1222 };
1222 };
1223
1223
1224 /**
1224 /**
1225 * Paste a cell from the clipboard above the selected cell.
1225 * Paste a cell from the clipboard above the selected cell.
1226 *
1226 *
1227 * @method paste_cell_above
1227 * @method paste_cell_above
1228 */
1228 */
1229 Notebook.prototype.paste_cell_above = function () {
1229 Notebook.prototype.paste_cell_above = function () {
1230 if (this.clipboard !== null && this.paste_enabled) {
1230 if (this.clipboard !== null && this.paste_enabled) {
1231 var cell_data = this.clipboard;
1231 var cell_data = this.clipboard;
1232 var new_cell = this.insert_cell_above(cell_data.cell_type);
1232 var new_cell = this.insert_cell_above(cell_data.cell_type);
1233 new_cell.fromJSON(cell_data);
1233 new_cell.fromJSON(cell_data);
1234 new_cell.focus_cell();
1234 new_cell.focus_cell();
1235 }
1235 }
1236 };
1236 };
1237
1237
1238 /**
1238 /**
1239 * Paste a cell from the clipboard below the selected cell.
1239 * Paste a cell from the clipboard below the selected cell.
1240 *
1240 *
1241 * @method paste_cell_below
1241 * @method paste_cell_below
1242 */
1242 */
1243 Notebook.prototype.paste_cell_below = function () {
1243 Notebook.prototype.paste_cell_below = function () {
1244 if (this.clipboard !== null && this.paste_enabled) {
1244 if (this.clipboard !== null && this.paste_enabled) {
1245 var cell_data = this.clipboard;
1245 var cell_data = this.clipboard;
1246 var new_cell = this.insert_cell_below(cell_data.cell_type);
1246 var new_cell = this.insert_cell_below(cell_data.cell_type);
1247 new_cell.fromJSON(cell_data);
1247 new_cell.fromJSON(cell_data);
1248 new_cell.focus_cell();
1248 new_cell.focus_cell();
1249 }
1249 }
1250 };
1250 };
1251
1251
1252 // Split/merge
1252 // Split/merge
1253
1253
1254 /**
1254 /**
1255 * Split the selected cell into two, at the cursor.
1255 * Split the selected cell into two, at the cursor.
1256 *
1256 *
1257 * @method split_cell
1257 * @method split_cell
1258 */
1258 */
1259 Notebook.prototype.split_cell = function () {
1259 Notebook.prototype.split_cell = function () {
1260 var cell = this.get_selected_cell();
1260 var cell = this.get_selected_cell();
1261 if (cell.is_splittable()) {
1261 if (cell.is_splittable()) {
1262 var texta = cell.get_pre_cursor();
1262 var texta = cell.get_pre_cursor();
1263 var textb = cell.get_post_cursor();
1263 var textb = cell.get_post_cursor();
1264 cell.set_text(textb);
1264 cell.set_text(textb);
1265 var new_cell = this.insert_cell_above(cell.cell_type);
1265 var new_cell = this.insert_cell_above(cell.cell_type);
1266 // Unrender the new cell so we can call set_text.
1266 // Unrender the new cell so we can call set_text.
1267 new_cell.unrender();
1267 new_cell.unrender();
1268 new_cell.set_text(texta);
1268 new_cell.set_text(texta);
1269 }
1269 }
1270 };
1270 };
1271
1271
1272 /**
1272 /**
1273 * Combine the selected cell into the cell above it.
1273 * Combine the selected cell into the cell above it.
1274 *
1274 *
1275 * @method merge_cell_above
1275 * @method merge_cell_above
1276 */
1276 */
1277 Notebook.prototype.merge_cell_above = function () {
1277 Notebook.prototype.merge_cell_above = function () {
1278 var index = this.get_selected_index();
1278 var index = this.get_selected_index();
1279 var cell = this.get_cell(index);
1279 var cell = this.get_cell(index);
1280 var render = cell.rendered;
1280 var render = cell.rendered;
1281 if (!cell.is_mergeable()) {
1281 if (!cell.is_mergeable()) {
1282 return;
1282 return;
1283 }
1283 }
1284 if (index > 0) {
1284 if (index > 0) {
1285 var upper_cell = this.get_cell(index-1);
1285 var upper_cell = this.get_cell(index-1);
1286 if (!upper_cell.is_mergeable()) {
1286 if (!upper_cell.is_mergeable()) {
1287 return;
1287 return;
1288 }
1288 }
1289 var upper_text = upper_cell.get_text();
1289 var upper_text = upper_cell.get_text();
1290 var text = cell.get_text();
1290 var text = cell.get_text();
1291 if (cell instanceof codecell.CodeCell) {
1291 if (cell instanceof codecell.CodeCell) {
1292 cell.set_text(upper_text+'\n'+text);
1292 cell.set_text(upper_text+'\n'+text);
1293 } else {
1293 } else {
1294 cell.unrender(); // Must unrender before we set_text.
1294 cell.unrender(); // Must unrender before we set_text.
1295 cell.set_text(upper_text+'\n\n'+text);
1295 cell.set_text(upper_text+'\n\n'+text);
1296 if (render) {
1296 if (render) {
1297 // The rendered state of the final cell should match
1297 // The rendered state of the final cell should match
1298 // that of the original selected cell;
1298 // that of the original selected cell;
1299 cell.render();
1299 cell.render();
1300 }
1300 }
1301 }
1301 }
1302 this.delete_cell(index-1);
1302 this.delete_cell(index-1);
1303 this.select(this.find_cell_index(cell));
1303 this.select(this.find_cell_index(cell));
1304 }
1304 }
1305 };
1305 };
1306
1306
1307 /**
1307 /**
1308 * Combine the selected cell into the cell below it.
1308 * Combine the selected cell into the cell below it.
1309 *
1309 *
1310 * @method merge_cell_below
1310 * @method merge_cell_below
1311 */
1311 */
1312 Notebook.prototype.merge_cell_below = function () {
1312 Notebook.prototype.merge_cell_below = function () {
1313 var index = this.get_selected_index();
1313 var index = this.get_selected_index();
1314 var cell = this.get_cell(index);
1314 var cell = this.get_cell(index);
1315 var render = cell.rendered;
1315 var render = cell.rendered;
1316 if (!cell.is_mergeable()) {
1316 if (!cell.is_mergeable()) {
1317 return;
1317 return;
1318 }
1318 }
1319 if (index < this.ncells()-1) {
1319 if (index < this.ncells()-1) {
1320 var lower_cell = this.get_cell(index+1);
1320 var lower_cell = this.get_cell(index+1);
1321 if (!lower_cell.is_mergeable()) {
1321 if (!lower_cell.is_mergeable()) {
1322 return;
1322 return;
1323 }
1323 }
1324 var lower_text = lower_cell.get_text();
1324 var lower_text = lower_cell.get_text();
1325 var text = cell.get_text();
1325 var text = cell.get_text();
1326 if (cell instanceof codecell.CodeCell) {
1326 if (cell instanceof codecell.CodeCell) {
1327 cell.set_text(text+'\n'+lower_text);
1327 cell.set_text(text+'\n'+lower_text);
1328 } else {
1328 } else {
1329 cell.unrender(); // Must unrender before we set_text.
1329 cell.unrender(); // Must unrender before we set_text.
1330 cell.set_text(text+'\n\n'+lower_text);
1330 cell.set_text(text+'\n\n'+lower_text);
1331 if (render) {
1331 if (render) {
1332 // The rendered state of the final cell should match
1332 // The rendered state of the final cell should match
1333 // that of the original selected cell;
1333 // that of the original selected cell;
1334 cell.render();
1334 cell.render();
1335 }
1335 }
1336 }
1336 }
1337 this.delete_cell(index+1);
1337 this.delete_cell(index+1);
1338 this.select(this.find_cell_index(cell));
1338 this.select(this.find_cell_index(cell));
1339 }
1339 }
1340 };
1340 };
1341
1341
1342
1342
1343 // Cell collapsing and output clearing
1343 // Cell collapsing and output clearing
1344
1344
1345 /**
1345 /**
1346 * Hide a cell's output.
1346 * Hide a cell's output.
1347 *
1347 *
1348 * @method collapse_output
1348 * @method collapse_output
1349 * @param {Number} index A cell's numeric index
1349 * @param {Number} index A cell's numeric index
1350 */
1350 */
1351 Notebook.prototype.collapse_output = function (index) {
1351 Notebook.prototype.collapse_output = function (index) {
1352 var i = this.index_or_selected(index);
1352 var i = this.index_or_selected(index);
1353 var cell = this.get_cell(i);
1353 var cell = this.get_cell(i);
1354 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1354 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1355 cell.collapse_output();
1355 cell.collapse_output();
1356 this.set_dirty(true);
1356 this.set_dirty(true);
1357 }
1357 }
1358 };
1358 };
1359
1359
1360 /**
1360 /**
1361 * Hide each code cell's output area.
1361 * Hide each code cell's output area.
1362 *
1362 *
1363 * @method collapse_all_output
1363 * @method collapse_all_output
1364 */
1364 */
1365 Notebook.prototype.collapse_all_output = function () {
1365 Notebook.prototype.collapse_all_output = function () {
1366 this.get_cells().map(function (cell, i) {
1366 this.get_cells().map(function (cell, i) {
1367 if (cell instanceof codecell.CodeCell) {
1367 if (cell instanceof codecell.CodeCell) {
1368 cell.collapse_output();
1368 cell.collapse_output();
1369 }
1369 }
1370 });
1370 });
1371 // this should not be set if the `collapse` key is removed from nbformat
1371 // this should not be set if the `collapse` key is removed from nbformat
1372 this.set_dirty(true);
1372 this.set_dirty(true);
1373 };
1373 };
1374
1374
1375 /**
1375 /**
1376 * Show a cell's output.
1376 * Show a cell's output.
1377 *
1377 *
1378 * @method expand_output
1378 * @method expand_output
1379 * @param {Number} index A cell's numeric index
1379 * @param {Number} index A cell's numeric index
1380 */
1380 */
1381 Notebook.prototype.expand_output = function (index) {
1381 Notebook.prototype.expand_output = function (index) {
1382 var i = this.index_or_selected(index);
1382 var i = this.index_or_selected(index);
1383 var cell = this.get_cell(i);
1383 var cell = this.get_cell(i);
1384 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1384 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1385 cell.expand_output();
1385 cell.expand_output();
1386 this.set_dirty(true);
1386 this.set_dirty(true);
1387 }
1387 }
1388 };
1388 };
1389
1389
1390 /**
1390 /**
1391 * Expand each code cell's output area, and remove scrollbars.
1391 * Expand each code cell's output area, and remove scrollbars.
1392 *
1392 *
1393 * @method expand_all_output
1393 * @method expand_all_output
1394 */
1394 */
1395 Notebook.prototype.expand_all_output = function () {
1395 Notebook.prototype.expand_all_output = function () {
1396 this.get_cells().map(function (cell, i) {
1396 this.get_cells().map(function (cell, i) {
1397 if (cell instanceof codecell.CodeCell) {
1397 if (cell instanceof codecell.CodeCell) {
1398 cell.expand_output();
1398 cell.expand_output();
1399 }
1399 }
1400 });
1400 });
1401 // this should not be set if the `collapse` key is removed from nbformat
1401 // this should not be set if the `collapse` key is removed from nbformat
1402 this.set_dirty(true);
1402 this.set_dirty(true);
1403 };
1403 };
1404
1404
1405 /**
1405 /**
1406 * Clear the selected CodeCell's output area.
1406 * Clear the selected CodeCell's output area.
1407 *
1407 *
1408 * @method clear_output
1408 * @method clear_output
1409 * @param {Number} index A cell's numeric index
1409 * @param {Number} index A cell's numeric index
1410 */
1410 */
1411 Notebook.prototype.clear_output = function (index) {
1411 Notebook.prototype.clear_output = function (index) {
1412 var i = this.index_or_selected(index);
1412 var i = this.index_or_selected(index);
1413 var cell = this.get_cell(i);
1413 var cell = this.get_cell(i);
1414 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1414 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1415 cell.clear_output();
1415 cell.clear_output();
1416 this.set_dirty(true);
1416 this.set_dirty(true);
1417 }
1417 }
1418 };
1418 };
1419
1419
1420 /**
1420 /**
1421 * Clear each code cell's output area.
1421 * Clear each code cell's output area.
1422 *
1422 *
1423 * @method clear_all_output
1423 * @method clear_all_output
1424 */
1424 */
1425 Notebook.prototype.clear_all_output = function () {
1425 Notebook.prototype.clear_all_output = function () {
1426 this.get_cells().map(function (cell, i) {
1426 this.get_cells().map(function (cell, i) {
1427 if (cell instanceof codecell.CodeCell) {
1427 if (cell instanceof codecell.CodeCell) {
1428 cell.clear_output();
1428 cell.clear_output();
1429 }
1429 }
1430 });
1430 });
1431 this.set_dirty(true);
1431 this.set_dirty(true);
1432 };
1432 };
1433
1433
1434 /**
1434 /**
1435 * Scroll the selected CodeCell's output area.
1435 * Scroll the selected CodeCell's output area.
1436 *
1436 *
1437 * @method scroll_output
1437 * @method scroll_output
1438 * @param {Number} index A cell's numeric index
1438 * @param {Number} index A cell's numeric index
1439 */
1439 */
1440 Notebook.prototype.scroll_output = function (index) {
1440 Notebook.prototype.scroll_output = function (index) {
1441 var i = this.index_or_selected(index);
1441 var i = this.index_or_selected(index);
1442 var cell = this.get_cell(i);
1442 var cell = this.get_cell(i);
1443 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1443 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1444 cell.scroll_output();
1444 cell.scroll_output();
1445 this.set_dirty(true);
1445 this.set_dirty(true);
1446 }
1446 }
1447 };
1447 };
1448
1448
1449 /**
1449 /**
1450 * Expand each code cell's output area, and add a scrollbar for long output.
1450 * Expand each code cell's output area, and add a scrollbar for long output.
1451 *
1451 *
1452 * @method scroll_all_output
1452 * @method scroll_all_output
1453 */
1453 */
1454 Notebook.prototype.scroll_all_output = function () {
1454 Notebook.prototype.scroll_all_output = function () {
1455 this.get_cells().map(function (cell, i) {
1455 this.get_cells().map(function (cell, i) {
1456 if (cell instanceof codecell.CodeCell) {
1456 if (cell instanceof codecell.CodeCell) {
1457 cell.scroll_output();
1457 cell.scroll_output();
1458 }
1458 }
1459 });
1459 });
1460 // this should not be set if the `collapse` key is removed from nbformat
1460 // this should not be set if the `collapse` key is removed from nbformat
1461 this.set_dirty(true);
1461 this.set_dirty(true);
1462 };
1462 };
1463
1463
1464 /** Toggle whether a cell's output is collapsed or expanded.
1464 /** Toggle whether a cell's output is collapsed or expanded.
1465 *
1465 *
1466 * @method toggle_output
1466 * @method toggle_output
1467 * @param {Number} index A cell's numeric index
1467 * @param {Number} index A cell's numeric index
1468 */
1468 */
1469 Notebook.prototype.toggle_output = function (index) {
1469 Notebook.prototype.toggle_output = function (index) {
1470 var i = this.index_or_selected(index);
1470 var i = this.index_or_selected(index);
1471 var cell = this.get_cell(i);
1471 var cell = this.get_cell(i);
1472 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1472 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1473 cell.toggle_output();
1473 cell.toggle_output();
1474 this.set_dirty(true);
1474 this.set_dirty(true);
1475 }
1475 }
1476 };
1476 };
1477
1477
1478 /**
1478 /**
1479 * Hide/show the output of all cells.
1479 * Hide/show the output of all cells.
1480 *
1480 *
1481 * @method toggle_all_output
1481 * @method toggle_all_output
1482 */
1482 */
1483 Notebook.prototype.toggle_all_output = function () {
1483 Notebook.prototype.toggle_all_output = function () {
1484 this.get_cells().map(function (cell, i) {
1484 this.get_cells().map(function (cell, i) {
1485 if (cell instanceof codecell.CodeCell) {
1485 if (cell instanceof codecell.CodeCell) {
1486 cell.toggle_output();
1486 cell.toggle_output();
1487 }
1487 }
1488 });
1488 });
1489 // this should not be set if the `collapse` key is removed from nbformat
1489 // this should not be set if the `collapse` key is removed from nbformat
1490 this.set_dirty(true);
1490 this.set_dirty(true);
1491 };
1491 };
1492
1492
1493 /**
1493 /**
1494 * Toggle a scrollbar for long cell outputs.
1494 * Toggle a scrollbar for long cell outputs.
1495 *
1495 *
1496 * @method toggle_output_scroll
1496 * @method toggle_output_scroll
1497 * @param {Number} index A cell's numeric index
1497 * @param {Number} index A cell's numeric index
1498 */
1498 */
1499 Notebook.prototype.toggle_output_scroll = function (index) {
1499 Notebook.prototype.toggle_output_scroll = function (index) {
1500 var i = this.index_or_selected(index);
1500 var i = this.index_or_selected(index);
1501 var cell = this.get_cell(i);
1501 var cell = this.get_cell(i);
1502 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1502 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1503 cell.toggle_output_scroll();
1503 cell.toggle_output_scroll();
1504 this.set_dirty(true);
1504 this.set_dirty(true);
1505 }
1505 }
1506 };
1506 };
1507
1507
1508 /**
1508 /**
1509 * Toggle the scrolling of long output on all cells.
1509 * Toggle the scrolling of long output on all cells.
1510 *
1510 *
1511 * @method toggle_all_output_scrolling
1511 * @method toggle_all_output_scrolling
1512 */
1512 */
1513 Notebook.prototype.toggle_all_output_scroll = function () {
1513 Notebook.prototype.toggle_all_output_scroll = function () {
1514 this.get_cells().map(function (cell, i) {
1514 this.get_cells().map(function (cell, i) {
1515 if (cell instanceof codecell.CodeCell) {
1515 if (cell instanceof codecell.CodeCell) {
1516 cell.toggle_output_scroll();
1516 cell.toggle_output_scroll();
1517 }
1517 }
1518 });
1518 });
1519 // this should not be set if the `collapse` key is removed from nbformat
1519 // this should not be set if the `collapse` key is removed from nbformat
1520 this.set_dirty(true);
1520 this.set_dirty(true);
1521 };
1521 };
1522
1522
1523 // Other cell functions: line numbers, ...
1523 // Other cell functions: line numbers, ...
1524
1524
1525 /**
1525 /**
1526 * Toggle line numbers in the selected cell's input area.
1526 * Toggle line numbers in the selected cell's input area.
1527 *
1527 *
1528 * @method cell_toggle_line_numbers
1528 * @method cell_toggle_line_numbers
1529 */
1529 */
1530 Notebook.prototype.cell_toggle_line_numbers = function() {
1530 Notebook.prototype.cell_toggle_line_numbers = function() {
1531 this.get_selected_cell().toggle_line_numbers();
1531 this.get_selected_cell().toggle_line_numbers();
1532 };
1532 };
1533
1533
1534 /**
1534 /**
1535 * Set the codemirror mode for all code cells, including the default for
1535 * Set the codemirror mode for all code cells, including the default for
1536 * new code cells.
1536 * new code cells.
1537 *
1537 *
1538 * @method set_codemirror_mode
1538 * @method set_codemirror_mode
1539 */
1539 */
1540 Notebook.prototype.set_codemirror_mode = function(newmode){
1540 Notebook.prototype.set_codemirror_mode = function(newmode){
1541 if (newmode === this.codemirror_mode) {
1541 if (newmode === this.codemirror_mode) {
1542 return;
1542 return;
1543 }
1543 }
1544 this.codemirror_mode = newmode;
1544 this.codemirror_mode = newmode;
1545 codecell.CodeCell.options_default.cm_config.mode = newmode;
1545 codecell.CodeCell.options_default.cm_config.mode = newmode;
1546 var modename = newmode.mode || newmode.name || newmode;
1546 var modename = newmode.mode || newmode.name || newmode;
1547
1547
1548 var that = this;
1548 var that = this;
1549 utils.requireCodeMirrorMode(modename, function () {
1549 utils.requireCodeMirrorMode(modename, function () {
1550 that.get_cells().map(function(cell, i) {
1550 that.get_cells().map(function(cell, i) {
1551 if (cell.cell_type === 'code'){
1551 if (cell.cell_type === 'code'){
1552 cell.code_mirror.setOption('mode', newmode);
1552 cell.code_mirror.setOption('mode', newmode);
1553 // This is currently redundant, because cm_config ends up as
1553 // This is currently redundant, because cm_config ends up as
1554 // codemirror's own .options object, but I don't want to
1554 // codemirror's own .options object, but I don't want to
1555 // rely on that.
1555 // rely on that.
1556 cell.cm_config.mode = newmode;
1556 cell.cm_config.mode = newmode;
1557 }
1557 }
1558 });
1558 });
1559 });
1559 });
1560 };
1560 };
1561
1561
1562 // Session related things
1562 // Session related things
1563
1563
1564 /**
1564 /**
1565 * Start a new session and set it on each code cell.
1565 * Start a new session and set it on each code cell.
1566 *
1566 *
1567 * @method start_session
1567 * @method start_session
1568 */
1568 */
1569 Notebook.prototype.start_session = function (kernel_name) {
1569 Notebook.prototype.start_session = function (kernel_name) {
1570 if (this._session_starting) {
1570 if (this._session_starting) {
1571 throw new session.SessionAlreadyStarting();
1571 throw new session.SessionAlreadyStarting();
1572 }
1572 }
1573 this._session_starting = true;
1573 this._session_starting = true;
1574
1574
1575 var options = {
1575 var options = {
1576 base_url: this.base_url,
1576 base_url: this.base_url,
1577 ws_url: this.ws_url,
1577 ws_url: this.ws_url,
1578 notebook_path: this.notebook_path,
1578 notebook_path: this.notebook_path,
1579 notebook_name: this.notebook_name,
1579 notebook_name: this.notebook_name,
1580 kernel_name: kernel_name,
1580 kernel_name: kernel_name,
1581 notebook: this
1581 notebook: this
1582 };
1582 };
1583
1583
1584 var success = $.proxy(this._session_started, this);
1584 var success = $.proxy(this._session_started, this);
1585 var failure = $.proxy(this._session_start_failed, this);
1585 var failure = $.proxy(this._session_start_failed, this);
1586
1586
1587 if (this.session !== null) {
1587 if (this.session !== null) {
1588 this.session.restart(options, success, failure);
1588 this.session.restart(options, success, failure);
1589 } else {
1589 } else {
1590 this.session = new session.Session(options);
1590 this.session = new session.Session(options);
1591 this.session.start(success, failure);
1591 this.session.start(success, failure);
1592 }
1592 }
1593 };
1593 };
1594
1594
1595
1595
1596 /**
1596 /**
1597 * Once a session is started, link the code cells to the kernel and pass the
1597 * Once a session is started, link the code cells to the kernel and pass the
1598 * comm manager to the widget manager
1598 * comm manager to the widget manager
1599 *
1599 *
1600 */
1600 */
1601 Notebook.prototype._session_started = function (){
1601 Notebook.prototype._session_started = function (){
1602 this._session_starting = false;
1602 this._session_starting = false;
1603 this.kernel = this.session.kernel;
1603 this.kernel = this.session.kernel;
1604 var ncells = this.ncells();
1604 var ncells = this.ncells();
1605 for (var i=0; i<ncells; i++) {
1605 for (var i=0; i<ncells; i++) {
1606 var cell = this.get_cell(i);
1606 var cell = this.get_cell(i);
1607 if (cell instanceof codecell.CodeCell) {
1607 if (cell instanceof codecell.CodeCell) {
1608 cell.set_kernel(this.session.kernel);
1608 cell.set_kernel(this.session.kernel);
1609 }
1609 }
1610 }
1610 }
1611 };
1611 };
1612 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1612 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1613 this._session_starting = false;
1613 this._session_starting = false;
1614 utils.log_ajax_error(jqxhr, status, error);
1614 utils.log_ajax_error(jqxhr, status, error);
1615 };
1615 };
1616
1616
1617 /**
1617 /**
1618 * Prompt the user to restart the IPython kernel.
1618 * Prompt the user to restart the IPython kernel.
1619 *
1619 *
1620 * @method restart_kernel
1620 * @method restart_kernel
1621 */
1621 */
1622 Notebook.prototype.restart_kernel = function () {
1622 Notebook.prototype.restart_kernel = function () {
1623 var that = this;
1623 var that = this;
1624 dialog.modal({
1624 dialog.modal({
1625 notebook: this,
1625 notebook: this,
1626 keyboard_manager: this.keyboard_manager,
1626 keyboard_manager: this.keyboard_manager,
1627 title : "Restart kernel or continue running?",
1627 title : "Restart kernel or continue running?",
1628 body : $("<p/>").text(
1628 body : $("<p/>").text(
1629 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1629 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1630 ),
1630 ),
1631 buttons : {
1631 buttons : {
1632 "Continue running" : {},
1632 "Continue running" : {},
1633 "Restart" : {
1633 "Restart" : {
1634 "class" : "btn-danger",
1634 "class" : "btn-danger",
1635 "click" : function() {
1635 "click" : function() {
1636 that.kernel.restart();
1636 that.kernel.restart();
1637 }
1637 }
1638 }
1638 }
1639 }
1639 }
1640 });
1640 });
1641 };
1641 };
1642
1642
1643 /**
1643 /**
1644 * Execute or render cell outputs and go into command mode.
1644 * Execute or render cell outputs and go into command mode.
1645 *
1645 *
1646 * @method execute_cell
1646 * @method execute_cell
1647 */
1647 */
1648 Notebook.prototype.execute_cell = function () {
1648 Notebook.prototype.execute_cell = function () {
1649 // mode = shift, ctrl, alt
1649 // mode = shift, ctrl, alt
1650 var cell = this.get_selected_cell();
1650 var cell = this.get_selected_cell();
1651
1651
1652 cell.execute();
1652 cell.execute();
1653 this.command_mode();
1653 this.command_mode();
1654 this.set_dirty(true);
1654 this.set_dirty(true);
1655 };
1655 };
1656
1656
1657 /**
1657 /**
1658 * Execute or render cell outputs and insert a new cell below.
1658 * Execute or render cell outputs and insert a new cell below.
1659 *
1659 *
1660 * @method execute_cell_and_insert_below
1660 * @method execute_cell_and_insert_below
1661 */
1661 */
1662 Notebook.prototype.execute_cell_and_insert_below = function () {
1662 Notebook.prototype.execute_cell_and_insert_below = function () {
1663 var cell = this.get_selected_cell();
1663 var cell = this.get_selected_cell();
1664 var cell_index = this.find_cell_index(cell);
1664 var cell_index = this.find_cell_index(cell);
1665
1665
1666 cell.execute();
1666 cell.execute();
1667
1667
1668 // If we are at the end always insert a new cell and return
1668 // If we are at the end always insert a new cell and return
1669 if (cell_index === (this.ncells()-1)) {
1669 if (cell_index === (this.ncells()-1)) {
1670 this.command_mode();
1670 this.command_mode();
1671 this.insert_cell_below();
1671 this.insert_cell_below();
1672 this.select(cell_index+1);
1672 this.select(cell_index+1);
1673 this.edit_mode();
1673 this.edit_mode();
1674 this.scroll_to_bottom();
1674 this.scroll_to_bottom();
1675 this.set_dirty(true);
1675 this.set_dirty(true);
1676 return;
1676 return;
1677 }
1677 }
1678
1678
1679 this.command_mode();
1679 this.command_mode();
1680 this.insert_cell_below();
1680 this.insert_cell_below();
1681 this.select(cell_index+1);
1681 this.select(cell_index+1);
1682 this.edit_mode();
1682 this.edit_mode();
1683 this.set_dirty(true);
1683 this.set_dirty(true);
1684 };
1684 };
1685
1685
1686 /**
1686 /**
1687 * Execute or render cell outputs and select the next cell.
1687 * Execute or render cell outputs and select the next cell.
1688 *
1688 *
1689 * @method execute_cell_and_select_below
1689 * @method execute_cell_and_select_below
1690 */
1690 */
1691 Notebook.prototype.execute_cell_and_select_below = function () {
1691 Notebook.prototype.execute_cell_and_select_below = function () {
1692
1692
1693 var cell = this.get_selected_cell();
1693 var cell = this.get_selected_cell();
1694 var cell_index = this.find_cell_index(cell);
1694 var cell_index = this.find_cell_index(cell);
1695
1695
1696 cell.execute();
1696 cell.execute();
1697
1697
1698 // If we are at the end always insert a new cell and return
1698 // If we are at the end always insert a new cell and return
1699 if (cell_index === (this.ncells()-1)) {
1699 if (cell_index === (this.ncells()-1)) {
1700 this.command_mode();
1700 this.command_mode();
1701 this.insert_cell_below();
1701 this.insert_cell_below();
1702 this.select(cell_index+1);
1702 this.select(cell_index+1);
1703 this.edit_mode();
1703 this.edit_mode();
1704 this.scroll_to_bottom();
1704 this.scroll_to_bottom();
1705 this.set_dirty(true);
1705 this.set_dirty(true);
1706 return;
1706 return;
1707 }
1707 }
1708
1708
1709 this.command_mode();
1709 this.command_mode();
1710 this.select(cell_index+1);
1710 this.select(cell_index+1);
1711 this.focus_cell();
1711 this.focus_cell();
1712 this.set_dirty(true);
1712 this.set_dirty(true);
1713 };
1713 };
1714
1714
1715 /**
1715 /**
1716 * Execute all cells below the selected cell.
1716 * Execute all cells below the selected cell.
1717 *
1717 *
1718 * @method execute_cells_below
1718 * @method execute_cells_below
1719 */
1719 */
1720 Notebook.prototype.execute_cells_below = function () {
1720 Notebook.prototype.execute_cells_below = function () {
1721 this.execute_cell_range(this.get_selected_index(), this.ncells());
1721 this.execute_cell_range(this.get_selected_index(), this.ncells());
1722 this.scroll_to_bottom();
1722 this.scroll_to_bottom();
1723 };
1723 };
1724
1724
1725 /**
1725 /**
1726 * Execute all cells above the selected cell.
1726 * Execute all cells above the selected cell.
1727 *
1727 *
1728 * @method execute_cells_above
1728 * @method execute_cells_above
1729 */
1729 */
1730 Notebook.prototype.execute_cells_above = function () {
1730 Notebook.prototype.execute_cells_above = function () {
1731 this.execute_cell_range(0, this.get_selected_index());
1731 this.execute_cell_range(0, this.get_selected_index());
1732 };
1732 };
1733
1733
1734 /**
1734 /**
1735 * Execute all cells.
1735 * Execute all cells.
1736 *
1736 *
1737 * @method execute_all_cells
1737 * @method execute_all_cells
1738 */
1738 */
1739 Notebook.prototype.execute_all_cells = function () {
1739 Notebook.prototype.execute_all_cells = function () {
1740 this.execute_cell_range(0, this.ncells());
1740 this.execute_cell_range(0, this.ncells());
1741 this.scroll_to_bottom();
1741 this.scroll_to_bottom();
1742 };
1742 };
1743
1743
1744 /**
1744 /**
1745 * Execute a contiguous range of cells.
1745 * Execute a contiguous range of cells.
1746 *
1746 *
1747 * @method execute_cell_range
1747 * @method execute_cell_range
1748 * @param {Number} start Index of the first cell to execute (inclusive)
1748 * @param {Number} start Index of the first cell to execute (inclusive)
1749 * @param {Number} end Index of the last cell to execute (exclusive)
1749 * @param {Number} end Index of the last cell to execute (exclusive)
1750 */
1750 */
1751 Notebook.prototype.execute_cell_range = function (start, end) {
1751 Notebook.prototype.execute_cell_range = function (start, end) {
1752 this.command_mode();
1752 this.command_mode();
1753 for (var i=start; i<end; i++) {
1753 for (var i=start; i<end; i++) {
1754 this.select(i);
1754 this.select(i);
1755 this.execute_cell();
1755 this.execute_cell();
1756 }
1756 }
1757 };
1757 };
1758
1758
1759 // Persistance and loading
1759 // Persistance and loading
1760
1760
1761 /**
1761 /**
1762 * Getter method for this notebook's name.
1762 * Getter method for this notebook's name.
1763 *
1763 *
1764 * @method get_notebook_name
1764 * @method get_notebook_name
1765 * @return {String} This notebook's name (excluding file extension)
1765 * @return {String} This notebook's name (excluding file extension)
1766 */
1766 */
1767 Notebook.prototype.get_notebook_name = function () {
1767 Notebook.prototype.get_notebook_name = function () {
1768 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1768 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1769 return nbname;
1769 return nbname;
1770 };
1770 };
1771
1771
1772 /**
1772 /**
1773 * Setter method for this notebook's name.
1773 * Setter method for this notebook's name.
1774 *
1774 *
1775 * @method set_notebook_name
1775 * @method set_notebook_name
1776 * @param {String} name A new name for this notebook
1776 * @param {String} name A new name for this notebook
1777 */
1777 */
1778 Notebook.prototype.set_notebook_name = function (name) {
1778 Notebook.prototype.set_notebook_name = function (name) {
1779 var parent = utils.url_path_split(this.notebook_path)[0];
1779 var parent = utils.url_path_split(this.notebook_path)[0];
1780 this.notebook_name = name;
1780 this.notebook_name = name;
1781 this.notebook_path = utils.url_path_join(parent, name);
1781 this.notebook_path = utils.url_path_join(parent, name);
1782 };
1782 };
1783
1783
1784 /**
1784 /**
1785 * Check that a notebook's name is valid.
1785 * Check that a notebook's name is valid.
1786 *
1786 *
1787 * @method test_notebook_name
1787 * @method test_notebook_name
1788 * @param {String} nbname A name for this notebook
1788 * @param {String} nbname A name for this notebook
1789 * @return {Boolean} True if the name is valid, false if invalid
1789 * @return {Boolean} True if the name is valid, false if invalid
1790 */
1790 */
1791 Notebook.prototype.test_notebook_name = function (nbname) {
1791 Notebook.prototype.test_notebook_name = function (nbname) {
1792 nbname = nbname || '';
1792 nbname = nbname || '';
1793 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1793 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1794 return true;
1794 return true;
1795 } else {
1795 } else {
1796 return false;
1796 return false;
1797 }
1797 }
1798 };
1798 };
1799
1799
1800 /**
1800 /**
1801 * Load a notebook from JSON (.ipynb).
1801 * Load a notebook from JSON (.ipynb).
1802 *
1802 *
1803 * @method fromJSON
1803 * @method fromJSON
1804 * @param {Object} data JSON representation of a notebook
1804 * @param {Object} data JSON representation of a notebook
1805 */
1805 */
1806 Notebook.prototype.fromJSON = function (data) {
1806 Notebook.prototype.fromJSON = function (data) {
1807
1807
1808 var content = data.content;
1808 var content = data.content;
1809 var ncells = this.ncells();
1809 var ncells = this.ncells();
1810 var i;
1810 var i;
1811 for (i=0; i<ncells; i++) {
1811 for (i=0; i<ncells; i++) {
1812 // Always delete cell 0 as they get renumbered as they are deleted.
1812 // Always delete cell 0 as they get renumbered as they are deleted.
1813 this.delete_cell(0);
1813 this.delete_cell(0);
1814 }
1814 }
1815 // Save the metadata and name.
1815 // Save the metadata and name.
1816 this.metadata = content.metadata;
1816 this.metadata = content.metadata;
1817 this.notebook_name = data.name;
1817 this.notebook_name = data.name;
1818 this.notebook_path = data.path;
1818 this.notebook_path = data.path;
1819 var trusted = true;
1819 var trusted = true;
1820
1820
1821 // Trigger an event changing the kernel spec - this will set the default
1821 // Trigger an event changing the kernel spec - this will set the default
1822 // codemirror mode
1822 // codemirror mode
1823 if (this.metadata.kernelspec !== undefined) {
1823 if (this.metadata.kernelspec !== undefined) {
1824 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1824 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1825 }
1825 }
1826
1826
1827 // Set the codemirror mode from language_info metadata
1827 // Set the codemirror mode from language_info metadata
1828 if (this.metadata.language_info !== undefined) {
1828 if (this.metadata.language_info !== undefined) {
1829 var langinfo = this.metadata.language_info;
1829 var langinfo = this.metadata.language_info;
1830 // Mode 'null' should be plain, unhighlighted text.
1830 // Mode 'null' should be plain, unhighlighted text.
1831 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1831 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1832 this.set_codemirror_mode(cm_mode);
1832 this.set_codemirror_mode(cm_mode);
1833 }
1833 }
1834
1834
1835 var new_cells = content.cells;
1835 var new_cells = content.cells;
1836 ncells = new_cells.length;
1836 ncells = new_cells.length;
1837 var cell_data = null;
1837 var cell_data = null;
1838 var new_cell = null;
1838 var new_cell = null;
1839 for (i=0; i<ncells; i++) {
1839 for (i=0; i<ncells; i++) {
1840 cell_data = new_cells[i];
1840 cell_data = new_cells[i];
1841 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1841 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1842 new_cell.fromJSON(cell_data);
1842 new_cell.fromJSON(cell_data);
1843 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1843 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1844 trusted = false;
1844 trusted = false;
1845 }
1845 }
1846 }
1846 }
1847 if (trusted !== this.trusted) {
1847 if (trusted !== this.trusted) {
1848 this.trusted = trusted;
1848 this.trusted = trusted;
1849 this.events.trigger("trust_changed.Notebook", trusted);
1849 this.events.trigger("trust_changed.Notebook", trusted);
1850 }
1850 }
1851 };
1851 };
1852
1852
1853 /**
1853 /**
1854 * Dump this notebook into a JSON-friendly object.
1854 * Dump this notebook into a JSON-friendly object.
1855 *
1855 *
1856 * @method toJSON
1856 * @method toJSON
1857 * @return {Object} A JSON-friendly representation of this notebook.
1857 * @return {Object} A JSON-friendly representation of this notebook.
1858 */
1858 */
1859 Notebook.prototype.toJSON = function () {
1859 Notebook.prototype.toJSON = function () {
1860 // remove the conversion indicator, which only belongs in-memory
1860 // remove the conversion indicator, which only belongs in-memory
1861 delete this.metadata.orig_nbformat;
1861 delete this.metadata.orig_nbformat;
1862 delete this.metadata.orig_nbformat_minor;
1862 delete this.metadata.orig_nbformat_minor;
1863
1863
1864 var cells = this.get_cells();
1864 var cells = this.get_cells();
1865 var ncells = cells.length;
1865 var ncells = cells.length;
1866 var cell_array = new Array(ncells);
1866 var cell_array = new Array(ncells);
1867 var trusted = true;
1867 var trusted = true;
1868 for (var i=0; i<ncells; i++) {
1868 for (var i=0; i<ncells; i++) {
1869 var cell = cells[i];
1869 var cell = cells[i];
1870 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1870 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1871 trusted = false;
1871 trusted = false;
1872 }
1872 }
1873 cell_array[i] = cell.toJSON();
1873 cell_array[i] = cell.toJSON();
1874 }
1874 }
1875 var data = {
1875 var data = {
1876 cells: cell_array,
1876 cells: cell_array,
1877 metadata: this.metadata,
1877 metadata: this.metadata,
1878 nbformat: this.nbformat,
1878 nbformat: this.nbformat,
1879 nbformat_minor: this.nbformat_minor
1879 nbformat_minor: this.nbformat_minor
1880 };
1880 };
1881 if (trusted != this.trusted) {
1881 if (trusted != this.trusted) {
1882 this.trusted = trusted;
1882 this.trusted = trusted;
1883 this.events.trigger("trust_changed.Notebook", trusted);
1883 this.events.trigger("trust_changed.Notebook", trusted);
1884 }
1884 }
1885 return data;
1885 return data;
1886 };
1886 };
1887
1887
1888 /**
1888 /**
1889 * Start an autosave timer, for periodically saving the notebook.
1889 * Start an autosave timer, for periodically saving the notebook.
1890 *
1890 *
1891 * @method set_autosave_interval
1891 * @method set_autosave_interval
1892 * @param {Integer} interval the autosave interval in milliseconds
1892 * @param {Integer} interval the autosave interval in milliseconds
1893 */
1893 */
1894 Notebook.prototype.set_autosave_interval = function (interval) {
1894 Notebook.prototype.set_autosave_interval = function (interval) {
1895 var that = this;
1895 var that = this;
1896 // clear previous interval, so we don't get simultaneous timers
1896 // clear previous interval, so we don't get simultaneous timers
1897 if (this.autosave_timer) {
1897 if (this.autosave_timer) {
1898 clearInterval(this.autosave_timer);
1898 clearInterval(this.autosave_timer);
1899 }
1899 }
1900 if (!this.writable) {
1900 if (!this.writable) {
1901 // disable autosave if not writable
1901 // disable autosave if not writable
1902 interval = 0;
1902 interval = 0;
1903 }
1903 }
1904
1904
1905 this.autosave_interval = this.minimum_autosave_interval = interval;
1905 this.autosave_interval = this.minimum_autosave_interval = interval;
1906 if (interval) {
1906 if (interval) {
1907 this.autosave_timer = setInterval(function() {
1907 this.autosave_timer = setInterval(function() {
1908 if (that.dirty) {
1908 if (that.dirty) {
1909 that.save_notebook();
1909 that.save_notebook();
1910 }
1910 }
1911 }, interval);
1911 }, interval);
1912 this.events.trigger("autosave_enabled.Notebook", interval);
1912 this.events.trigger("autosave_enabled.Notebook", interval);
1913 } else {
1913 } else {
1914 this.autosave_timer = null;
1914 this.autosave_timer = null;
1915 this.events.trigger("autosave_disabled.Notebook");
1915 this.events.trigger("autosave_disabled.Notebook");
1916 }
1916 }
1917 };
1917 };
1918
1918
1919 /**
1919 /**
1920 * Save this notebook on the server. This becomes a notebook instance's
1920 * Save this notebook on the server. This becomes a notebook instance's
1921 * .save_notebook method *after* the entire notebook has been loaded.
1921 * .save_notebook method *after* the entire notebook has been loaded.
1922 *
1922 *
1923 * @method save_notebook
1923 * @method save_notebook
1924 */
1924 */
1925 Notebook.prototype.save_notebook = function () {
1925 Notebook.prototype.save_notebook = function () {
1926 if (!this._fully_loaded) {
1926 if (!this._fully_loaded) {
1927 this.events.trigger('notebook_save_failed.Notebook',
1927 this.events.trigger('notebook_save_failed.Notebook',
1928 new Error("Load failed, save is disabled")
1928 new Error("Load failed, save is disabled")
1929 );
1929 );
1930 return;
1930 return;
1931 } else if (!this.writable) {
1931 } else if (!this.writable) {
1932 this.events.trigger('notebook_save_failed.Notebook',
1932 this.events.trigger('notebook_save_failed.Notebook',
1933 new Error("Notebook is read-only")
1933 new Error("Notebook is read-only")
1934 );
1934 );
1935 return;
1935 return;
1936 }
1936 }
1937
1937
1938 // Create a JSON model to be sent to the server.
1938 // Create a JSON model to be sent to the server.
1939 var model = {
1939 var model = {
1940 type : "notebook",
1940 type : "notebook",
1941 content : this.toJSON()
1941 content : this.toJSON()
1942 };
1942 };
1943 // time the ajax call for autosave tuning purposes.
1943 // time the ajax call for autosave tuning purposes.
1944 var start = new Date().getTime();
1944 var start = new Date().getTime();
1945
1945
1946 var that = this;
1946 var that = this;
1947 this.contents.save(this.notebook_path, model).then(
1947 return this.contents.save(this.notebook_path, model).then(
1948 $.proxy(this.save_notebook_success, this, start),
1948 $.proxy(this.save_notebook_success, this, start),
1949 function (error) {
1949 function (error) {
1950 that.events.trigger('notebook_save_failed.Notebook', error);
1950 that.events.trigger('notebook_save_failed.Notebook', error);
1951 }
1951 }
1952 );
1952 );
1953 };
1953 };
1954
1954
1955 /**
1955 /**
1956 * Success callback for saving a notebook.
1956 * Success callback for saving a notebook.
1957 *
1957 *
1958 * @method save_notebook_success
1958 * @method save_notebook_success
1959 * @param {Integer} start Time when the save request start
1959 * @param {Integer} start Time when the save request start
1960 * @param {Object} data JSON representation of a notebook
1960 * @param {Object} data JSON representation of a notebook
1961 */
1961 */
1962 Notebook.prototype.save_notebook_success = function (start, data) {
1962 Notebook.prototype.save_notebook_success = function (start, data) {
1963 this.set_dirty(false);
1963 this.set_dirty(false);
1964 if (data.message) {
1964 if (data.message) {
1965 // save succeeded, but validation failed.
1965 // save succeeded, but validation failed.
1966 var body = $("<div>");
1966 var body = $("<div>");
1967 var title = "Notebook validation failed";
1967 var title = "Notebook validation failed";
1968
1968
1969 body.append($("<p>").text(
1969 body.append($("<p>").text(
1970 "The save operation succeeded," +
1970 "The save operation succeeded," +
1971 " but the notebook does not appear to be valid." +
1971 " but the notebook does not appear to be valid." +
1972 " The validation error was:"
1972 " The validation error was:"
1973 )).append($("<div>").addClass("validation-error").append(
1973 )).append($("<div>").addClass("validation-error").append(
1974 $("<pre>").text(data.message)
1974 $("<pre>").text(data.message)
1975 ));
1975 ));
1976 dialog.modal({
1976 dialog.modal({
1977 notebook: this,
1977 notebook: this,
1978 keyboard_manager: this.keyboard_manager,
1978 keyboard_manager: this.keyboard_manager,
1979 title: title,
1979 title: title,
1980 body: body,
1980 body: body,
1981 buttons : {
1981 buttons : {
1982 OK : {
1982 OK : {
1983 "class" : "btn-primary"
1983 "class" : "btn-primary"
1984 }
1984 }
1985 }
1985 }
1986 });
1986 });
1987 }
1987 }
1988 this.events.trigger('notebook_saved.Notebook');
1988 this.events.trigger('notebook_saved.Notebook');
1989 this._update_autosave_interval(start);
1989 this._update_autosave_interval(start);
1990 if (this._checkpoint_after_save) {
1990 if (this._checkpoint_after_save) {
1991 this.create_checkpoint();
1991 this.create_checkpoint();
1992 this._checkpoint_after_save = false;
1992 this._checkpoint_after_save = false;
1993 }
1993 }
1994 };
1994 };
1995
1995
1996 /**
1996 /**
1997 * update the autosave interval based on how long the last save took
1997 * update the autosave interval based on how long the last save took
1998 *
1998 *
1999 * @method _update_autosave_interval
1999 * @method _update_autosave_interval
2000 * @param {Integer} timestamp when the save request started
2000 * @param {Integer} timestamp when the save request started
2001 */
2001 */
2002 Notebook.prototype._update_autosave_interval = function (start) {
2002 Notebook.prototype._update_autosave_interval = function (start) {
2003 var duration = (new Date().getTime() - start);
2003 var duration = (new Date().getTime() - start);
2004 if (this.autosave_interval) {
2004 if (this.autosave_interval) {
2005 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2005 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2006 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2006 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2007 // round to 10 seconds, otherwise we will be setting a new interval too often
2007 // round to 10 seconds, otherwise we will be setting a new interval too often
2008 interval = 10000 * Math.round(interval / 10000);
2008 interval = 10000 * Math.round(interval / 10000);
2009 // set new interval, if it's changed
2009 // set new interval, if it's changed
2010 if (interval != this.autosave_interval) {
2010 if (interval != this.autosave_interval) {
2011 this.set_autosave_interval(interval);
2011 this.set_autosave_interval(interval);
2012 }
2012 }
2013 }
2013 }
2014 };
2014 };
2015
2015
2016 /**
2016 /**
2017 * Explicitly trust the output of this notebook.
2017 * Explicitly trust the output of this notebook.
2018 *
2018 *
2019 * @method trust_notebook
2019 * @method trust_notebook
2020 */
2020 */
2021 Notebook.prototype.trust_notebook = function () {
2021 Notebook.prototype.trust_notebook = function () {
2022 var body = $("<div>").append($("<p>")
2022 var body = $("<div>").append($("<p>")
2023 .text("A trusted IPython notebook may execute hidden malicious code ")
2023 .text("A trusted IPython notebook may execute hidden malicious code ")
2024 .append($("<strong>")
2024 .append($("<strong>")
2025 .append(
2025 .append(
2026 $("<em>").text("when you open it")
2026 $("<em>").text("when you open it")
2027 )
2027 )
2028 ).append(".").append(
2028 ).append(".").append(
2029 " Selecting trust will immediately reload this notebook in a trusted state."
2029 " Selecting trust will immediately reload this notebook in a trusted state."
2030 ).append(
2030 ).append(
2031 " For more information, see the "
2031 " For more information, see the "
2032 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2032 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2033 .text("IPython security documentation")
2033 .text("IPython security documentation")
2034 ).append(".")
2034 ).append(".")
2035 );
2035 );
2036
2036
2037 var nb = this;
2037 var nb = this;
2038 dialog.modal({
2038 dialog.modal({
2039 notebook: this,
2039 notebook: this,
2040 keyboard_manager: this.keyboard_manager,
2040 keyboard_manager: this.keyboard_manager,
2041 title: "Trust this notebook?",
2041 title: "Trust this notebook?",
2042 body: body,
2042 body: body,
2043
2043
2044 buttons: {
2044 buttons: {
2045 Cancel : {},
2045 Cancel : {},
2046 Trust : {
2046 Trust : {
2047 class : "btn-danger",
2047 class : "btn-danger",
2048 click : function () {
2048 click : function () {
2049 var cells = nb.get_cells();
2049 var cells = nb.get_cells();
2050 for (var i = 0; i < cells.length; i++) {
2050 for (var i = 0; i < cells.length; i++) {
2051 var cell = cells[i];
2051 var cell = cells[i];
2052 if (cell.cell_type == 'code') {
2052 if (cell.cell_type == 'code') {
2053 cell.output_area.trusted = true;
2053 cell.output_area.trusted = true;
2054 }
2054 }
2055 }
2055 }
2056 nb.events.on('notebook_saved.Notebook', function () {
2056 nb.events.on('notebook_saved.Notebook', function () {
2057 window.location.reload();
2057 window.location.reload();
2058 });
2058 });
2059 nb.save_notebook();
2059 nb.save_notebook();
2060 }
2060 }
2061 }
2061 }
2062 }
2062 }
2063 });
2063 });
2064 };
2064 };
2065
2065
2066 Notebook.prototype.copy_notebook = function () {
2066 Notebook.prototype.copy_notebook = function () {
2067 var that = this;
2067 var that = this;
2068 var base_url = this.base_url;
2068 var base_url = this.base_url;
2069 var w = window.open();
2069 var w = window.open();
2070 var parent = utils.url_path_split(this.notebook_path)[0];
2070 var parent = utils.url_path_split(this.notebook_path)[0];
2071 this.contents.copy(this.notebook_path, parent).then(
2071 this.contents.copy(this.notebook_path, parent).then(
2072 function (data) {
2072 function (data) {
2073 w.location = utils.url_join_encode(
2073 w.location = utils.url_join_encode(
2074 base_url, 'notebooks', data.path
2074 base_url, 'notebooks', data.path
2075 );
2075 );
2076 },
2076 },
2077 function(error) {
2077 function(error) {
2078 w.close();
2078 w.close();
2079 that.events.trigger('notebook_copy_failed', error);
2079 that.events.trigger('notebook_copy_failed', error);
2080 }
2080 }
2081 );
2081 );
2082 };
2082 };
2083
2083
2084 Notebook.prototype.rename = function (new_name) {
2084 Notebook.prototype.rename = function (new_name) {
2085 if (!new_name.match(/\.ipynb$/)) {
2085 if (!new_name.match(/\.ipynb$/)) {
2086 new_name = new_name + ".ipynb";
2086 new_name = new_name + ".ipynb";
2087 }
2087 }
2088
2088
2089 var that = this;
2089 var that = this;
2090 var parent = utils.url_path_split(this.notebook_path)[0];
2090 var parent = utils.url_path_split(this.notebook_path)[0];
2091 var new_path = utils.url_path_join(parent, new_name);
2091 var new_path = utils.url_path_join(parent, new_name);
2092 return this.contents.rename(this.notebook_path, new_path).then(
2092 return this.contents.rename(this.notebook_path, new_path).then(
2093 function (json) {
2093 function (json) {
2094 that.notebook_name = json.name;
2094 that.notebook_name = json.name;
2095 that.notebook_path = json.path;
2095 that.notebook_path = json.path;
2096 that.session.rename_notebook(json.path);
2096 that.session.rename_notebook(json.path);
2097 that.events.trigger('notebook_renamed.Notebook', json);
2097 that.events.trigger('notebook_renamed.Notebook', json);
2098 }
2098 }
2099 );
2099 );
2100 };
2100 };
2101
2101
2102 Notebook.prototype.delete = function () {
2102 Notebook.prototype.delete = function () {
2103 this.contents.delete(this.notebook_path);
2103 this.contents.delete(this.notebook_path);
2104 };
2104 };
2105
2105
2106 /**
2106 /**
2107 * Request a notebook's data from the server.
2107 * Request a notebook's data from the server.
2108 *
2108 *
2109 * @method load_notebook
2109 * @method load_notebook
2110 * @param {String} notebook_path A notebook to load
2110 * @param {String} notebook_path A notebook to load
2111 */
2111 */
2112 Notebook.prototype.load_notebook = function (notebook_path) {
2112 Notebook.prototype.load_notebook = function (notebook_path) {
2113 this.notebook_path = notebook_path;
2113 this.notebook_path = notebook_path;
2114 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2114 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2115 this.events.trigger('notebook_loading.Notebook');
2115 this.events.trigger('notebook_loading.Notebook');
2116 this.contents.get(notebook_path, {type: 'notebook'}).then(
2116 this.contents.get(notebook_path, {type: 'notebook'}).then(
2117 $.proxy(this.load_notebook_success, this),
2117 $.proxy(this.load_notebook_success, this),
2118 $.proxy(this.load_notebook_error, this)
2118 $.proxy(this.load_notebook_error, this)
2119 );
2119 );
2120 };
2120 };
2121
2121
2122 /**
2122 /**
2123 * Success callback for loading a notebook from the server.
2123 * Success callback for loading a notebook from the server.
2124 *
2124 *
2125 * Load notebook data from the JSON response.
2125 * Load notebook data from the JSON response.
2126 *
2126 *
2127 * @method load_notebook_success
2127 * @method load_notebook_success
2128 * @param {Object} data JSON representation of a notebook
2128 * @param {Object} data JSON representation of a notebook
2129 */
2129 */
2130 Notebook.prototype.load_notebook_success = function (data) {
2130 Notebook.prototype.load_notebook_success = function (data) {
2131 var failed, msg;
2131 var failed, msg;
2132 try {
2132 try {
2133 this.fromJSON(data);
2133 this.fromJSON(data);
2134 } catch (e) {
2134 } catch (e) {
2135 failed = e;
2135 failed = e;
2136 console.log("Notebook failed to load from JSON:", e);
2136 console.log("Notebook failed to load from JSON:", e);
2137 }
2137 }
2138 if (failed || data.message) {
2138 if (failed || data.message) {
2139 // *either* fromJSON failed or validation failed
2139 // *either* fromJSON failed or validation failed
2140 var body = $("<div>");
2140 var body = $("<div>");
2141 var title;
2141 var title;
2142 if (failed) {
2142 if (failed) {
2143 title = "Notebook failed to load";
2143 title = "Notebook failed to load";
2144 body.append($("<p>").text(
2144 body.append($("<p>").text(
2145 "The error was: "
2145 "The error was: "
2146 )).append($("<div>").addClass("js-error").text(
2146 )).append($("<div>").addClass("js-error").text(
2147 failed.toString()
2147 failed.toString()
2148 )).append($("<p>").text(
2148 )).append($("<p>").text(
2149 "See the error console for details."
2149 "See the error console for details."
2150 ));
2150 ));
2151 } else {
2151 } else {
2152 title = "Notebook validation failed";
2152 title = "Notebook validation failed";
2153 }
2153 }
2154
2154
2155 if (data.message) {
2155 if (data.message) {
2156 if (failed) {
2156 if (failed) {
2157 msg = "The notebook also failed validation:";
2157 msg = "The notebook also failed validation:";
2158 } else {
2158 } else {
2159 msg = "An invalid notebook may not function properly." +
2159 msg = "An invalid notebook may not function properly." +
2160 " The validation error was:";
2160 " The validation error was:";
2161 }
2161 }
2162 body.append($("<p>").text(
2162 body.append($("<p>").text(
2163 msg
2163 msg
2164 )).append($("<div>").addClass("validation-error").append(
2164 )).append($("<div>").addClass("validation-error").append(
2165 $("<pre>").text(data.message)
2165 $("<pre>").text(data.message)
2166 ));
2166 ));
2167 }
2167 }
2168
2168
2169 dialog.modal({
2169 dialog.modal({
2170 notebook: this,
2170 notebook: this,
2171 keyboard_manager: this.keyboard_manager,
2171 keyboard_manager: this.keyboard_manager,
2172 title: title,
2172 title: title,
2173 body: body,
2173 body: body,
2174 buttons : {
2174 buttons : {
2175 OK : {
2175 OK : {
2176 "class" : "btn-primary"
2176 "class" : "btn-primary"
2177 }
2177 }
2178 }
2178 }
2179 });
2179 });
2180 }
2180 }
2181 if (this.ncells() === 0) {
2181 if (this.ncells() === 0) {
2182 this.insert_cell_below('code');
2182 this.insert_cell_below('code');
2183 this.edit_mode(0);
2183 this.edit_mode(0);
2184 } else {
2184 } else {
2185 this.select(0);
2185 this.select(0);
2186 this.handle_command_mode(this.get_cell(0));
2186 this.handle_command_mode(this.get_cell(0));
2187 }
2187 }
2188 this.set_dirty(false);
2188 this.set_dirty(false);
2189 this.scroll_to_top();
2189 this.scroll_to_top();
2190 this.writable = data.writable || false;
2190 this.writable = data.writable || false;
2191 var nbmodel = data.content;
2191 var nbmodel = data.content;
2192 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2192 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2193 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2193 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2194 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2194 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2195 var src;
2195 var src;
2196 if (nbmodel.nbformat > orig_nbformat) {
2196 if (nbmodel.nbformat > orig_nbformat) {
2197 src = " an older notebook format ";
2197 src = " an older notebook format ";
2198 } else {
2198 } else {
2199 src = " a newer notebook format ";
2199 src = " a newer notebook format ";
2200 }
2200 }
2201
2201
2202 msg = "This notebook has been converted from" + src +
2202 msg = "This notebook has been converted from" + src +
2203 "(v"+orig_nbformat+") to the current notebook " +
2203 "(v"+orig_nbformat+") to the current notebook " +
2204 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2204 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2205 "current notebook format will be used.";
2205 "current notebook format will be used.";
2206
2206
2207 if (nbmodel.nbformat > orig_nbformat) {
2207 if (nbmodel.nbformat > orig_nbformat) {
2208 msg += " Older versions of IPython may not be able to read the new format.";
2208 msg += " Older versions of IPython may not be able to read the new format.";
2209 } else {
2209 } else {
2210 msg += " Some features of the original notebook may not be available.";
2210 msg += " Some features of the original notebook may not be available.";
2211 }
2211 }
2212 msg += " To preserve the original version, close the " +
2212 msg += " To preserve the original version, close the " +
2213 "notebook without saving it.";
2213 "notebook without saving it.";
2214 dialog.modal({
2214 dialog.modal({
2215 notebook: this,
2215 notebook: this,
2216 keyboard_manager: this.keyboard_manager,
2216 keyboard_manager: this.keyboard_manager,
2217 title : "Notebook converted",
2217 title : "Notebook converted",
2218 body : msg,
2218 body : msg,
2219 buttons : {
2219 buttons : {
2220 OK : {
2220 OK : {
2221 class : "btn-primary"
2221 class : "btn-primary"
2222 }
2222 }
2223 }
2223 }
2224 });
2224 });
2225 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2225 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2226 var that = this;
2226 var that = this;
2227 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2227 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2228 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2228 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2229 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2229 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2230 this_vs + ". You can still work with this notebook, but some features " +
2230 this_vs + ". You can still work with this notebook, but some features " +
2231 "introduced in later notebook versions may not be available.";
2231 "introduced in later notebook versions may not be available.";
2232
2232
2233 dialog.modal({
2233 dialog.modal({
2234 notebook: this,
2234 notebook: this,
2235 keyboard_manager: this.keyboard_manager,
2235 keyboard_manager: this.keyboard_manager,
2236 title : "Newer Notebook",
2236 title : "Newer Notebook",
2237 body : msg,
2237 body : msg,
2238 buttons : {
2238 buttons : {
2239 OK : {
2239 OK : {
2240 class : "btn-danger"
2240 class : "btn-danger"
2241 }
2241 }
2242 }
2242 }
2243 });
2243 });
2244
2244
2245 }
2245 }
2246
2246
2247 // Create the session after the notebook is completely loaded to prevent
2247 // Create the session after the notebook is completely loaded to prevent
2248 // code execution upon loading, which is a security risk.
2248 // code execution upon loading, which is a security risk.
2249 if (this.session === null) {
2249 if (this.session === null) {
2250 var kernelspec = this.metadata.kernelspec || {};
2250 var kernelspec = this.metadata.kernelspec || {};
2251 var kernel_name = kernelspec.name;
2251 var kernel_name = kernelspec.name;
2252
2252
2253 this.start_session(kernel_name);
2253 this.start_session(kernel_name);
2254 }
2254 }
2255 // load our checkpoint list
2255 // load our checkpoint list
2256 this.list_checkpoints();
2256 this.list_checkpoints();
2257
2257
2258 // load toolbar state
2258 // load toolbar state
2259 if (this.metadata.celltoolbar) {
2259 if (this.metadata.celltoolbar) {
2260 celltoolbar.CellToolbar.global_show();
2260 celltoolbar.CellToolbar.global_show();
2261 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2261 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2262 } else {
2262 } else {
2263 celltoolbar.CellToolbar.global_hide();
2263 celltoolbar.CellToolbar.global_hide();
2264 }
2264 }
2265
2265
2266 if (!this.writable) {
2266 if (!this.writable) {
2267 this.set_autosave_interval(0);
2267 this.set_autosave_interval(0);
2268 this.events.trigger('notebook_read_only.Notebook');
2268 this.events.trigger('notebook_read_only.Notebook');
2269 }
2269 }
2270
2270
2271 // now that we're fully loaded, it is safe to restore save functionality
2271 // now that we're fully loaded, it is safe to restore save functionality
2272 this._fully_loaded = true;
2272 this._fully_loaded = true;
2273 this.events.trigger('notebook_loaded.Notebook');
2273 this.events.trigger('notebook_loaded.Notebook');
2274 };
2274 };
2275
2275
2276 /**
2276 /**
2277 * Failure callback for loading a notebook from the server.
2277 * Failure callback for loading a notebook from the server.
2278 *
2278 *
2279 * @method load_notebook_error
2279 * @method load_notebook_error
2280 * @param {Error} error
2280 * @param {Error} error
2281 */
2281 */
2282 Notebook.prototype.load_notebook_error = function (error) {
2282 Notebook.prototype.load_notebook_error = function (error) {
2283 this.events.trigger('notebook_load_failed.Notebook', error);
2283 this.events.trigger('notebook_load_failed.Notebook', error);
2284 var msg;
2284 var msg;
2285 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2285 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2286 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2286 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2287 msg = "An unknown error occurred while loading this notebook. " +
2287 msg = "An unknown error occurred while loading this notebook. " +
2288 "This version can load notebook formats " +
2288 "This version can load notebook formats " +
2289 "v" + this.nbformat + " or earlier. See the server log for details.";
2289 "v" + this.nbformat + " or earlier. See the server log for details.";
2290 } else {
2290 } else {
2291 msg = error.message;
2291 msg = error.message;
2292 }
2292 }
2293 dialog.modal({
2293 dialog.modal({
2294 notebook: this,
2294 notebook: this,
2295 keyboard_manager: this.keyboard_manager,
2295 keyboard_manager: this.keyboard_manager,
2296 title: "Error loading notebook",
2296 title: "Error loading notebook",
2297 body : msg,
2297 body : msg,
2298 buttons : {
2298 buttons : {
2299 "OK": {}
2299 "OK": {}
2300 }
2300 }
2301 });
2301 });
2302 };
2302 };
2303
2303
2304 /********************* checkpoint-related *********************/
2304 /********************* checkpoint-related *********************/
2305
2305
2306 /**
2306 /**
2307 * Save the notebook then immediately create a checkpoint.
2307 * Save the notebook then immediately create a checkpoint.
2308 *
2308 *
2309 * @method save_checkpoint
2309 * @method save_checkpoint
2310 */
2310 */
2311 Notebook.prototype.save_checkpoint = function () {
2311 Notebook.prototype.save_checkpoint = function () {
2312 this._checkpoint_after_save = true;
2312 this._checkpoint_after_save = true;
2313 this.save_notebook();
2313 this.save_notebook();
2314 };
2314 };
2315
2315
2316 /**
2316 /**
2317 * Add a checkpoint for this notebook.
2317 * Add a checkpoint for this notebook.
2318 * for use as a callback from checkpoint creation.
2318 * for use as a callback from checkpoint creation.
2319 *
2319 *
2320 * @method add_checkpoint
2320 * @method add_checkpoint
2321 */
2321 */
2322 Notebook.prototype.add_checkpoint = function (checkpoint) {
2322 Notebook.prototype.add_checkpoint = function (checkpoint) {
2323 var found = false;
2323 var found = false;
2324 for (var i = 0; i < this.checkpoints.length; i++) {
2324 for (var i = 0; i < this.checkpoints.length; i++) {
2325 var existing = this.checkpoints[i];
2325 var existing = this.checkpoints[i];
2326 if (existing.id == checkpoint.id) {
2326 if (existing.id == checkpoint.id) {
2327 found = true;
2327 found = true;
2328 this.checkpoints[i] = checkpoint;
2328 this.checkpoints[i] = checkpoint;
2329 break;
2329 break;
2330 }
2330 }
2331 }
2331 }
2332 if (!found) {
2332 if (!found) {
2333 this.checkpoints.push(checkpoint);
2333 this.checkpoints.push(checkpoint);
2334 }
2334 }
2335 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2335 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2336 };
2336 };
2337
2337
2338 /**
2338 /**
2339 * List checkpoints for this notebook.
2339 * List checkpoints for this notebook.
2340 *
2340 *
2341 * @method list_checkpoints
2341 * @method list_checkpoints
2342 */
2342 */
2343 Notebook.prototype.list_checkpoints = function () {
2343 Notebook.prototype.list_checkpoints = function () {
2344 var that = this;
2344 var that = this;
2345 this.contents.list_checkpoints(this.notebook_path).then(
2345 this.contents.list_checkpoints(this.notebook_path).then(
2346 $.proxy(this.list_checkpoints_success, this),
2346 $.proxy(this.list_checkpoints_success, this),
2347 function(error) {
2347 function(error) {
2348 that.events.trigger('list_checkpoints_failed.Notebook', error);
2348 that.events.trigger('list_checkpoints_failed.Notebook', error);
2349 }
2349 }
2350 );
2350 );
2351 };
2351 };
2352
2352
2353 /**
2353 /**
2354 * Success callback for listing checkpoints.
2354 * Success callback for listing checkpoints.
2355 *
2355 *
2356 * @method list_checkpoint_success
2356 * @method list_checkpoint_success
2357 * @param {Object} data JSON representation of a checkpoint
2357 * @param {Object} data JSON representation of a checkpoint
2358 */
2358 */
2359 Notebook.prototype.list_checkpoints_success = function (data) {
2359 Notebook.prototype.list_checkpoints_success = function (data) {
2360 this.checkpoints = data;
2360 this.checkpoints = data;
2361 if (data.length) {
2361 if (data.length) {
2362 this.last_checkpoint = data[data.length - 1];
2362 this.last_checkpoint = data[data.length - 1];
2363 } else {
2363 } else {
2364 this.last_checkpoint = null;
2364 this.last_checkpoint = null;
2365 }
2365 }
2366 this.events.trigger('checkpoints_listed.Notebook', [data]);
2366 this.events.trigger('checkpoints_listed.Notebook', [data]);
2367 };
2367 };
2368
2368
2369 /**
2369 /**
2370 * Create a checkpoint of this notebook on the server from the most recent save.
2370 * Create a checkpoint of this notebook on the server from the most recent save.
2371 *
2371 *
2372 * @method create_checkpoint
2372 * @method create_checkpoint
2373 */
2373 */
2374 Notebook.prototype.create_checkpoint = function () {
2374 Notebook.prototype.create_checkpoint = function () {
2375 var that = this;
2375 var that = this;
2376 this.contents.create_checkpoint(this.notebook_path).then(
2376 this.contents.create_checkpoint(this.notebook_path).then(
2377 $.proxy(this.create_checkpoint_success, this),
2377 $.proxy(this.create_checkpoint_success, this),
2378 function (error) {
2378 function (error) {
2379 that.events.trigger('checkpoint_failed.Notebook', error);
2379 that.events.trigger('checkpoint_failed.Notebook', error);
2380 }
2380 }
2381 );
2381 );
2382 };
2382 };
2383
2383
2384 /**
2384 /**
2385 * Success callback for creating a checkpoint.
2385 * Success callback for creating a checkpoint.
2386 *
2386 *
2387 * @method create_checkpoint_success
2387 * @method create_checkpoint_success
2388 * @param {Object} data JSON representation of a checkpoint
2388 * @param {Object} data JSON representation of a checkpoint
2389 */
2389 */
2390 Notebook.prototype.create_checkpoint_success = function (data) {
2390 Notebook.prototype.create_checkpoint_success = function (data) {
2391 this.add_checkpoint(data);
2391 this.add_checkpoint(data);
2392 this.events.trigger('checkpoint_created.Notebook', data);
2392 this.events.trigger('checkpoint_created.Notebook', data);
2393 };
2393 };
2394
2394
2395 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2395 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2396 var that = this;
2396 var that = this;
2397 checkpoint = checkpoint || this.last_checkpoint;
2397 checkpoint = checkpoint || this.last_checkpoint;
2398 if ( ! checkpoint ) {
2398 if ( ! checkpoint ) {
2399 console.log("restore dialog, but no checkpoint to restore to!");
2399 console.log("restore dialog, but no checkpoint to restore to!");
2400 return;
2400 return;
2401 }
2401 }
2402 var body = $('<div/>').append(
2402 var body = $('<div/>').append(
2403 $('<p/>').addClass("p-space").text(
2403 $('<p/>').addClass("p-space").text(
2404 "Are you sure you want to revert the notebook to " +
2404 "Are you sure you want to revert the notebook to " +
2405 "the latest checkpoint?"
2405 "the latest checkpoint?"
2406 ).append(
2406 ).append(
2407 $("<strong/>").text(
2407 $("<strong/>").text(
2408 " This cannot be undone."
2408 " This cannot be undone."
2409 )
2409 )
2410 )
2410 )
2411 ).append(
2411 ).append(
2412 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2412 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2413 ).append(
2413 ).append(
2414 $('<p/>').addClass("p-space").text(
2414 $('<p/>').addClass("p-space").text(
2415 Date(checkpoint.last_modified)
2415 Date(checkpoint.last_modified)
2416 ).css("text-align", "center")
2416 ).css("text-align", "center")
2417 );
2417 );
2418
2418
2419 dialog.modal({
2419 dialog.modal({
2420 notebook: this,
2420 notebook: this,
2421 keyboard_manager: this.keyboard_manager,
2421 keyboard_manager: this.keyboard_manager,
2422 title : "Revert notebook to checkpoint",
2422 title : "Revert notebook to checkpoint",
2423 body : body,
2423 body : body,
2424 buttons : {
2424 buttons : {
2425 Revert : {
2425 Revert : {
2426 class : "btn-danger",
2426 class : "btn-danger",
2427 click : function () {
2427 click : function () {
2428 that.restore_checkpoint(checkpoint.id);
2428 that.restore_checkpoint(checkpoint.id);
2429 }
2429 }
2430 },
2430 },
2431 Cancel : {}
2431 Cancel : {}
2432 }
2432 }
2433 });
2433 });
2434 };
2434 };
2435
2435
2436 /**
2436 /**
2437 * Restore the notebook to a checkpoint state.
2437 * Restore the notebook to a checkpoint state.
2438 *
2438 *
2439 * @method restore_checkpoint
2439 * @method restore_checkpoint
2440 * @param {String} checkpoint ID
2440 * @param {String} checkpoint ID
2441 */
2441 */
2442 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2442 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2443 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2443 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2444 var that = this;
2444 var that = this;
2445 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2445 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2446 $.proxy(this.restore_checkpoint_success, this),
2446 $.proxy(this.restore_checkpoint_success, this),
2447 function (error) {
2447 function (error) {
2448 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2448 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2449 }
2449 }
2450 );
2450 );
2451 };
2451 };
2452
2452
2453 /**
2453 /**
2454 * Success callback for restoring a notebook to a checkpoint.
2454 * Success callback for restoring a notebook to a checkpoint.
2455 *
2455 *
2456 * @method restore_checkpoint_success
2456 * @method restore_checkpoint_success
2457 */
2457 */
2458 Notebook.prototype.restore_checkpoint_success = function () {
2458 Notebook.prototype.restore_checkpoint_success = function () {
2459 this.events.trigger('checkpoint_restored.Notebook');
2459 this.events.trigger('checkpoint_restored.Notebook');
2460 this.load_notebook(this.notebook_path);
2460 this.load_notebook(this.notebook_path);
2461 };
2461 };
2462
2462
2463 /**
2463 /**
2464 * Delete a notebook checkpoint.
2464 * Delete a notebook checkpoint.
2465 *
2465 *
2466 * @method delete_checkpoint
2466 * @method delete_checkpoint
2467 * @param {String} checkpoint ID
2467 * @param {String} checkpoint ID
2468 */
2468 */
2469 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2469 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2470 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2470 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2471 var that = this;
2471 var that = this;
2472 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2472 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2473 $.proxy(this.delete_checkpoint_success, this),
2473 $.proxy(this.delete_checkpoint_success, this),
2474 function (error) {
2474 function (error) {
2475 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2475 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2476 }
2476 }
2477 );
2477 );
2478 };
2478 };
2479
2479
2480 /**
2480 /**
2481 * Success callback for deleting a notebook checkpoint
2481 * Success callback for deleting a notebook checkpoint
2482 *
2482 *
2483 * @method delete_checkpoint_success
2483 * @method delete_checkpoint_success
2484 */
2484 */
2485 Notebook.prototype.delete_checkpoint_success = function () {
2485 Notebook.prototype.delete_checkpoint_success = function () {
2486 this.events.trigger('checkpoint_deleted.Notebook');
2486 this.events.trigger('checkpoint_deleted.Notebook');
2487 this.load_notebook(this.notebook_path);
2487 this.load_notebook(this.notebook_path);
2488 };
2488 };
2489
2489
2490
2490
2491 // For backwards compatability.
2491 // For backwards compatability.
2492 IPython.Notebook = Notebook;
2492 IPython.Notebook = Notebook;
2493
2493
2494 return {'Notebook': Notebook};
2494 return {'Notebook': Notebook};
2495 });
2495 });
@@ -1,328 +1,328 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16
16
17 {{super()}}
17 {{super()}}
18
18
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20
20
21 {% endblock %}
21 {% endblock %}
22
22
23 {% block params %}
23 {% block params %}
24
24
25 data-project="{{project}}"
25 data-project="{{project}}"
26 data-base-url="{{base_url}}"
26 data-base-url="{{base_url}}"
27 data-ws-url="{{ws_url}}"
27 data-ws-url="{{ws_url}}"
28 data-notebook-name="{{notebook_name}}"
28 data-notebook-name="{{notebook_name}}"
29 data-notebook-path="{{notebook_path}}"
29 data-notebook-path="{{notebook_path}}"
30 class="notebook_app"
30 class="notebook_app"
31
31
32 {% endblock %}
32 {% endblock %}
33
33
34
34
35 {% block header %}
35 {% block header %}
36
36
37
37
38 <span id="save_widget" class="nav pull-left">
38 <span id="save_widget" class="nav pull-left">
39 <span id="notebook_name"></span>
39 <span id="notebook_name"></span>
40 <span id="checkpoint_status"></span>
40 <span id="checkpoint_status"></span>
41 <span id="autosave_status"></span>
41 <span id="autosave_status"></span>
42 </span>
42 </span>
43
43
44 <span id="kernel_selector_widget" class="pull-right dropdown">
44 <span id="kernel_selector_widget" class="pull-right dropdown">
45 <button class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
45 <button class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
46 <span class='kernel_name'>Python</span>
46 <span class='kernel_name'>Python</span>
47 <span class="caret"></span>
47 <span class="caret"></span>
48 </button>
48 </button>
49 <ul id="kernel_selector" class="dropdown-menu">
49 <ul id="kernel_selector" class="dropdown-menu">
50 </ul>
50 </ul>
51 </span>
51 </span>
52
52
53 {% endblock %}
53 {% endblock %}
54
54
55
55
56 {% block site %}
56 {% block site %}
57
57
58 <div id="menubar-container" class="container">
58 <div id="menubar-container" class="container">
59 <div id="menubar">
59 <div id="menubar">
60 <div id="menus" class="navbar navbar-default" role="navigation">
60 <div id="menus" class="navbar navbar-default" role="navigation">
61 <div class="container-fluid">
61 <div class="container-fluid">
62 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
62 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
63 <i class="fa fa-bars"></i>
63 <i class="fa fa-bars"></i>
64 <span class="navbar-text">Menu</span>
64 <span class="navbar-text">Menu</span>
65 </button>
65 </button>
66 <ul class="nav navbar-nav navbar-right">
66 <ul class="nav navbar-nav navbar-right">
67 <li id="kernel_indicator">
67 <li id="kernel_indicator">
68 <i id="kernel_indicator_icon"></i>
68 <i id="kernel_indicator_icon"></i>
69 </li>
69 </li>
70 <li id="modal_indicator">
70 <li id="modal_indicator">
71 <i id="modal_indicator_icon"></i>
71 <i id="modal_indicator_icon"></i>
72 </li>
72 </li>
73 <li id="notification_area"></li>
73 <li id="notification_area"></li>
74 </ul>
74 </ul>
75 <div class="navbar-collapse collapse">
75 <div class="navbar-collapse collapse">
76 <ul class="nav navbar-nav">
76 <ul class="nav navbar-nav">
77 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
77 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
78 <ul id="file_menu" class="dropdown-menu">
78 <ul id="file_menu" class="dropdown-menu">
79 <li id="new_notebook"
79 <li id="new_notebook"
80 title="Make a new notebook (Opens a new window)">
80 title="Make a new notebook (Opens a new window)">
81 <a href="#">New</a></li>
81 <a href="#">New</a></li>
82 <li id="open_notebook"
82 <li id="open_notebook"
83 title="Opens a new window with the Dashboard view">
83 title="Opens a new window with the Dashboard view">
84 <a href="#">Open...</a></li>
84 <a href="#">Open...</a></li>
85 <!-- <hr/> -->
85 <!-- <hr/> -->
86 <li class="divider"></li>
86 <li class="divider"></li>
87 <li id="copy_notebook"
87 <li id="copy_notebook"
88 title="Open a copy of this notebook's contents and start a new kernel">
88 title="Open a copy of this notebook's contents and start a new kernel">
89 <a href="#">Make a Copy...</a></li>
89 <a href="#">Make a Copy...</a></li>
90 <li id="rename_notebook"><a href="#">Rename...</a></li>
90 <li id="rename_notebook"><a href="#">Rename...</a></li>
91 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
91 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
92 <!-- <hr/> -->
92 <!-- <hr/> -->
93 <li class="divider"></li>
93 <li class="divider"></li>
94 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
94 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
95 <ul class="dropdown-menu">
95 <ul class="dropdown-menu">
96 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
99 <li><a href="#"></a></li>
99 <li><a href="#"></a></li>
100 <li><a href="#"></a></li>
100 <li><a href="#"></a></li>
101 </ul>
101 </ul>
102 </li>
102 </li>
103 <li class="divider"></li>
103 <li class="divider"></li>
104 <li id="print_preview"><a href="#">Print Preview</a></li>
104 <li id="print_preview"><a href="#">Print Preview</a></li>
105 <li class="dropdown-submenu"><a href="#">Download as</a>
105 <li class="dropdown-submenu"><a href="#">Download as</a>
106 <ul class="dropdown-menu">
106 <ul class="dropdown-menu">
107 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
107 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
108 <li id="download_py"><a href="#">Python (.py)</a></li>
108 <li id="download_script"><a href="#">Script</a></li>
109 <li id="download_html"><a href="#">HTML (.html)</a></li>
109 <li id="download_html"><a href="#">HTML (.html)</a></li>
110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
111 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
111 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
112 </ul>
112 </ul>
113 </li>
113 </li>
114 <li class="divider"></li>
114 <li class="divider"></li>
115 <li id="trust_notebook"
115 <li id="trust_notebook"
116 title="Trust the output of this notebook">
116 title="Trust the output of this notebook">
117 <a href="#" >Trust Notebook</a></li>
117 <a href="#" >Trust Notebook</a></li>
118 <li class="divider"></li>
118 <li class="divider"></li>
119 <li id="kill_and_exit"
119 <li id="kill_and_exit"
120 title="Shutdown this notebook's kernel, and close this window">
120 title="Shutdown this notebook's kernel, and close this window">
121 <a href="#" >Close and halt</a></li>
121 <a href="#" >Close and halt</a></li>
122 </ul>
122 </ul>
123 </li>
123 </li>
124 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
124 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
125 <ul id="edit_menu" class="dropdown-menu">
125 <ul id="edit_menu" class="dropdown-menu">
126 <li id="cut_cell"><a href="#">Cut Cell</a></li>
126 <li id="cut_cell"><a href="#">Cut Cell</a></li>
127 <li id="copy_cell"><a href="#">Copy Cell</a></li>
127 <li id="copy_cell"><a href="#">Copy Cell</a></li>
128 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
128 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
129 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
129 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
130 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
130 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
131 <li id="delete_cell"><a href="#">Delete Cell</a></li>
131 <li id="delete_cell"><a href="#">Delete Cell</a></li>
132 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
132 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
133 <li class="divider"></li>
133 <li class="divider"></li>
134 <li id="split_cell"><a href="#">Split Cell</a></li>
134 <li id="split_cell"><a href="#">Split Cell</a></li>
135 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
135 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
136 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
136 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
137 <li class="divider"></li>
137 <li class="divider"></li>
138 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
138 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
139 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
139 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
140 <li class="divider"></li>
140 <li class="divider"></li>
141 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
141 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
142 </ul>
142 </ul>
143 </li>
143 </li>
144 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
144 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
145 <ul id="view_menu" class="dropdown-menu">
145 <ul id="view_menu" class="dropdown-menu">
146 <li id="toggle_header"
146 <li id="toggle_header"
147 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
147 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
148 <a href="#">Toggle Header</a></li>
148 <a href="#">Toggle Header</a></li>
149 <li id="toggle_toolbar"
149 <li id="toggle_toolbar"
150 title="Show/Hide the action icons (below menu bar)">
150 title="Show/Hide the action icons (below menu bar)">
151 <a href="#">Toggle Toolbar</a></li>
151 <a href="#">Toggle Toolbar</a></li>
152 </ul>
152 </ul>
153 </li>
153 </li>
154 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
154 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
155 <ul id="insert_menu" class="dropdown-menu">
155 <ul id="insert_menu" class="dropdown-menu">
156 <li id="insert_cell_above"
156 <li id="insert_cell_above"
157 title="Insert an empty Code cell above the currently active cell">
157 title="Insert an empty Code cell above the currently active cell">
158 <a href="#">Insert Cell Above</a></li>
158 <a href="#">Insert Cell Above</a></li>
159 <li id="insert_cell_below"
159 <li id="insert_cell_below"
160 title="Insert an empty Code cell below the currently active cell">
160 title="Insert an empty Code cell below the currently active cell">
161 <a href="#">Insert Cell Below</a></li>
161 <a href="#">Insert Cell Below</a></li>
162 </ul>
162 </ul>
163 </li>
163 </li>
164 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
164 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
165 <ul id="cell_menu" class="dropdown-menu">
165 <ul id="cell_menu" class="dropdown-menu">
166 <li id="run_cell" title="Run this cell, and move cursor to the next one">
166 <li id="run_cell" title="Run this cell, and move cursor to the next one">
167 <a href="#">Run</a></li>
167 <a href="#">Run</a></li>
168 <li id="run_cell_select_below" title="Run this cell, select below">
168 <li id="run_cell_select_below" title="Run this cell, select below">
169 <a href="#">Run and Select Below</a></li>
169 <a href="#">Run and Select Below</a></li>
170 <li id="run_cell_insert_below" title="Run this cell, insert below">
170 <li id="run_cell_insert_below" title="Run this cell, insert below">
171 <a href="#">Run and Insert Below</a></li>
171 <a href="#">Run and Insert Below</a></li>
172 <li id="run_all_cells" title="Run all cells in the notebook">
172 <li id="run_all_cells" title="Run all cells in the notebook">
173 <a href="#">Run All</a></li>
173 <a href="#">Run All</a></li>
174 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
174 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
175 <a href="#">Run All Above</a></li>
175 <a href="#">Run All Above</a></li>
176 <li id="run_all_cells_below" title="Run this cell and all cells below it">
176 <li id="run_all_cells_below" title="Run this cell and all cells below it">
177 <a href="#">Run All Below</a></li>
177 <a href="#">Run All Below</a></li>
178 <li class="divider"></li>
178 <li class="divider"></li>
179 <li id="change_cell_type" class="dropdown-submenu"
179 <li id="change_cell_type" class="dropdown-submenu"
180 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
180 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
181 <a href="#">Cell Type</a>
181 <a href="#">Cell Type</a>
182 <ul class="dropdown-menu">
182 <ul class="dropdown-menu">
183 <li id="to_code"
183 <li id="to_code"
184 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
184 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
185 <a href="#">Code</a></li>
185 <a href="#">Code</a></li>
186 <li id="to_markdown"
186 <li id="to_markdown"
187 title="Contents will be rendered as HTML and serve as explanatory text">
187 title="Contents will be rendered as HTML and serve as explanatory text">
188 <a href="#">Markdown</a></li>
188 <a href="#">Markdown</a></li>
189 <li id="to_raw"
189 <li id="to_raw"
190 title="Contents will pass through nbconvert unmodified">
190 title="Contents will pass through nbconvert unmodified">
191 <a href="#">Raw NBConvert</a></li>
191 <a href="#">Raw NBConvert</a></li>
192 </ul>
192 </ul>
193 </li>
193 </li>
194 <li class="divider"></li>
194 <li class="divider"></li>
195 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
195 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
196 <ul class="dropdown-menu">
196 <ul class="dropdown-menu">
197 <li id="toggle_current_output"
197 <li id="toggle_current_output"
198 title="Hide/Show the output of the current cell">
198 title="Hide/Show the output of the current cell">
199 <a href="#">Toggle</a>
199 <a href="#">Toggle</a>
200 </li>
200 </li>
201 <li id="toggle_current_output_scroll"
201 <li id="toggle_current_output_scroll"
202 title="Scroll the output of the current cell">
202 title="Scroll the output of the current cell">
203 <a href="#">Toggle Scrolling</a>
203 <a href="#">Toggle Scrolling</a>
204 </li>
204 </li>
205 <li id="clear_current_output"
205 <li id="clear_current_output"
206 title="Clear the output of the current cell">
206 title="Clear the output of the current cell">
207 <a href="#">Clear</a>
207 <a href="#">Clear</a>
208 </li>
208 </li>
209 </ul>
209 </ul>
210 </li>
210 </li>
211 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
211 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
212 <ul class="dropdown-menu">
212 <ul class="dropdown-menu">
213 <li id="toggle_all_output"
213 <li id="toggle_all_output"
214 title="Hide/Show the output of all cells">
214 title="Hide/Show the output of all cells">
215 <a href="#">Toggle</a>
215 <a href="#">Toggle</a>
216 </li>
216 </li>
217 <li id="toggle_all_output_scroll"
217 <li id="toggle_all_output_scroll"
218 title="Scroll the output of all cells">
218 title="Scroll the output of all cells">
219 <a href="#">Toggle Scrolling</a>
219 <a href="#">Toggle Scrolling</a>
220 </li>
220 </li>
221 <li id="clear_all_output"
221 <li id="clear_all_output"
222 title="Clear the output of all cells">
222 title="Clear the output of all cells">
223 <a href="#">Clear</a>
223 <a href="#">Clear</a>
224 </li>
224 </li>
225 </ul>
225 </ul>
226 </li>
226 </li>
227 </ul>
227 </ul>
228 </li>
228 </li>
229 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
229 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
230 <ul id="kernel_menu" class="dropdown-menu">
230 <ul id="kernel_menu" class="dropdown-menu">
231 <li id="int_kernel"
231 <li id="int_kernel"
232 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
232 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
233 <a href="#">Interrupt</a>
233 <a href="#">Interrupt</a>
234 </li>
234 </li>
235 <li id="restart_kernel"
235 <li id="restart_kernel"
236 title="Restart the Kernel">
236 title="Restart the Kernel">
237 <a href="#">Restart</a>
237 <a href="#">Restart</a>
238 </li>
238 </li>
239 <li id="reconnect_kernel"
239 <li id="reconnect_kernel"
240 title="Reconnect to the Kernel">
240 title="Reconnect to the Kernel">
241 <a href="#">Reconnect</a>
241 <a href="#">Reconnect</a>
242 </li>
242 </li>
243 <li class="divider"></li>
243 <li class="divider"></li>
244 <li id="menu-change-kernel" class="dropdown-submenu">
244 <li id="menu-change-kernel" class="dropdown-submenu">
245 <a href="#">Change kernel</a>
245 <a href="#">Change kernel</a>
246 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
246 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
247 </li>
247 </li>
248 </ul>
248 </ul>
249 </li>
249 </li>
250 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
250 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
251 <ul id="help_menu" class="dropdown-menu">
251 <ul id="help_menu" class="dropdown-menu">
252 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
252 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
253 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
253 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
254 <li class="divider"></li>
254 <li class="divider"></li>
255 {% set
255 {% set
256 sections = (
256 sections = (
257 (
257 (
258 ("http://ipython.org/documentation.html","IPython Help",True),
258 ("http://ipython.org/documentation.html","IPython Help",True),
259 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
259 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
260 ),(
260 ),(
261 ("http://docs.python.org","Python",True),
261 ("http://docs.python.org","Python",True),
262 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
262 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
263 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
263 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
264 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
264 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
265 ("http://matplotlib.org/contents.html","Matplotlib",True),
265 ("http://matplotlib.org/contents.html","Matplotlib",True),
266 ("http://docs.sympy.org/latest/index.html","SymPy",True),
266 ("http://docs.sympy.org/latest/index.html","SymPy",True),
267 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
267 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
268 )
268 )
269 )
269 )
270 %}
270 %}
271
271
272 {% for helplinks in sections %}
272 {% for helplinks in sections %}
273 {% for link in helplinks %}
273 {% for link in helplinks %}
274 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
274 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
275 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
275 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
276 {{link[1]}}
276 {{link[1]}}
277 </a></li>
277 </a></li>
278 {% endfor %}
278 {% endfor %}
279 {% if not loop.last %}
279 {% if not loop.last %}
280 <li class="divider"></li>
280 <li class="divider"></li>
281 {% endif %}
281 {% endif %}
282 {% endfor %}
282 {% endfor %}
283 <li class="divider"></li>
283 <li class="divider"></li>
284 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
284 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
285 </ul>
285 </ul>
286 </li>
286 </li>
287 </ul>
287 </ul>
288 </div>
288 </div>
289 </div>
289 </div>
290 </div>
290 </div>
291 </div>
291 </div>
292 <div id="maintoolbar" class="navbar">
292 <div id="maintoolbar" class="navbar">
293 <div class="toolbar-inner navbar-inner navbar-nobg">
293 <div class="toolbar-inner navbar-inner navbar-nobg">
294 <div id="maintoolbar-container" class="container"></div>
294 <div id="maintoolbar-container" class="container"></div>
295 </div>
295 </div>
296 </div>
296 </div>
297 </div>
297 </div>
298
298
299 <div id="ipython-main-app">
299 <div id="ipython-main-app">
300
300
301 <div id="notebook_panel">
301 <div id="notebook_panel">
302 <div id="notebook"></div>
302 <div id="notebook"></div>
303 <div id="pager_splitter"></div>
303 <div id="pager_splitter"></div>
304 <div id="pager">
304 <div id="pager">
305 <div id='pager_button_area'>
305 <div id='pager_button_area'>
306 </div>
306 </div>
307 <div id="pager-container" class="container"></div>
307 <div id="pager-container" class="container"></div>
308 </div>
308 </div>
309 </div>
309 </div>
310
310
311 </div>
311 </div>
312 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
312 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
313
313
314
314
315 {% endblock %}
315 {% endblock %}
316
316
317
317
318 {% block script %}
318 {% block script %}
319 {{super()}}
319 {{super()}}
320 <script type="text/javascript">
320 <script type="text/javascript">
321 sys_info = {{sys_info}};
321 sys_info = {{sys_info}};
322 </script>
322 </script>
323
323
324 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
324 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
325
325
326 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
327
327
328 {% endblock %}
328 {% endblock %}
@@ -1,328 +1,330 b''
1 """The IPython kernel implementation"""
1 """The IPython kernel implementation"""
2
2
3 import getpass
3 import getpass
4 import sys
4 import sys
5 import traceback
5 import traceback
6
6
7 from IPython.core import release
7 from IPython.core import release
8 from IPython.html.widgets import Widget
8 from IPython.html.widgets import Widget
9 from IPython.utils.py3compat import builtin_mod, PY3
9 from IPython.utils.py3compat import builtin_mod, PY3
10 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
10 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
11 from IPython.utils.traitlets import Instance, Type, Any
11 from IPython.utils.traitlets import Instance, Type, Any
12 from IPython.utils.decorators import undoc
12 from IPython.utils.decorators import undoc
13
13
14 from ..comm import CommManager
14 from ..comm import CommManager
15 from .kernelbase import Kernel as KernelBase
15 from .kernelbase import Kernel as KernelBase
16 from .serialize import serialize_object, unpack_apply_message
16 from .serialize import serialize_object, unpack_apply_message
17 from .zmqshell import ZMQInteractiveShell
17 from .zmqshell import ZMQInteractiveShell
18
18
19 class IPythonKernel(KernelBase):
19 class IPythonKernel(KernelBase):
20 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
20 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
21 shell_class = Type(ZMQInteractiveShell)
21 shell_class = Type(ZMQInteractiveShell)
22
22
23 user_module = Any()
23 user_module = Any()
24 def _user_module_changed(self, name, old, new):
24 def _user_module_changed(self, name, old, new):
25 if self.shell is not None:
25 if self.shell is not None:
26 self.shell.user_module = new
26 self.shell.user_module = new
27
27
28 user_ns = Instance(dict, args=None, allow_none=True)
28 user_ns = Instance(dict, args=None, allow_none=True)
29 def _user_ns_changed(self, name, old, new):
29 def _user_ns_changed(self, name, old, new):
30 if self.shell is not None:
30 if self.shell is not None:
31 self.shell.user_ns = new
31 self.shell.user_ns = new
32 self.shell.init_user_ns()
32 self.shell.init_user_ns()
33
33
34 # A reference to the Python builtin 'raw_input' function.
34 # A reference to the Python builtin 'raw_input' function.
35 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
35 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
36 _sys_raw_input = Any()
36 _sys_raw_input = Any()
37 _sys_eval_input = Any()
37 _sys_eval_input = Any()
38
38
39 def __init__(self, **kwargs):
39 def __init__(self, **kwargs):
40 super(IPythonKernel, self).__init__(**kwargs)
40 super(IPythonKernel, self).__init__(**kwargs)
41
41
42 # Initialize the InteractiveShell subclass
42 # Initialize the InteractiveShell subclass
43 self.shell = self.shell_class.instance(parent=self,
43 self.shell = self.shell_class.instance(parent=self,
44 profile_dir = self.profile_dir,
44 profile_dir = self.profile_dir,
45 user_module = self.user_module,
45 user_module = self.user_module,
46 user_ns = self.user_ns,
46 user_ns = self.user_ns,
47 kernel = self,
47 kernel = self,
48 )
48 )
49 self.shell.displayhook.session = self.session
49 self.shell.displayhook.session = self.session
50 self.shell.displayhook.pub_socket = self.iopub_socket
50 self.shell.displayhook.pub_socket = self.iopub_socket
51 self.shell.displayhook.topic = self._topic('execute_result')
51 self.shell.displayhook.topic = self._topic('execute_result')
52 self.shell.display_pub.session = self.session
52 self.shell.display_pub.session = self.session
53 self.shell.display_pub.pub_socket = self.iopub_socket
53 self.shell.display_pub.pub_socket = self.iopub_socket
54 self.shell.data_pub.session = self.session
54 self.shell.data_pub.session = self.session
55 self.shell.data_pub.pub_socket = self.iopub_socket
55 self.shell.data_pub.pub_socket = self.iopub_socket
56
56
57 # TMP - hack while developing
57 # TMP - hack while developing
58 self.shell._reply_content = None
58 self.shell._reply_content = None
59
59
60 self.comm_manager = CommManager(shell=self.shell, parent=self,
60 self.comm_manager = CommManager(shell=self.shell, parent=self,
61 kernel=self)
61 kernel=self)
62 self.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
62 self.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
63
63
64 self.shell.configurables.append(self.comm_manager)
64 self.shell.configurables.append(self.comm_manager)
65 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
65 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
66 for msg_type in comm_msg_types:
66 for msg_type in comm_msg_types:
67 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
67 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
68
68
69 # Kernel info fields
69 # Kernel info fields
70 implementation = 'ipython'
70 implementation = 'ipython'
71 implementation_version = release.version
71 implementation_version = release.version
72 language = 'python'
72 language = 'python'
73 language_version = sys.version.split()[0]
73 language_version = sys.version.split()[0]
74 language_info = {'mimetype': 'text/x-python',
74 language_info = {'mimetype': 'text/x-python',
75 'codemirror_mode': {'name': 'ipython',
75 'codemirror_mode': {'name': 'ipython',
76 'version': sys.version_info[0]},
76 'version': sys.version_info[0]},
77 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
77 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
78 'nbconvert_exporter': 'python',
79 'file_extension': '.py'
78 }
80 }
79 @property
81 @property
80 def banner(self):
82 def banner(self):
81 return self.shell.banner
83 return self.shell.banner
82
84
83 def start(self):
85 def start(self):
84 self.shell.exit_now = False
86 self.shell.exit_now = False
85 super(IPythonKernel, self).start()
87 super(IPythonKernel, self).start()
86
88
87 def set_parent(self, ident, parent):
89 def set_parent(self, ident, parent):
88 """Overridden from parent to tell the display hook and output streams
90 """Overridden from parent to tell the display hook and output streams
89 about the parent message.
91 about the parent message.
90 """
92 """
91 super(IPythonKernel, self).set_parent(ident, parent)
93 super(IPythonKernel, self).set_parent(ident, parent)
92 self.shell.set_parent(parent)
94 self.shell.set_parent(parent)
93
95
94 def _forward_input(self, allow_stdin=False):
96 def _forward_input(self, allow_stdin=False):
95 """Forward raw_input and getpass to the current frontend.
97 """Forward raw_input and getpass to the current frontend.
96
98
97 via input_request
99 via input_request
98 """
100 """
99 self._allow_stdin = allow_stdin
101 self._allow_stdin = allow_stdin
100
102
101 if PY3:
103 if PY3:
102 self._sys_raw_input = builtin_mod.input
104 self._sys_raw_input = builtin_mod.input
103 builtin_mod.input = self.raw_input
105 builtin_mod.input = self.raw_input
104 else:
106 else:
105 self._sys_raw_input = builtin_mod.raw_input
107 self._sys_raw_input = builtin_mod.raw_input
106 self._sys_eval_input = builtin_mod.input
108 self._sys_eval_input = builtin_mod.input
107 builtin_mod.raw_input = self.raw_input
109 builtin_mod.raw_input = self.raw_input
108 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
110 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
109 self._save_getpass = getpass.getpass
111 self._save_getpass = getpass.getpass
110 getpass.getpass = self.getpass
112 getpass.getpass = self.getpass
111
113
112 def _restore_input(self):
114 def _restore_input(self):
113 """Restore raw_input, getpass"""
115 """Restore raw_input, getpass"""
114 if PY3:
116 if PY3:
115 builtin_mod.input = self._sys_raw_input
117 builtin_mod.input = self._sys_raw_input
116 else:
118 else:
117 builtin_mod.raw_input = self._sys_raw_input
119 builtin_mod.raw_input = self._sys_raw_input
118 builtin_mod.input = self._sys_eval_input
120 builtin_mod.input = self._sys_eval_input
119
121
120 getpass.getpass = self._save_getpass
122 getpass.getpass = self._save_getpass
121
123
122 @property
124 @property
123 def execution_count(self):
125 def execution_count(self):
124 return self.shell.execution_count
126 return self.shell.execution_count
125
127
126 @execution_count.setter
128 @execution_count.setter
127 def execution_count(self, value):
129 def execution_count(self, value):
128 # Ignore the incrememnting done by KernelBase, in favour of our shell's
130 # Ignore the incrememnting done by KernelBase, in favour of our shell's
129 # execution counter.
131 # execution counter.
130 pass
132 pass
131
133
132 def do_execute(self, code, silent, store_history=True,
134 def do_execute(self, code, silent, store_history=True,
133 user_expressions=None, allow_stdin=False):
135 user_expressions=None, allow_stdin=False):
134 shell = self.shell # we'll need this a lot here
136 shell = self.shell # we'll need this a lot here
135
137
136 self._forward_input(allow_stdin)
138 self._forward_input(allow_stdin)
137
139
138 reply_content = {}
140 reply_content = {}
139 # FIXME: the shell calls the exception handler itself.
141 # FIXME: the shell calls the exception handler itself.
140 shell._reply_content = None
142 shell._reply_content = None
141 try:
143 try:
142 shell.run_cell(code, store_history=store_history, silent=silent)
144 shell.run_cell(code, store_history=store_history, silent=silent)
143 except:
145 except:
144 status = u'error'
146 status = u'error'
145 # FIXME: this code right now isn't being used yet by default,
147 # FIXME: this code right now isn't being used yet by default,
146 # because the run_cell() call above directly fires off exception
148 # because the run_cell() call above directly fires off exception
147 # reporting. This code, therefore, is only active in the scenario
149 # reporting. This code, therefore, is only active in the scenario
148 # where runlines itself has an unhandled exception. We need to
150 # where runlines itself has an unhandled exception. We need to
149 # uniformize this, for all exception construction to come from a
151 # uniformize this, for all exception construction to come from a
150 # single location in the codbase.
152 # single location in the codbase.
151 etype, evalue, tb = sys.exc_info()
153 etype, evalue, tb = sys.exc_info()
152 tb_list = traceback.format_exception(etype, evalue, tb)
154 tb_list = traceback.format_exception(etype, evalue, tb)
153 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
155 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
154 else:
156 else:
155 status = u'ok'
157 status = u'ok'
156 finally:
158 finally:
157 self._restore_input()
159 self._restore_input()
158
160
159 reply_content[u'status'] = status
161 reply_content[u'status'] = status
160
162
161 # Return the execution counter so clients can display prompts
163 # Return the execution counter so clients can display prompts
162 reply_content['execution_count'] = shell.execution_count - 1
164 reply_content['execution_count'] = shell.execution_count - 1
163
165
164 # FIXME - fish exception info out of shell, possibly left there by
166 # FIXME - fish exception info out of shell, possibly left there by
165 # runlines. We'll need to clean up this logic later.
167 # runlines. We'll need to clean up this logic later.
166 if shell._reply_content is not None:
168 if shell._reply_content is not None:
167 reply_content.update(shell._reply_content)
169 reply_content.update(shell._reply_content)
168 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
170 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
169 reply_content['engine_info'] = e_info
171 reply_content['engine_info'] = e_info
170 # reset after use
172 # reset after use
171 shell._reply_content = None
173 shell._reply_content = None
172
174
173 if 'traceback' in reply_content:
175 if 'traceback' in reply_content:
174 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
176 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
175
177
176
178
177 # At this point, we can tell whether the main code execution succeeded
179 # At this point, we can tell whether the main code execution succeeded
178 # or not. If it did, we proceed to evaluate user_expressions
180 # or not. If it did, we proceed to evaluate user_expressions
179 if reply_content['status'] == 'ok':
181 if reply_content['status'] == 'ok':
180 reply_content[u'user_expressions'] = \
182 reply_content[u'user_expressions'] = \
181 shell.user_expressions(user_expressions or {})
183 shell.user_expressions(user_expressions or {})
182 else:
184 else:
183 # If there was an error, don't even try to compute expressions
185 # If there was an error, don't even try to compute expressions
184 reply_content[u'user_expressions'] = {}
186 reply_content[u'user_expressions'] = {}
185
187
186 # Payloads should be retrieved regardless of outcome, so we can both
188 # Payloads should be retrieved regardless of outcome, so we can both
187 # recover partial output (that could have been generated early in a
189 # recover partial output (that could have been generated early in a
188 # block, before an error) and clear the payload system always.
190 # block, before an error) and clear the payload system always.
189 reply_content[u'payload'] = shell.payload_manager.read_payload()
191 reply_content[u'payload'] = shell.payload_manager.read_payload()
190 # Be agressive about clearing the payload because we don't want
192 # Be agressive about clearing the payload because we don't want
191 # it to sit in memory until the next execute_request comes in.
193 # it to sit in memory until the next execute_request comes in.
192 shell.payload_manager.clear_payload()
194 shell.payload_manager.clear_payload()
193
195
194 return reply_content
196 return reply_content
195
197
196 def do_complete(self, code, cursor_pos):
198 def do_complete(self, code, cursor_pos):
197 # FIXME: IPython completers currently assume single line,
199 # FIXME: IPython completers currently assume single line,
198 # but completion messages give multi-line context
200 # but completion messages give multi-line context
199 # For now, extract line from cell, based on cursor_pos:
201 # For now, extract line from cell, based on cursor_pos:
200 if cursor_pos is None:
202 if cursor_pos is None:
201 cursor_pos = len(code)
203 cursor_pos = len(code)
202 line, offset = line_at_cursor(code, cursor_pos)
204 line, offset = line_at_cursor(code, cursor_pos)
203 line_cursor = cursor_pos - offset
205 line_cursor = cursor_pos - offset
204
206
205 txt, matches = self.shell.complete('', line, line_cursor)
207 txt, matches = self.shell.complete('', line, line_cursor)
206 return {'matches' : matches,
208 return {'matches' : matches,
207 'cursor_end' : cursor_pos,
209 'cursor_end' : cursor_pos,
208 'cursor_start' : cursor_pos - len(txt),
210 'cursor_start' : cursor_pos - len(txt),
209 'metadata' : {},
211 'metadata' : {},
210 'status' : 'ok'}
212 'status' : 'ok'}
211
213
212 def do_inspect(self, code, cursor_pos, detail_level=0):
214 def do_inspect(self, code, cursor_pos, detail_level=0):
213 name = token_at_cursor(code, cursor_pos)
215 name = token_at_cursor(code, cursor_pos)
214 info = self.shell.object_inspect(name)
216 info = self.shell.object_inspect(name)
215
217
216 reply_content = {'status' : 'ok'}
218 reply_content = {'status' : 'ok'}
217 reply_content['data'] = data = {}
219 reply_content['data'] = data = {}
218 reply_content['metadata'] = {}
220 reply_content['metadata'] = {}
219 reply_content['found'] = info['found']
221 reply_content['found'] = info['found']
220 if info['found']:
222 if info['found']:
221 info_text = self.shell.object_inspect_text(
223 info_text = self.shell.object_inspect_text(
222 name,
224 name,
223 detail_level=detail_level,
225 detail_level=detail_level,
224 )
226 )
225 data['text/plain'] = info_text
227 data['text/plain'] = info_text
226
228
227 return reply_content
229 return reply_content
228
230
229 def do_history(self, hist_access_type, output, raw, session=None, start=None,
231 def do_history(self, hist_access_type, output, raw, session=None, start=None,
230 stop=None, n=None, pattern=None, unique=False):
232 stop=None, n=None, pattern=None, unique=False):
231 if hist_access_type == 'tail':
233 if hist_access_type == 'tail':
232 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
234 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
233 include_latest=True)
235 include_latest=True)
234
236
235 elif hist_access_type == 'range':
237 elif hist_access_type == 'range':
236 hist = self.shell.history_manager.get_range(session, start, stop,
238 hist = self.shell.history_manager.get_range(session, start, stop,
237 raw=raw, output=output)
239 raw=raw, output=output)
238
240
239 elif hist_access_type == 'search':
241 elif hist_access_type == 'search':
240 hist = self.shell.history_manager.search(
242 hist = self.shell.history_manager.search(
241 pattern, raw=raw, output=output, n=n, unique=unique)
243 pattern, raw=raw, output=output, n=n, unique=unique)
242 else:
244 else:
243 hist = []
245 hist = []
244
246
245 return {'history' : list(hist)}
247 return {'history' : list(hist)}
246
248
247 def do_shutdown(self, restart):
249 def do_shutdown(self, restart):
248 self.shell.exit_now = True
250 self.shell.exit_now = True
249 return dict(status='ok', restart=restart)
251 return dict(status='ok', restart=restart)
250
252
251 def do_is_complete(self, code):
253 def do_is_complete(self, code):
252 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
254 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
253 r = {'status': status}
255 r = {'status': status}
254 if status == 'incomplete':
256 if status == 'incomplete':
255 r['indent'] = ' ' * indent_spaces
257 r['indent'] = ' ' * indent_spaces
256 return r
258 return r
257
259
258 def do_apply(self, content, bufs, msg_id, reply_metadata):
260 def do_apply(self, content, bufs, msg_id, reply_metadata):
259 shell = self.shell
261 shell = self.shell
260 try:
262 try:
261 working = shell.user_ns
263 working = shell.user_ns
262
264
263 prefix = "_"+str(msg_id).replace("-","")+"_"
265 prefix = "_"+str(msg_id).replace("-","")+"_"
264
266
265 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
267 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
266
268
267 fname = getattr(f, '__name__', 'f')
269 fname = getattr(f, '__name__', 'f')
268
270
269 fname = prefix+"f"
271 fname = prefix+"f"
270 argname = prefix+"args"
272 argname = prefix+"args"
271 kwargname = prefix+"kwargs"
273 kwargname = prefix+"kwargs"
272 resultname = prefix+"result"
274 resultname = prefix+"result"
273
275
274 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
276 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
275 # print ns
277 # print ns
276 working.update(ns)
278 working.update(ns)
277 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
279 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
278 try:
280 try:
279 exec(code, shell.user_global_ns, shell.user_ns)
281 exec(code, shell.user_global_ns, shell.user_ns)
280 result = working.get(resultname)
282 result = working.get(resultname)
281 finally:
283 finally:
282 for key in ns:
284 for key in ns:
283 working.pop(key)
285 working.pop(key)
284
286
285 result_buf = serialize_object(result,
287 result_buf = serialize_object(result,
286 buffer_threshold=self.session.buffer_threshold,
288 buffer_threshold=self.session.buffer_threshold,
287 item_threshold=self.session.item_threshold,
289 item_threshold=self.session.item_threshold,
288 )
290 )
289
291
290 except:
292 except:
291 # invoke IPython traceback formatting
293 # invoke IPython traceback formatting
292 shell.showtraceback()
294 shell.showtraceback()
293 # FIXME - fish exception info out of shell, possibly left there by
295 # FIXME - fish exception info out of shell, possibly left there by
294 # run_code. We'll need to clean up this logic later.
296 # run_code. We'll need to clean up this logic later.
295 reply_content = {}
297 reply_content = {}
296 if shell._reply_content is not None:
298 if shell._reply_content is not None:
297 reply_content.update(shell._reply_content)
299 reply_content.update(shell._reply_content)
298 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
300 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
299 reply_content['engine_info'] = e_info
301 reply_content['engine_info'] = e_info
300 # reset after use
302 # reset after use
301 shell._reply_content = None
303 shell._reply_content = None
302
304
303 self.send_response(self.iopub_socket, u'error', reply_content,
305 self.send_response(self.iopub_socket, u'error', reply_content,
304 ident=self._topic('error'))
306 ident=self._topic('error'))
305 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
307 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
306 result_buf = []
308 result_buf = []
307
309
308 if reply_content['ename'] == 'UnmetDependency':
310 if reply_content['ename'] == 'UnmetDependency':
309 reply_metadata['dependencies_met'] = False
311 reply_metadata['dependencies_met'] = False
310 else:
312 else:
311 reply_content = {'status' : 'ok'}
313 reply_content = {'status' : 'ok'}
312
314
313 return reply_content, result_buf
315 return reply_content, result_buf
314
316
315 def do_clear(self):
317 def do_clear(self):
316 self.shell.reset(False)
318 self.shell.reset(False)
317 return dict(status='ok')
319 return dict(status='ok')
318
320
319
321
320 # This exists only for backwards compatibility - use IPythonKernel instead
322 # This exists only for backwards compatibility - use IPythonKernel instead
321
323
322 @undoc
324 @undoc
323 class Kernel(IPythonKernel):
325 class Kernel(IPythonKernel):
324 def __init__(self, *args, **kwargs):
326 def __init__(self, *args, **kwargs):
325 import warnings
327 import warnings
326 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
328 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
327 DeprecationWarning)
329 DeprecationWarning)
328 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
330 super(Kernel, self).__init__(*args, **kwargs)
@@ -1,174 +1,177 b''
1 """Module containing single call export functions."""
1 """Module containing single call export functions."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from functools import wraps
6 from functools import wraps
7
7
8 from IPython.nbformat import NotebookNode
8 from IPython.nbformat import NotebookNode
9 from IPython.utils.decorators import undoc
9 from IPython.utils.decorators import undoc
10 from IPython.utils.py3compat import string_types
10 from IPython.utils.py3compat import string_types
11
11
12 from .exporter import Exporter
12 from .exporter import Exporter
13 from .templateexporter import TemplateExporter
13 from .templateexporter import TemplateExporter
14 from .html import HTMLExporter
14 from .html import HTMLExporter
15 from .slides import SlidesExporter
15 from .slides import SlidesExporter
16 from .latex import LatexExporter
16 from .latex import LatexExporter
17 from .pdf import PDFExporter
17 from .pdf import PDFExporter
18 from .markdown import MarkdownExporter
18 from .markdown import MarkdownExporter
19 from .python import PythonExporter
19 from .python import PythonExporter
20 from .rst import RSTExporter
20 from .rst import RSTExporter
21 from .notebook import NotebookExporter
21 from .notebook import NotebookExporter
22 from .script import ScriptExporter
22
23
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24 # Classes
25 # Classes
25 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
26
27
27 @undoc
28 @undoc
28 def DocDecorator(f):
29 def DocDecorator(f):
29
30
30 #Set docstring of function
31 #Set docstring of function
31 f.__doc__ = f.__doc__ + """
32 f.__doc__ = f.__doc__ + """
32 nb : :class:`~IPython.nbformat.NotebookNode`
33 nb : :class:`~IPython.nbformat.NotebookNode`
33 The notebook to export.
34 The notebook to export.
34 config : config (optional, keyword arg)
35 config : config (optional, keyword arg)
35 User configuration instance.
36 User configuration instance.
36 resources : dict (optional, keyword arg)
37 resources : dict (optional, keyword arg)
37 Resources used in the conversion process.
38 Resources used in the conversion process.
38
39
39 Returns
40 Returns
40 -------
41 -------
41 tuple- output, resources, exporter_instance
42 tuple- output, resources, exporter_instance
42 output : str
43 output : str
43 Jinja 2 output. This is the resulting converted notebook.
44 Jinja 2 output. This is the resulting converted notebook.
44 resources : dictionary
45 resources : dictionary
45 Dictionary of resources used prior to and during the conversion
46 Dictionary of resources used prior to and during the conversion
46 process.
47 process.
47 exporter_instance : Exporter
48 exporter_instance : Exporter
48 Instance of the Exporter class used to export the document. Useful
49 Instance of the Exporter class used to export the document. Useful
49 to caller because it provides a 'file_extension' property which
50 to caller because it provides a 'file_extension' property which
50 specifies what extension the output should be saved as.
51 specifies what extension the output should be saved as.
51
52
52 Notes
53 Notes
53 -----
54 -----
54 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT
55 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT
55 """
56 """
56
57
57 @wraps(f)
58 @wraps(f)
58 def decorator(*args, **kwargs):
59 def decorator(*args, **kwargs):
59 return f(*args, **kwargs)
60 return f(*args, **kwargs)
60
61
61 return decorator
62 return decorator
62
63
63
64
64 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
65 # Functions
66 # Functions
66 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
67
68
68 __all__ = [
69 __all__ = [
69 'export',
70 'export',
70 'export_html',
71 'export_html',
71 'export_custom',
72 'export_custom',
72 'export_slides',
73 'export_slides',
73 'export_latex',
74 'export_latex',
74 'export_pdf',
75 'export_pdf',
75 'export_markdown',
76 'export_markdown',
76 'export_python',
77 'export_python',
78 'export_script',
77 'export_rst',
79 'export_rst',
78 'export_by_name',
80 'export_by_name',
79 'get_export_names',
81 'get_export_names',
80 'ExporterNameError'
82 'ExporterNameError'
81 ]
83 ]
82
84
83
85
84 class ExporterNameError(NameError):
86 class ExporterNameError(NameError):
85 pass
87 pass
86
88
87 @DocDecorator
89 @DocDecorator
88 def export(exporter, nb, **kw):
90 def export(exporter, nb, **kw):
89 """
91 """
90 Export a notebook object using specific exporter class.
92 Export a notebook object using specific exporter class.
91
93
92 Parameters
94 Parameters
93 ----------
95 ----------
94 exporter : class:`~IPython.nbconvert.exporters.exporter.Exporter` class or instance
96 exporter : class:`~IPython.nbconvert.exporters.exporter.Exporter` class or instance
95 Class type or instance of the exporter that should be used. If the
97 Class type or instance of the exporter that should be used. If the
96 method initializes it's own instance of the class, it is ASSUMED that
98 method initializes it's own instance of the class, it is ASSUMED that
97 the class type provided exposes a constructor (``__init__``) with the same
99 the class type provided exposes a constructor (``__init__``) with the same
98 signature as the base Exporter class.
100 signature as the base Exporter class.
99 """
101 """
100
102
101 #Check arguments
103 #Check arguments
102 if exporter is None:
104 if exporter is None:
103 raise TypeError("Exporter is None")
105 raise TypeError("Exporter is None")
104 elif not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
106 elif not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
105 raise TypeError("exporter does not inherit from Exporter (base)")
107 raise TypeError("exporter does not inherit from Exporter (base)")
106 if nb is None:
108 if nb is None:
107 raise TypeError("nb is None")
109 raise TypeError("nb is None")
108
110
109 #Create the exporter
111 #Create the exporter
110 resources = kw.pop('resources', None)
112 resources = kw.pop('resources', None)
111 if isinstance(exporter, Exporter):
113 if isinstance(exporter, Exporter):
112 exporter_instance = exporter
114 exporter_instance = exporter
113 else:
115 else:
114 exporter_instance = exporter(**kw)
116 exporter_instance = exporter(**kw)
115
117
116 #Try to convert the notebook using the appropriate conversion function.
118 #Try to convert the notebook using the appropriate conversion function.
117 if isinstance(nb, NotebookNode):
119 if isinstance(nb, NotebookNode):
118 output, resources = exporter_instance.from_notebook_node(nb, resources)
120 output, resources = exporter_instance.from_notebook_node(nb, resources)
119 elif isinstance(nb, string_types):
121 elif isinstance(nb, string_types):
120 output, resources = exporter_instance.from_filename(nb, resources)
122 output, resources = exporter_instance.from_filename(nb, resources)
121 else:
123 else:
122 output, resources = exporter_instance.from_file(nb, resources)
124 output, resources = exporter_instance.from_file(nb, resources)
123 return output, resources
125 return output, resources
124
126
125 exporter_map = dict(
127 exporter_map = dict(
126 custom=TemplateExporter,
128 custom=TemplateExporter,
127 html=HTMLExporter,
129 html=HTMLExporter,
128 slides=SlidesExporter,
130 slides=SlidesExporter,
129 latex=LatexExporter,
131 latex=LatexExporter,
130 pdf=PDFExporter,
132 pdf=PDFExporter,
131 markdown=MarkdownExporter,
133 markdown=MarkdownExporter,
132 python=PythonExporter,
134 python=PythonExporter,
133 rst=RSTExporter,
135 rst=RSTExporter,
134 notebook=NotebookExporter,
136 notebook=NotebookExporter,
137 script=ScriptExporter,
135 )
138 )
136
139
137 def _make_exporter(name, E):
140 def _make_exporter(name, E):
138 """make an export_foo function from a short key and Exporter class E"""
141 """make an export_foo function from a short key and Exporter class E"""
139 def _export(nb, **kw):
142 def _export(nb, **kw):
140 return export(E, nb, **kw)
143 return export(E, nb, **kw)
141 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
144 _export.__doc__ = """Export a notebook object to {0} format""".format(name)
142 return _export
145 return _export
143
146
144 g = globals()
147 g = globals()
145
148
146 for name, E in exporter_map.items():
149 for name, E in exporter_map.items():
147 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
150 g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
148
151
149 @DocDecorator
152 @DocDecorator
150 def export_by_name(format_name, nb, **kw):
153 def export_by_name(format_name, nb, **kw):
151 """
154 """
152 Export a notebook object to a template type by its name. Reflection
155 Export a notebook object to a template type by its name. Reflection
153 (Inspect) is used to find the template's corresponding explicit export
156 (Inspect) is used to find the template's corresponding explicit export
154 method defined in this module. That method is then called directly.
157 method defined in this module. That method is then called directly.
155
158
156 Parameters
159 Parameters
157 ----------
160 ----------
158 format_name : str
161 format_name : str
159 Name of the template style to export to.
162 Name of the template style to export to.
160 """
163 """
161
164
162 function_name = "export_" + format_name.lower()
165 function_name = "export_" + format_name.lower()
163
166
164 if function_name in globals():
167 if function_name in globals():
165 return globals()[function_name](nb, **kw)
168 return globals()[function_name](nb, **kw)
166 else:
169 else:
167 raise ExporterNameError("template for `%s` not found" % function_name)
170 raise ExporterNameError("template for `%s` not found" % function_name)
168
171
169
172
170 def get_export_names():
173 def get_export_names():
171 """Return a list of the currently supported export targets
174 """Return a list of the currently supported export targets
172
175
173 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
176 WARNING: API WILL CHANGE IN FUTURE RELEASES OF NBCONVERT"""
174 return sorted(exporter_map.keys())
177 return sorted(exporter_map.keys())
@@ -1,259 +1,259 b''
1 """This module defines a base Exporter class. For Jinja template-based export,
1 """This module defines a base Exporter class. For Jinja template-based export,
2 see templateexporter.py.
2 see templateexporter.py.
3 """
3 """
4
4
5
5
6 from __future__ import print_function, absolute_import
6 from __future__ import print_function, absolute_import
7
7
8 import io
8 import io
9 import os
9 import os
10 import copy
10 import copy
11 import collections
11 import collections
12 import datetime
12 import datetime
13
13
14 from IPython.config.configurable import LoggingConfigurable
14 from IPython.config.configurable import LoggingConfigurable
15 from IPython.config import Config
15 from IPython.config import Config
16 from IPython import nbformat
16 from IPython import nbformat
17 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
17 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
18 from IPython.utils.importstring import import_item
18 from IPython.utils.importstring import import_item
19 from IPython.utils import text, py3compat
19 from IPython.utils import text, py3compat
20
20
21
21
22 class ResourcesDict(collections.defaultdict):
22 class ResourcesDict(collections.defaultdict):
23 def __missing__(self, key):
23 def __missing__(self, key):
24 return ''
24 return ''
25
25
26
26
27 class Exporter(LoggingConfigurable):
27 class Exporter(LoggingConfigurable):
28 """
28 """
29 Class containing methods that sequentially run a list of preprocessors on a
29 Class containing methods that sequentially run a list of preprocessors on a
30 NotebookNode object and then return the modified NotebookNode object and
30 NotebookNode object and then return the modified NotebookNode object and
31 accompanying resources dict.
31 accompanying resources dict.
32 """
32 """
33
33
34 file_extension = Unicode(
34 file_extension = Unicode(
35 'txt', config=True,
35 '.txt', config=True,
36 help="Extension of the file that should be written to disk"
36 help="Extension of the file that should be written to disk"
37 )
37 )
38
38
39 # MIME type of the result file, for HTTP response headers.
39 # MIME type of the result file, for HTTP response headers.
40 # This is *not* a traitlet, because we want to be able to access it from
40 # This is *not* a traitlet, because we want to be able to access it from
41 # the class, not just on instances.
41 # the class, not just on instances.
42 output_mimetype = ''
42 output_mimetype = ''
43
43
44 #Configurability, allows the user to easily add filters and preprocessors.
44 #Configurability, allows the user to easily add filters and preprocessors.
45 preprocessors = List(config=True,
45 preprocessors = List(config=True,
46 help="""List of preprocessors, by name or namespace, to enable.""")
46 help="""List of preprocessors, by name or namespace, to enable.""")
47
47
48 _preprocessors = List()
48 _preprocessors = List()
49
49
50 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
50 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
51 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
51 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
52 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
52 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
53 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
53 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
54 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
54 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
55 'IPython.nbconvert.preprocessors.LatexPreprocessor',
55 'IPython.nbconvert.preprocessors.LatexPreprocessor',
56 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
56 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
57 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
57 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
58 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
58 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
59 config=True,
59 config=True,
60 help="""List of preprocessors available by default, by name, namespace,
60 help="""List of preprocessors available by default, by name, namespace,
61 instance, or type.""")
61 instance, or type.""")
62
62
63
63
64 def __init__(self, config=None, **kw):
64 def __init__(self, config=None, **kw):
65 """
65 """
66 Public constructor
66 Public constructor
67
67
68 Parameters
68 Parameters
69 ----------
69 ----------
70 config : config
70 config : config
71 User configuration instance.
71 User configuration instance.
72 """
72 """
73 with_default_config = self.default_config
73 with_default_config = self.default_config
74 if config:
74 if config:
75 with_default_config.merge(config)
75 with_default_config.merge(config)
76
76
77 super(Exporter, self).__init__(config=with_default_config, **kw)
77 super(Exporter, self).__init__(config=with_default_config, **kw)
78
78
79 self._init_preprocessors()
79 self._init_preprocessors()
80
80
81
81
82 @property
82 @property
83 def default_config(self):
83 def default_config(self):
84 return Config()
84 return Config()
85
85
86 def from_notebook_node(self, nb, resources=None, **kw):
86 def from_notebook_node(self, nb, resources=None, **kw):
87 """
87 """
88 Convert a notebook from a notebook node instance.
88 Convert a notebook from a notebook node instance.
89
89
90 Parameters
90 Parameters
91 ----------
91 ----------
92 nb : :class:`~IPython.nbformat.NotebookNode`
92 nb : :class:`~IPython.nbformat.NotebookNode`
93 Notebook node (dict-like with attr-access)
93 Notebook node (dict-like with attr-access)
94 resources : dict
94 resources : dict
95 Additional resources that can be accessed read/write by
95 Additional resources that can be accessed read/write by
96 preprocessors and filters.
96 preprocessors and filters.
97 **kw
97 **kw
98 Ignored (?)
98 Ignored (?)
99 """
99 """
100 nb_copy = copy.deepcopy(nb)
100 nb_copy = copy.deepcopy(nb)
101 resources = self._init_resources(resources)
101 resources = self._init_resources(resources)
102
102
103 if 'language' in nb['metadata']:
103 if 'language' in nb['metadata']:
104 resources['language'] = nb['metadata']['language'].lower()
104 resources['language'] = nb['metadata']['language'].lower()
105
105
106 # Preprocess
106 # Preprocess
107 nb_copy, resources = self._preprocess(nb_copy, resources)
107 nb_copy, resources = self._preprocess(nb_copy, resources)
108
108
109 return nb_copy, resources
109 return nb_copy, resources
110
110
111
111
112 def from_filename(self, filename, resources=None, **kw):
112 def from_filename(self, filename, resources=None, **kw):
113 """
113 """
114 Convert a notebook from a notebook file.
114 Convert a notebook from a notebook file.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 filename : str
118 filename : str
119 Full filename of the notebook file to open and convert.
119 Full filename of the notebook file to open and convert.
120 """
120 """
121
121
122 # Pull the metadata from the filesystem.
122 # Pull the metadata from the filesystem.
123 if resources is None:
123 if resources is None:
124 resources = ResourcesDict()
124 resources = ResourcesDict()
125 if not 'metadata' in resources or resources['metadata'] == '':
125 if not 'metadata' in resources or resources['metadata'] == '':
126 resources['metadata'] = ResourcesDict()
126 resources['metadata'] = ResourcesDict()
127 basename = os.path.basename(filename)
127 basename = os.path.basename(filename)
128 notebook_name = basename[:basename.rfind('.')]
128 notebook_name = basename[:basename.rfind('.')]
129 resources['metadata']['name'] = notebook_name
129 resources['metadata']['name'] = notebook_name
130
130
131 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
131 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
132 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
132 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
133
133
134 with io.open(filename, encoding='utf-8') as f:
134 with io.open(filename, encoding='utf-8') as f:
135 return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw)
135 return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw)
136
136
137
137
138 def from_file(self, file_stream, resources=None, **kw):
138 def from_file(self, file_stream, resources=None, **kw):
139 """
139 """
140 Convert a notebook from a notebook file.
140 Convert a notebook from a notebook file.
141
141
142 Parameters
142 Parameters
143 ----------
143 ----------
144 file_stream : file-like object
144 file_stream : file-like object
145 Notebook file-like object to convert.
145 Notebook file-like object to convert.
146 """
146 """
147 return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
147 return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
148
148
149
149
150 def register_preprocessor(self, preprocessor, enabled=False):
150 def register_preprocessor(self, preprocessor, enabled=False):
151 """
151 """
152 Register a preprocessor.
152 Register a preprocessor.
153 Preprocessors are classes that act upon the notebook before it is
153 Preprocessors are classes that act upon the notebook before it is
154 passed into the Jinja templating engine. preprocessors are also
154 passed into the Jinja templating engine. preprocessors are also
155 capable of passing additional information to the Jinja
155 capable of passing additional information to the Jinja
156 templating engine.
156 templating engine.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 preprocessor : preprocessor
160 preprocessor : preprocessor
161 """
161 """
162 if preprocessor is None:
162 if preprocessor is None:
163 raise TypeError('preprocessor')
163 raise TypeError('preprocessor')
164 isclass = isinstance(preprocessor, type)
164 isclass = isinstance(preprocessor, type)
165 constructed = not isclass
165 constructed = not isclass
166
166
167 # Handle preprocessor's registration based on it's type
167 # Handle preprocessor's registration based on it's type
168 if constructed and isinstance(preprocessor, py3compat.string_types):
168 if constructed and isinstance(preprocessor, py3compat.string_types):
169 # Preprocessor is a string, import the namespace and recursively call
169 # Preprocessor is a string, import the namespace and recursively call
170 # this register_preprocessor method
170 # this register_preprocessor method
171 preprocessor_cls = import_item(preprocessor)
171 preprocessor_cls = import_item(preprocessor)
172 return self.register_preprocessor(preprocessor_cls, enabled)
172 return self.register_preprocessor(preprocessor_cls, enabled)
173
173
174 if constructed and hasattr(preprocessor, '__call__'):
174 if constructed and hasattr(preprocessor, '__call__'):
175 # Preprocessor is a function, no need to construct it.
175 # Preprocessor is a function, no need to construct it.
176 # Register and return the preprocessor.
176 # Register and return the preprocessor.
177 if enabled:
177 if enabled:
178 preprocessor.enabled = True
178 preprocessor.enabled = True
179 self._preprocessors.append(preprocessor)
179 self._preprocessors.append(preprocessor)
180 return preprocessor
180 return preprocessor
181
181
182 elif isclass and isinstance(preprocessor, MetaHasTraits):
182 elif isclass and isinstance(preprocessor, MetaHasTraits):
183 # Preprocessor is configurable. Make sure to pass in new default for
183 # Preprocessor is configurable. Make sure to pass in new default for
184 # the enabled flag if one was specified.
184 # the enabled flag if one was specified.
185 self.register_preprocessor(preprocessor(parent=self), enabled)
185 self.register_preprocessor(preprocessor(parent=self), enabled)
186
186
187 elif isclass:
187 elif isclass:
188 # Preprocessor is not configurable, construct it
188 # Preprocessor is not configurable, construct it
189 self.register_preprocessor(preprocessor(), enabled)
189 self.register_preprocessor(preprocessor(), enabled)
190
190
191 else:
191 else:
192 # Preprocessor is an instance of something without a __call__
192 # Preprocessor is an instance of something without a __call__
193 # attribute.
193 # attribute.
194 raise TypeError('preprocessor')
194 raise TypeError('preprocessor')
195
195
196
196
197 def _init_preprocessors(self):
197 def _init_preprocessors(self):
198 """
198 """
199 Register all of the preprocessors needed for this exporter, disabled
199 Register all of the preprocessors needed for this exporter, disabled
200 unless specified explicitly.
200 unless specified explicitly.
201 """
201 """
202 self._preprocessors = []
202 self._preprocessors = []
203
203
204 # Load default preprocessors (not necessarly enabled by default).
204 # Load default preprocessors (not necessarly enabled by default).
205 for preprocessor in self.default_preprocessors:
205 for preprocessor in self.default_preprocessors:
206 self.register_preprocessor(preprocessor)
206 self.register_preprocessor(preprocessor)
207
207
208 # Load user-specified preprocessors. Enable by default.
208 # Load user-specified preprocessors. Enable by default.
209 for preprocessor in self.preprocessors:
209 for preprocessor in self.preprocessors:
210 self.register_preprocessor(preprocessor, enabled=True)
210 self.register_preprocessor(preprocessor, enabled=True)
211
211
212
212
213 def _init_resources(self, resources):
213 def _init_resources(self, resources):
214
214
215 #Make sure the resources dict is of ResourcesDict type.
215 #Make sure the resources dict is of ResourcesDict type.
216 if resources is None:
216 if resources is None:
217 resources = ResourcesDict()
217 resources = ResourcesDict()
218 if not isinstance(resources, ResourcesDict):
218 if not isinstance(resources, ResourcesDict):
219 new_resources = ResourcesDict()
219 new_resources = ResourcesDict()
220 new_resources.update(resources)
220 new_resources.update(resources)
221 resources = new_resources
221 resources = new_resources
222
222
223 #Make sure the metadata extension exists in resources
223 #Make sure the metadata extension exists in resources
224 if 'metadata' in resources:
224 if 'metadata' in resources:
225 if not isinstance(resources['metadata'], ResourcesDict):
225 if not isinstance(resources['metadata'], ResourcesDict):
226 resources['metadata'] = ResourcesDict(resources['metadata'])
226 resources['metadata'] = ResourcesDict(resources['metadata'])
227 else:
227 else:
228 resources['metadata'] = ResourcesDict()
228 resources['metadata'] = ResourcesDict()
229 if not resources['metadata']['name']:
229 if not resources['metadata']['name']:
230 resources['metadata']['name'] = 'Notebook'
230 resources['metadata']['name'] = 'Notebook'
231
231
232 #Set the output extension
232 #Set the output extension
233 resources['output_extension'] = self.file_extension
233 resources['output_extension'] = self.file_extension
234 return resources
234 return resources
235
235
236
236
237 def _preprocess(self, nb, resources):
237 def _preprocess(self, nb, resources):
238 """
238 """
239 Preprocess the notebook before passing it into the Jinja engine.
239 Preprocess the notebook before passing it into the Jinja engine.
240 To preprocess the notebook is to apply all of the
240 To preprocess the notebook is to apply all of the
241
241
242 Parameters
242 Parameters
243 ----------
243 ----------
244 nb : notebook node
244 nb : notebook node
245 notebook that is being exported.
245 notebook that is being exported.
246 resources : a dict of additional resources that
246 resources : a dict of additional resources that
247 can be accessed read/write by preprocessors
247 can be accessed read/write by preprocessors
248 """
248 """
249
249
250 # Do a copy.deepcopy first,
250 # Do a copy.deepcopy first,
251 # we are never safe enough with what the preprocessors could do.
251 # we are never safe enough with what the preprocessors could do.
252 nbc = copy.deepcopy(nb)
252 nbc = copy.deepcopy(nb)
253 resc = copy.deepcopy(resources)
253 resc = copy.deepcopy(resources)
254
254
255 #Run each preprocessor on the notebook. Carry the output along
255 #Run each preprocessor on the notebook. Carry the output along
256 #to each preprocessor
256 #to each preprocessor
257 for preprocessor in self._preprocessors:
257 for preprocessor in self._preprocessors:
258 nbc, resc = preprocessor(nbc, resc)
258 nbc, resc = preprocessor(nbc, resc)
259 return nbc, resc
259 return nbc, resc
@@ -1,66 +1,66 b''
1 """HTML Exporter class"""
1 """HTML Exporter class"""
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
16
17 from IPython.nbconvert.filters.highlight import Highlight2HTML
17 from IPython.nbconvert.filters.highlight import Highlight2HTML
18 from IPython.config import Config
18 from IPython.config import Config
19
19
20 from .templateexporter import TemplateExporter
20 from .templateexporter import TemplateExporter
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 class HTMLExporter(TemplateExporter):
26 class HTMLExporter(TemplateExporter):
27 """
27 """
28 Exports a basic HTML document. This exporter assists with the export of
28 Exports a basic HTML document. This exporter assists with the export of
29 HTML. Inherit from it if you are writing your own HTML template and need
29 HTML. Inherit from it if you are writing your own HTML template and need
30 custom preprocessors/filters. If you don't need custom preprocessors/
30 custom preprocessors/filters. If you don't need custom preprocessors/
31 filters, just change the 'template_file' config option.
31 filters, just change the 'template_file' config option.
32 """
32 """
33
33
34 def _file_extension_default(self):
34 def _file_extension_default(self):
35 return 'html'
35 return '.html'
36
36
37 def _default_template_path_default(self):
37 def _default_template_path_default(self):
38 return os.path.join("..", "templates", "html")
38 return os.path.join("..", "templates", "html")
39
39
40 def _template_file_default(self):
40 def _template_file_default(self):
41 return 'full'
41 return 'full'
42
42
43 output_mimetype = 'text/html'
43 output_mimetype = 'text/html'
44
44
45 @property
45 @property
46 def default_config(self):
46 def default_config(self):
47 c = Config({
47 c = Config({
48 'NbConvertBase': {
48 'NbConvertBase': {
49 'display_data_priority' : ['text/javascript', 'text/html', 'application/pdf', 'image/svg+xml', 'text/latex', 'image/png', 'image/jpeg', 'text/plain']
49 'display_data_priority' : ['text/javascript', 'text/html', 'application/pdf', 'image/svg+xml', 'text/latex', 'image/png', 'image/jpeg', 'text/plain']
50 },
50 },
51 'CSSHTMLHeaderPreprocessor':{
51 'CSSHTMLHeaderPreprocessor':{
52 'enabled':True
52 'enabled':True
53 },
53 },
54 'HighlightMagicsPreprocessor': {
54 'HighlightMagicsPreprocessor': {
55 'enabled':True
55 'enabled':True
56 }
56 }
57 })
57 })
58 c.merge(super(HTMLExporter,self).default_config)
58 c.merge(super(HTMLExporter,self).default_config)
59 return c
59 return c
60
60
61 def from_notebook_node(self, nb, resources=None, **kw):
61 def from_notebook_node(self, nb, resources=None, **kw):
62 langinfo = nb.metadata.get('language_info', {})
62 langinfo = nb.metadata.get('language_info', {})
63 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
63 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
64 self.register_filter('highlight_code',
64 self.register_filter('highlight_code',
65 Highlight2HTML(pygments_lexer=lexer, parent=self))
65 Highlight2HTML(pygments_lexer=lexer, parent=self))
66 return super(HTMLExporter, self).from_notebook_node(nb, resources, **kw)
66 return super(HTMLExporter, self).from_notebook_node(nb, resources, **kw)
@@ -1,96 +1,96 b''
1 """LaTeX Exporter class"""
1 """LaTeX Exporter class"""
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 # Stdlib imports
15 # Stdlib imports
16 import os
16 import os
17
17
18 # IPython imports
18 # IPython imports
19 from IPython.utils.traitlets import Unicode
19 from IPython.utils.traitlets import Unicode
20 from IPython.config import Config
20 from IPython.config import Config
21
21
22 from IPython.nbconvert.filters.highlight import Highlight2Latex
22 from IPython.nbconvert.filters.highlight import Highlight2Latex
23 from .templateexporter import TemplateExporter
23 from .templateexporter import TemplateExporter
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes and functions
26 # Classes and functions
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class LatexExporter(TemplateExporter):
29 class LatexExporter(TemplateExporter):
30 """
30 """
31 Exports to a Latex template. Inherit from this class if your template is
31 Exports to a Latex template. Inherit from this class if your template is
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
33 you are writing your own HTML template and need custom tranformers/filters.
33 you are writing your own HTML template and need custom tranformers/filters.
34 If you don't need custom tranformers/filters, just change the
34 If you don't need custom tranformers/filters, just change the
35 'template_file' config option. Place your template in the special "/latex"
35 'template_file' config option. Place your template in the special "/latex"
36 subfolder of the "../templates" folder.
36 subfolder of the "../templates" folder.
37 """
37 """
38
38
39 def _file_extension_default(self):
39 def _file_extension_default(self):
40 return 'tex'
40 return '.tex'
41
41
42 def _template_file_default(self):
42 def _template_file_default(self):
43 return 'article'
43 return 'article'
44
44
45 #Latex constants
45 #Latex constants
46 def _default_template_path_default(self):
46 def _default_template_path_default(self):
47 return os.path.join("..", "templates", "latex")
47 return os.path.join("..", "templates", "latex")
48
48
49 def _template_skeleton_path_default(self):
49 def _template_skeleton_path_default(self):
50 return os.path.join("..", "templates", "latex", "skeleton")
50 return os.path.join("..", "templates", "latex", "skeleton")
51
51
52 #Special Jinja2 syntax that will not conflict when exporting latex.
52 #Special Jinja2 syntax that will not conflict when exporting latex.
53 jinja_comment_block_start = Unicode("((=", config=True)
53 jinja_comment_block_start = Unicode("((=", config=True)
54 jinja_comment_block_end = Unicode("=))", config=True)
54 jinja_comment_block_end = Unicode("=))", config=True)
55 jinja_variable_block_start = Unicode("(((", config=True)
55 jinja_variable_block_start = Unicode("(((", config=True)
56 jinja_variable_block_end = Unicode(")))", config=True)
56 jinja_variable_block_end = Unicode(")))", config=True)
57 jinja_logic_block_start = Unicode("((*", config=True)
57 jinja_logic_block_start = Unicode("((*", config=True)
58 jinja_logic_block_end = Unicode("*))", config=True)
58 jinja_logic_block_end = Unicode("*))", config=True)
59
59
60 #Extension that the template files use.
60 #Extension that the template files use.
61 template_extension = Unicode(".tplx", config=True)
61 template_extension = Unicode(".tplx", config=True)
62
62
63 output_mimetype = 'text/latex'
63 output_mimetype = 'text/latex'
64
64
65
65
66 @property
66 @property
67 def default_config(self):
67 def default_config(self):
68 c = Config({
68 c = Config({
69 'NbConvertBase': {
69 'NbConvertBase': {
70 'display_data_priority' : ['text/latex', 'application/pdf', 'image/png', 'image/jpeg', 'image/svg+xml', 'text/plain']
70 'display_data_priority' : ['text/latex', 'application/pdf', 'image/png', 'image/jpeg', 'image/svg+xml', 'text/plain']
71 },
71 },
72 'ExtractOutputPreprocessor': {
72 'ExtractOutputPreprocessor': {
73 'enabled':True
73 'enabled':True
74 },
74 },
75 'SVG2PDFPreprocessor': {
75 'SVG2PDFPreprocessor': {
76 'enabled':True
76 'enabled':True
77 },
77 },
78 'LatexPreprocessor': {
78 'LatexPreprocessor': {
79 'enabled':True
79 'enabled':True
80 },
80 },
81 'SphinxPreprocessor': {
81 'SphinxPreprocessor': {
82 'enabled':True
82 'enabled':True
83 },
83 },
84 'HighlightMagicsPreprocessor': {
84 'HighlightMagicsPreprocessor': {
85 'enabled':True
85 'enabled':True
86 }
86 }
87 })
87 })
88 c.merge(super(LatexExporter,self).default_config)
88 c.merge(super(LatexExporter,self).default_config)
89 return c
89 return c
90
90
91 def from_notebook_node(self, nb, resources=None, **kw):
91 def from_notebook_node(self, nb, resources=None, **kw):
92 langinfo = nb.metadata.get('language_info', {})
92 langinfo = nb.metadata.get('language_info', {})
93 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
93 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
94 self.register_filter('highlight_code',
94 self.register_filter('highlight_code',
95 Highlight2Latex(pygments_lexer=lexer, parent=self))
95 Highlight2Latex(pygments_lexer=lexer, parent=self))
96 return super(LatexExporter, self).from_notebook_node(nb, resources, **kw)
96 return super(LatexExporter, self).from_notebook_node(nb, resources, **kw)
@@ -1,141 +1,141 b''
1 """Export to PDF via latex"""
1 """Export to PDF via latex"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import subprocess
6 import subprocess
7 import os
7 import os
8 import sys
8 import sys
9
9
10 from IPython.utils.traitlets import Integer, List, Bool, Instance
10 from IPython.utils.traitlets import Integer, List, Bool, Instance
11 from IPython.utils.tempdir import TemporaryWorkingDirectory
11 from IPython.utils.tempdir import TemporaryWorkingDirectory
12 from .latex import LatexExporter
12 from .latex import LatexExporter
13
13
14
14
15 class PDFExporter(LatexExporter):
15 class PDFExporter(LatexExporter):
16 """Writer designed to write to PDF files"""
16 """Writer designed to write to PDF files"""
17
17
18 latex_count = Integer(3, config=True,
18 latex_count = Integer(3, config=True,
19 help="How many times latex will be called."
19 help="How many times latex will be called."
20 )
20 )
21
21
22 latex_command = List([u"pdflatex", u"{filename}"], config=True,
22 latex_command = List([u"pdflatex", u"{filename}"], config=True,
23 help="Shell command used to compile latex."
23 help="Shell command used to compile latex."
24 )
24 )
25
25
26 bib_command = List([u"bibtex", u"{filename}"], config=True,
26 bib_command = List([u"bibtex", u"{filename}"], config=True,
27 help="Shell command used to run bibtex."
27 help="Shell command used to run bibtex."
28 )
28 )
29
29
30 verbose = Bool(False, config=True,
30 verbose = Bool(False, config=True,
31 help="Whether to display the output of latex commands."
31 help="Whether to display the output of latex commands."
32 )
32 )
33
33
34 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
34 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
35 help="File extensions of temp files to remove after running."
35 help="File extensions of temp files to remove after running."
36 )
36 )
37
37
38 writer = Instance("IPython.nbconvert.writers.FilesWriter", args=())
38 writer = Instance("IPython.nbconvert.writers.FilesWriter", args=())
39
39
40 def run_command(self, command_list, filename, count, log_function):
40 def run_command(self, command_list, filename, count, log_function):
41 """Run command_list count times.
41 """Run command_list count times.
42
42
43 Parameters
43 Parameters
44 ----------
44 ----------
45 command_list : list
45 command_list : list
46 A list of args to provide to Popen. Each element of this
46 A list of args to provide to Popen. Each element of this
47 list will be interpolated with the filename to convert.
47 list will be interpolated with the filename to convert.
48 filename : unicode
48 filename : unicode
49 The name of the file to convert.
49 The name of the file to convert.
50 count : int
50 count : int
51 How many times to run the command.
51 How many times to run the command.
52
52
53 Returns
53 Returns
54 -------
54 -------
55 success : bool
55 success : bool
56 A boolean indicating if the command was successful (True)
56 A boolean indicating if the command was successful (True)
57 or failed (False).
57 or failed (False).
58 """
58 """
59 command = [c.format(filename=filename) for c in command_list]
59 command = [c.format(filename=filename) for c in command_list]
60 #In windows and python 2.x there is a bug in subprocess.Popen and
60 #In windows and python 2.x there is a bug in subprocess.Popen and
61 # unicode commands are not supported
61 # unicode commands are not supported
62 if sys.platform == 'win32' and sys.version_info < (3,0):
62 if sys.platform == 'win32' and sys.version_info < (3,0):
63 #We must use cp1252 encoding for calling subprocess.Popen
63 #We must use cp1252 encoding for calling subprocess.Popen
64 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
64 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
65 # could be different (cp437 in case of dos console)
65 # could be different (cp437 in case of dos console)
66 command = [c.encode('cp1252') for c in command]
66 command = [c.encode('cp1252') for c in command]
67 times = 'time' if count == 1 else 'times'
67 times = 'time' if count == 1 else 'times'
68 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
68 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
69 with open(os.devnull, 'rb') as null:
69 with open(os.devnull, 'rb') as null:
70 stdout = subprocess.PIPE if not self.verbose else None
70 stdout = subprocess.PIPE if not self.verbose else None
71 for index in range(count):
71 for index in range(count):
72 p = subprocess.Popen(command, stdout=stdout, stdin=null)
72 p = subprocess.Popen(command, stdout=stdout, stdin=null)
73 out, err = p.communicate()
73 out, err = p.communicate()
74 if p.returncode:
74 if p.returncode:
75 if self.verbose:
75 if self.verbose:
76 # verbose means I didn't capture stdout with PIPE,
76 # verbose means I didn't capture stdout with PIPE,
77 # so it's already been displayed and `out` is None.
77 # so it's already been displayed and `out` is None.
78 out = u''
78 out = u''
79 else:
79 else:
80 out = out.decode('utf-8', 'replace')
80 out = out.decode('utf-8', 'replace')
81 log_function(command, out)
81 log_function(command, out)
82 return False # failure
82 return False # failure
83 return True # success
83 return True # success
84
84
85 def run_latex(self, filename):
85 def run_latex(self, filename):
86 """Run pdflatex self.latex_count times."""
86 """Run pdflatex self.latex_count times."""
87
87
88 def log_error(command, out):
88 def log_error(command, out):
89 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
89 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
90
90
91 return self.run_command(self.latex_command, filename,
91 return self.run_command(self.latex_command, filename,
92 self.latex_count, log_error)
92 self.latex_count, log_error)
93
93
94 def run_bib(self, filename):
94 def run_bib(self, filename):
95 """Run bibtex self.latex_count times."""
95 """Run bibtex self.latex_count times."""
96 filename = os.path.splitext(filename)[0]
96 filename = os.path.splitext(filename)[0]
97
97
98 def log_error(command, out):
98 def log_error(command, out):
99 self.log.warn('%s had problems, most likely because there were no citations',
99 self.log.warn('%s had problems, most likely because there were no citations',
100 command[0])
100 command[0])
101 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
101 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
102
102
103 return self.run_command(self.bib_command, filename, 1, log_error)
103 return self.run_command(self.bib_command, filename, 1, log_error)
104
104
105 def clean_temp_files(self, filename):
105 def clean_temp_files(self, filename):
106 """Remove temporary files created by pdflatex/bibtex."""
106 """Remove temporary files created by pdflatex/bibtex."""
107 self.log.info("Removing temporary LaTeX files")
107 self.log.info("Removing temporary LaTeX files")
108 filename = os.path.splitext(filename)[0]
108 filename = os.path.splitext(filename)[0]
109 for ext in self.temp_file_exts:
109 for ext in self.temp_file_exts:
110 try:
110 try:
111 os.remove(filename+ext)
111 os.remove(filename+ext)
112 except OSError:
112 except OSError:
113 pass
113 pass
114
114
115 def from_notebook_node(self, nb, resources=None, **kw):
115 def from_notebook_node(self, nb, resources=None, **kw):
116 latex, resources = super(PDFExporter, self).from_notebook_node(
116 latex, resources = super(PDFExporter, self).from_notebook_node(
117 nb, resources=resources, **kw
117 nb, resources=resources, **kw
118 )
118 )
119 with TemporaryWorkingDirectory() as td:
119 with TemporaryWorkingDirectory() as td:
120 notebook_name = "notebook"
120 notebook_name = "notebook"
121 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
121 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
122 self.log.info("Building PDF")
122 self.log.info("Building PDF")
123 rc = self.run_latex(tex_file)
123 rc = self.run_latex(tex_file)
124 if not rc:
124 if not rc:
125 rc = self.run_bib(tex_file)
125 rc = self.run_bib(tex_file)
126 if not rc:
126 if not rc:
127 rc = self.run_latex(tex_file)
127 rc = self.run_latex(tex_file)
128
128
129 pdf_file = notebook_name + '.pdf'
129 pdf_file = notebook_name + '.pdf'
130 if not os.path.isfile(pdf_file):
130 if not os.path.isfile(pdf_file):
131 raise RuntimeError("PDF creating failed")
131 raise RuntimeError("PDF creating failed")
132 self.log.info('PDF successfully created')
132 self.log.info('PDF successfully created')
133 with open(pdf_file, 'rb') as f:
133 with open(pdf_file, 'rb') as f:
134 pdf_data = f.read()
134 pdf_data = f.read()
135
135
136 # convert output extension to pdf
136 # convert output extension to pdf
137 # the writer above required it to be tex
137 # the writer above required it to be tex
138 resources['output_extension'] = 'pdf'
138 resources['output_extension'] = '.pdf'
139
139
140 return pdf_data, resources
140 return pdf_data, resources
141
141
@@ -1,31 +1,31 b''
1 """Python script Exporter class"""
1 """Python script Exporter class"""
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 from .templateexporter import TemplateExporter
15 from .templateexporter import TemplateExporter
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Classes
18 # Classes
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 class PythonExporter(TemplateExporter):
21 class PythonExporter(TemplateExporter):
22 """
22 """
23 Exports a Python code file.
23 Exports a Python code file.
24 """
24 """
25 def _file_extension_default(self):
25 def _file_extension_default(self):
26 return 'py'
26 return '.py'
27
27
28 def _template_file_default(self):
28 def _template_file_default(self):
29 return 'python'
29 return 'python'
30
30
31 output_mimetype = 'text/x-python'
31 output_mimetype = 'text/x-python'
@@ -1,40 +1,40 b''
1 """restructuredText Exporter class"""
1 """restructuredText Exporter class"""
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 from IPython.config import Config
15 from IPython.config import Config
16
16
17 from .templateexporter import TemplateExporter
17 from .templateexporter import TemplateExporter
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 class RSTExporter(TemplateExporter):
23 class RSTExporter(TemplateExporter):
24 """
24 """
25 Exports restructured text documents.
25 Exports restructured text documents.
26 """
26 """
27
27
28 def _file_extension_default(self):
28 def _file_extension_default(self):
29 return 'rst'
29 return '.rst'
30
30
31 def _template_file_default(self):
31 def _template_file_default(self):
32 return 'rst'
32 return 'rst'
33
33
34 output_mimetype = 'text/restructuredtext'
34 output_mimetype = 'text/restructuredtext'
35
35
36 @property
36 @property
37 def default_config(self):
37 def default_config(self):
38 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
38 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
39 c.merge(super(RSTExporter,self).default_config)
39 c.merge(super(RSTExporter,self).default_config)
40 return c
40 return c
@@ -1,43 +1,43 b''
1 """HTML slide show Exporter class"""
1 """HTML slide show Exporter class"""
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 from IPython.nbconvert import preprocessors
15 from IPython.nbconvert import preprocessors
16 from IPython.config import Config
16 from IPython.config import Config
17
17
18 from .html import HTMLExporter
18 from .html import HTMLExporter
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 class SlidesExporter(HTMLExporter):
24 class SlidesExporter(HTMLExporter):
25 """Exports HTML slides with reveal.js"""
25 """Exports HTML slides with reveal.js"""
26
26
27 def _file_extension_default(self):
27 def _file_extension_default(self):
28 return 'slides.html'
28 return '.slides.html'
29
29
30 def _template_file_default(self):
30 def _template_file_default(self):
31 return 'slides_reveal'
31 return 'slides_reveal'
32
32
33 output_mimetype = 'text/html'
33 output_mimetype = 'text/html'
34
34
35 @property
35 @property
36 def default_config(self):
36 def default_config(self):
37 c = Config({
37 c = Config({
38 'RevealHelpPreprocessor': {
38 'RevealHelpPreprocessor': {
39 'enabled': True,
39 'enabled': True,
40 },
40 },
41 })
41 })
42 c.merge(super(SlidesExporter,self).default_config)
42 c.merge(super(SlidesExporter,self).default_config)
43 return c
43 return c
@@ -1,324 +1,324 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """NbConvert is a utility for conversion of .ipynb files.
2 """NbConvert is a utility for conversion of .ipynb files.
3
3
4 Command-line interface for the NbConvert conversion utility.
4 Command-line interface for the NbConvert conversion utility.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 import logging
12 import logging
13 import sys
13 import sys
14 import os
14 import os
15 import glob
15 import glob
16
16
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.config import catch_config_error, Configurable
19 from IPython.config import catch_config_error, Configurable
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
22 )
22 )
23 from IPython.utils.importstring import import_item
23 from IPython.utils.importstring import import_item
24
24
25 from .exporters.export import get_export_names, exporter_map
25 from .exporters.export import get_export_names, exporter_map
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
27 from .utils.base import NbConvertBase
27 from .utils.base import NbConvertBase
28 from .utils.exceptions import ConversionException
28 from .utils.exceptions import ConversionException
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 #Classes and functions
31 #Classes and functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class DottedOrNone(DottedObjectName):
34 class DottedOrNone(DottedObjectName):
35 """
35 """
36 A string holding a valid dotted object name in Python, such as A.b3._c
36 A string holding a valid dotted object name in Python, such as A.b3._c
37 Also allows for None type."""
37 Also allows for None type."""
38
38
39 default_value = u''
39 default_value = u''
40
40
41 def validate(self, obj, value):
41 def validate(self, obj, value):
42 if value is not None and len(value) > 0:
42 if value is not None and len(value) > 0:
43 return super(DottedOrNone, self).validate(obj, value)
43 return super(DottedOrNone, self).validate(obj, value)
44 else:
44 else:
45 return value
45 return value
46
46
47 nbconvert_aliases = {}
47 nbconvert_aliases = {}
48 nbconvert_aliases.update(base_aliases)
48 nbconvert_aliases.update(base_aliases)
49 nbconvert_aliases.update({
49 nbconvert_aliases.update({
50 'to' : 'NbConvertApp.export_format',
50 'to' : 'NbConvertApp.export_format',
51 'template' : 'TemplateExporter.template_file',
51 'template' : 'TemplateExporter.template_file',
52 'writer' : 'NbConvertApp.writer_class',
52 'writer' : 'NbConvertApp.writer_class',
53 'post': 'NbConvertApp.postprocessor_class',
53 'post': 'NbConvertApp.postprocessor_class',
54 'output': 'NbConvertApp.output_base',
54 'output': 'NbConvertApp.output_base',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
56 'nbformat': 'NotebookExporter.nbformat_version',
56 'nbformat': 'NotebookExporter.nbformat_version',
57 })
57 })
58
58
59 nbconvert_flags = {}
59 nbconvert_flags = {}
60 nbconvert_flags.update(base_flags)
60 nbconvert_flags.update(base_flags)
61 nbconvert_flags.update({
61 nbconvert_flags.update({
62 'execute' : (
62 'execute' : (
63 {'ExecutePreprocessor' : {'enabled' : True}},
63 {'ExecutePreprocessor' : {'enabled' : True}},
64 "Execute the notebook prior to export."
64 "Execute the notebook prior to export."
65 ),
65 ),
66 'stdout' : (
66 'stdout' : (
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
67 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
68 "Write notebook output to stdout instead of files."
68 "Write notebook output to stdout instead of files."
69 )
69 )
70 })
70 })
71
71
72
72
73 class NbConvertApp(BaseIPythonApplication):
73 class NbConvertApp(BaseIPythonApplication):
74 """Application used to convert from notebook file type (``*.ipynb``)"""
74 """Application used to convert from notebook file type (``*.ipynb``)"""
75
75
76 name = 'ipython-nbconvert'
76 name = 'ipython-nbconvert'
77 aliases = nbconvert_aliases
77 aliases = nbconvert_aliases
78 flags = nbconvert_flags
78 flags = nbconvert_flags
79
79
80 def _log_level_default(self):
80 def _log_level_default(self):
81 return logging.INFO
81 return logging.INFO
82
82
83 def _classes_default(self):
83 def _classes_default(self):
84 classes = [NbConvertBase, ProfileDir]
84 classes = [NbConvertBase, ProfileDir]
85 for pkg in (exporters, preprocessors, writers, postprocessors):
85 for pkg in (exporters, preprocessors, writers, postprocessors):
86 for name in dir(pkg):
86 for name in dir(pkg):
87 cls = getattr(pkg, name)
87 cls = getattr(pkg, name)
88 if isinstance(cls, type) and issubclass(cls, Configurable):
88 if isinstance(cls, type) and issubclass(cls, Configurable):
89 classes.append(cls)
89 classes.append(cls)
90
90
91 return classes
91 return classes
92
92
93 description = Unicode(
93 description = Unicode(
94 u"""This application is used to convert notebook files (*.ipynb)
94 u"""This application is used to convert notebook files (*.ipynb)
95 to various other formats.
95 to various other formats.
96
96
97 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
97 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
98
98
99 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
99 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
100 can only be used when converting one notebook at a time.
100 can only be used when converting one notebook at a time.
101 ''')
101 ''')
102
102
103 examples = Unicode(u"""
103 examples = Unicode(u"""
104 The simplest way to use nbconvert is
104 The simplest way to use nbconvert is
105
105
106 > ipython nbconvert mynotebook.ipynb
106 > ipython nbconvert mynotebook.ipynb
107
107
108 which will convert mynotebook.ipynb to the default format (probably HTML).
108 which will convert mynotebook.ipynb to the default format (probably HTML).
109
109
110 You can specify the export format with `--to`.
110 You can specify the export format with `--to`.
111 Options include {0}
111 Options include {0}
112
112
113 > ipython nbconvert --to latex mynotebook.ipynb
113 > ipython nbconvert --to latex mynotebook.ipynb
114
114
115 Both HTML and LaTeX support multiple output templates. LaTeX includes
115 Both HTML and LaTeX support multiple output templates. LaTeX includes
116 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
116 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You
117 can specify the flavor of the format used.
117 can specify the flavor of the format used.
118
118
119 > ipython nbconvert --to html --template basic mynotebook.ipynb
119 > ipython nbconvert --to html --template basic mynotebook.ipynb
120
120
121 You can also pipe the output to stdout, rather than a file
121 You can also pipe the output to stdout, rather than a file
122
122
123 > ipython nbconvert mynotebook.ipynb --stdout
123 > ipython nbconvert mynotebook.ipynb --stdout
124
124
125 PDF is generated via latex
125 PDF is generated via latex
126
126
127 > ipython nbconvert mynotebook.ipynb --to pdf
127 > ipython nbconvert mynotebook.ipynb --to pdf
128
128
129 You can get (and serve) a Reveal.js-powered slideshow
129 You can get (and serve) a Reveal.js-powered slideshow
130
130
131 > ipython nbconvert myslides.ipynb --to slides --post serve
131 > ipython nbconvert myslides.ipynb --to slides --post serve
132
132
133 Multiple notebooks can be given at the command line in a couple of
133 Multiple notebooks can be given at the command line in a couple of
134 different ways:
134 different ways:
135
135
136 > ipython nbconvert notebook*.ipynb
136 > ipython nbconvert notebook*.ipynb
137 > ipython nbconvert notebook1.ipynb notebook2.ipynb
137 > ipython nbconvert notebook1.ipynb notebook2.ipynb
138
138
139 or you can specify the notebooks list in a config file, containing::
139 or you can specify the notebooks list in a config file, containing::
140
140
141 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
141 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
142
142
143 > ipython nbconvert --config mycfg.py
143 > ipython nbconvert --config mycfg.py
144 """.format(get_export_names()))
144 """.format(get_export_names()))
145
145
146 # Writer specific variables
146 # Writer specific variables
147 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
147 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
148 help="""Instance of the writer class used to write the
148 help="""Instance of the writer class used to write the
149 results of the conversion.""")
149 results of the conversion.""")
150 writer_class = DottedObjectName('FilesWriter', config=True,
150 writer_class = DottedObjectName('FilesWriter', config=True,
151 help="""Writer class used to write the
151 help="""Writer class used to write the
152 results of the conversion""")
152 results of the conversion""")
153 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
153 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
154 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
154 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
155 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
155 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
156 writer_factory = Type()
156 writer_factory = Type()
157
157
158 def _writer_class_changed(self, name, old, new):
158 def _writer_class_changed(self, name, old, new):
159 if new.lower() in self.writer_aliases:
159 if new.lower() in self.writer_aliases:
160 new = self.writer_aliases[new.lower()]
160 new = self.writer_aliases[new.lower()]
161 self.writer_factory = import_item(new)
161 self.writer_factory = import_item(new)
162
162
163 # Post-processor specific variables
163 # Post-processor specific variables
164 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
164 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
165 help="""Instance of the PostProcessor class used to write the
165 help="""Instance of the PostProcessor class used to write the
166 results of the conversion.""")
166 results of the conversion.""")
167
167
168 postprocessor_class = DottedOrNone(config=True,
168 postprocessor_class = DottedOrNone(config=True,
169 help="""PostProcessor class used to write the
169 help="""PostProcessor class used to write the
170 results of the conversion""")
170 results of the conversion""")
171 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
171 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
172 postprocessor_factory = Type()
172 postprocessor_factory = Type()
173
173
174 def _postprocessor_class_changed(self, name, old, new):
174 def _postprocessor_class_changed(self, name, old, new):
175 if new.lower() in self.postprocessor_aliases:
175 if new.lower() in self.postprocessor_aliases:
176 new = self.postprocessor_aliases[new.lower()]
176 new = self.postprocessor_aliases[new.lower()]
177 if new:
177 if new:
178 self.postprocessor_factory = import_item(new)
178 self.postprocessor_factory = import_item(new)
179
179
180
180
181 # Other configurable variables
181 # Other configurable variables
182 export_format = CaselessStrEnum(get_export_names(),
182 export_format = CaselessStrEnum(get_export_names(),
183 default_value="html",
183 default_value="html",
184 config=True,
184 config=True,
185 help="""The export format to be used."""
185 help="""The export format to be used."""
186 )
186 )
187
187
188 notebooks = List([], config=True, help="""List of notebooks to convert.
188 notebooks = List([], config=True, help="""List of notebooks to convert.
189 Wildcards are supported.
189 Wildcards are supported.
190 Filenames passed positionally will be added to the list.
190 Filenames passed positionally will be added to the list.
191 """)
191 """)
192
192
193 @catch_config_error
193 @catch_config_error
194 def initialize(self, argv=None):
194 def initialize(self, argv=None):
195 self.init_syspath()
195 self.init_syspath()
196 super(NbConvertApp, self).initialize(argv)
196 super(NbConvertApp, self).initialize(argv)
197 self.init_notebooks()
197 self.init_notebooks()
198 self.init_writer()
198 self.init_writer()
199 self.init_postprocessor()
199 self.init_postprocessor()
200
200
201
201
202
202
203 def init_syspath(self):
203 def init_syspath(self):
204 """
204 """
205 Add the cwd to the sys.path ($PYTHONPATH)
205 Add the cwd to the sys.path ($PYTHONPATH)
206 """
206 """
207 sys.path.insert(0, os.getcwd())
207 sys.path.insert(0, os.getcwd())
208
208
209
209
210 def init_notebooks(self):
210 def init_notebooks(self):
211 """Construct the list of notebooks.
211 """Construct the list of notebooks.
212 If notebooks are passed on the command-line,
212 If notebooks are passed on the command-line,
213 they override notebooks specified in config files.
213 they override notebooks specified in config files.
214 Glob each notebook to replace notebook patterns with filenames.
214 Glob each notebook to replace notebook patterns with filenames.
215 """
215 """
216
216
217 # Specifying notebooks on the command-line overrides (rather than adds)
217 # Specifying notebooks on the command-line overrides (rather than adds)
218 # the notebook list
218 # the notebook list
219 if self.extra_args:
219 if self.extra_args:
220 patterns = self.extra_args
220 patterns = self.extra_args
221 else:
221 else:
222 patterns = self.notebooks
222 patterns = self.notebooks
223
223
224 # Use glob to replace all the notebook patterns with filenames.
224 # Use glob to replace all the notebook patterns with filenames.
225 filenames = []
225 filenames = []
226 for pattern in patterns:
226 for pattern in patterns:
227
227
228 # Use glob to find matching filenames. Allow the user to convert
228 # Use glob to find matching filenames. Allow the user to convert
229 # notebooks without having to type the extension.
229 # notebooks without having to type the extension.
230 globbed_files = glob.glob(pattern)
230 globbed_files = glob.glob(pattern)
231 globbed_files.extend(glob.glob(pattern + '.ipynb'))
231 globbed_files.extend(glob.glob(pattern + '.ipynb'))
232 if not globbed_files:
232 if not globbed_files:
233 self.log.warn("pattern %r matched no files", pattern)
233 self.log.warn("pattern %r matched no files", pattern)
234
234
235 for filename in globbed_files:
235 for filename in globbed_files:
236 if not filename in filenames:
236 if not filename in filenames:
237 filenames.append(filename)
237 filenames.append(filename)
238 self.notebooks = filenames
238 self.notebooks = filenames
239
239
240 def init_writer(self):
240 def init_writer(self):
241 """
241 """
242 Initialize the writer (which is stateless)
242 Initialize the writer (which is stateless)
243 """
243 """
244 self._writer_class_changed(None, self.writer_class, self.writer_class)
244 self._writer_class_changed(None, self.writer_class, self.writer_class)
245 self.writer = self.writer_factory(parent=self)
245 self.writer = self.writer_factory(parent=self)
246
246
247 def init_postprocessor(self):
247 def init_postprocessor(self):
248 """
248 """
249 Initialize the postprocessor (which is stateless)
249 Initialize the postprocessor (which is stateless)
250 """
250 """
251 self._postprocessor_class_changed(None, self.postprocessor_class,
251 self._postprocessor_class_changed(None, self.postprocessor_class,
252 self.postprocessor_class)
252 self.postprocessor_class)
253 if self.postprocessor_factory:
253 if self.postprocessor_factory:
254 self.postprocessor = self.postprocessor_factory(parent=self)
254 self.postprocessor = self.postprocessor_factory(parent=self)
255
255
256 def start(self):
256 def start(self):
257 """
257 """
258 Ran after initialization completed
258 Ran after initialization completed
259 """
259 """
260 super(NbConvertApp, self).start()
260 super(NbConvertApp, self).start()
261 self.convert_notebooks()
261 self.convert_notebooks()
262
262
263 def convert_notebooks(self):
263 def convert_notebooks(self):
264 """
264 """
265 Convert the notebooks in the self.notebook traitlet
265 Convert the notebooks in the self.notebook traitlet
266 """
266 """
267 # Export each notebook
267 # Export each notebook
268 conversion_success = 0
268 conversion_success = 0
269
269
270 if self.output_base != '' and len(self.notebooks) > 1:
270 if self.output_base != '' and len(self.notebooks) > 1:
271 self.log.error(
271 self.log.error(
272 """UsageError: --output flag or `NbConvertApp.output_base` config option
272 """UsageError: --output flag or `NbConvertApp.output_base` config option
273 cannot be used when converting multiple notebooks.
273 cannot be used when converting multiple notebooks.
274 """)
274 """)
275 self.exit(1)
275 self.exit(1)
276
276
277 exporter = exporter_map[self.export_format](config=self.config)
277 exporter = exporter_map[self.export_format](config=self.config)
278
278
279 for notebook_filename in self.notebooks:
279 for notebook_filename in self.notebooks:
280 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
280 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
281
281
282 # Get a unique key for the notebook and set it in the resources object.
282 # Get a unique key for the notebook and set it in the resources object.
283 basename = os.path.basename(notebook_filename)
283 basename = os.path.basename(notebook_filename)
284 notebook_name = basename[:basename.rfind('.')]
284 notebook_name = basename[:basename.rfind('.')]
285 if self.output_base:
285 if self.output_base:
286 # strip duplicate extension from output_base, to avoid Basname.ext.ext
286 # strip duplicate extension from output_base, to avoid Basname.ext.ext
287 if getattr(exporter, 'file_extension', False):
287 if getattr(exporter, 'file_extension', False):
288 base, ext = os.path.splitext(self.output_base)
288 base, ext = os.path.splitext(self.output_base)
289 if ext == '.' + exporter.file_extension:
289 if ext == exporter.file_extension:
290 self.output_base = base
290 self.output_base = base
291 notebook_name = self.output_base
291 notebook_name = self.output_base
292 resources = {}
292 resources = {}
293 resources['profile_dir'] = self.profile_dir.location
293 resources['profile_dir'] = self.profile_dir.location
294 resources['unique_key'] = notebook_name
294 resources['unique_key'] = notebook_name
295 resources['output_files_dir'] = '%s_files' % notebook_name
295 resources['output_files_dir'] = '%s_files' % notebook_name
296 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
296 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
297
297
298 # Try to export
298 # Try to export
299 try:
299 try:
300 output, resources = exporter.from_filename(notebook_filename, resources=resources)
300 output, resources = exporter.from_filename(notebook_filename, resources=resources)
301 except ConversionException as e:
301 except ConversionException as e:
302 self.log.error("Error while converting '%s'", notebook_filename,
302 self.log.error("Error while converting '%s'", notebook_filename,
303 exc_info=True)
303 exc_info=True)
304 self.exit(1)
304 self.exit(1)
305 else:
305 else:
306 if 'output_suffix' in resources and not self.output_base:
306 if 'output_suffix' in resources and not self.output_base:
307 notebook_name += resources['output_suffix']
307 notebook_name += resources['output_suffix']
308 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
308 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
309
309
310 #Post-process if post processor has been defined.
310 #Post-process if post processor has been defined.
311 if hasattr(self, 'postprocessor') and self.postprocessor:
311 if hasattr(self, 'postprocessor') and self.postprocessor:
312 self.postprocessor(write_results)
312 self.postprocessor(write_results)
313 conversion_success += 1
313 conversion_success += 1
314
314
315 # If nothing was converted successfully, help the user.
315 # If nothing was converted successfully, help the user.
316 if conversion_success == 0:
316 if conversion_success == 0:
317 self.print_help()
317 self.print_help()
318 sys.exit(-1)
318 sys.exit(-1)
319
319
320 #-----------------------------------------------------------------------------
320 #-----------------------------------------------------------------------------
321 # Main entry point
321 # Main entry point
322 #-----------------------------------------------------------------------------
322 #-----------------------------------------------------------------------------
323
323
324 launch_new_instance = NbConvertApp.launch_instance
324 launch_new_instance = NbConvertApp.launch_instance
@@ -1,111 +1,111 b''
1 """Contains writer for writing nbconvert output to filesystem."""
1 """Contains writer for writing nbconvert output to filesystem."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import io
6 import io
7 import os
7 import os
8 import glob
8 import glob
9
9
10 from IPython.utils.traitlets import Unicode
10 from IPython.utils.traitlets import Unicode
11 from IPython.utils.path import link_or_copy, ensure_dir_exists
11 from IPython.utils.path import link_or_copy, ensure_dir_exists
12 from IPython.utils.py3compat import unicode_type
12 from IPython.utils.py3compat import unicode_type
13
13
14 from .base import WriterBase
14 from .base import WriterBase
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Classes
17 # Classes
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 class FilesWriter(WriterBase):
20 class FilesWriter(WriterBase):
21 """Consumes nbconvert output and produces files."""
21 """Consumes nbconvert output and produces files."""
22
22
23
23
24 build_directory = Unicode("", config=True,
24 build_directory = Unicode("", config=True,
25 help="""Directory to write output to. Leave blank
25 help="""Directory to write output to. Leave blank
26 to output to the current directory""")
26 to output to the current directory""")
27
27
28
28
29 # Make sure that the output directory exists.
29 # Make sure that the output directory exists.
30 def _build_directory_changed(self, name, old, new):
30 def _build_directory_changed(self, name, old, new):
31 if new:
31 if new:
32 ensure_dir_exists(new)
32 ensure_dir_exists(new)
33
33
34
34
35 def __init__(self, **kw):
35 def __init__(self, **kw):
36 super(FilesWriter, self).__init__(**kw)
36 super(FilesWriter, self).__init__(**kw)
37 self._build_directory_changed('build_directory', self.build_directory,
37 self._build_directory_changed('build_directory', self.build_directory,
38 self.build_directory)
38 self.build_directory)
39
39
40 def _makedir(self, path):
40 def _makedir(self, path):
41 """Make a directory if it doesn't already exist"""
41 """Make a directory if it doesn't already exist"""
42 if path:
42 if path:
43 self.log.info("Making directory %s", path)
43 self.log.info("Making directory %s", path)
44 ensure_dir_exists(path)
44 ensure_dir_exists(path)
45
45
46 def write(self, output, resources, notebook_name=None, **kw):
46 def write(self, output, resources, notebook_name=None, **kw):
47 """
47 """
48 Consume and write Jinja output to the file system. Output directory
48 Consume and write Jinja output to the file system. Output directory
49 is set via the 'build_directory' variable of this instance (a
49 is set via the 'build_directory' variable of this instance (a
50 configurable).
50 configurable).
51
51
52 See base for more...
52 See base for more...
53 """
53 """
54
54
55 # Verify that a notebook name is provided.
55 # Verify that a notebook name is provided.
56 if notebook_name is None:
56 if notebook_name is None:
57 raise TypeError('notebook_name')
57 raise TypeError('notebook_name')
58
58
59 # Pull the extension and subdir from the resources dict.
59 # Pull the extension and subdir from the resources dict.
60 output_extension = resources.get('output_extension', None)
60 output_extension = resources.get('output_extension', None)
61
61
62 # Write all of the extracted resources to the destination directory.
62 # Write all of the extracted resources to the destination directory.
63 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
63 # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
64 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
64 # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
65 for filename, data in resources.get('outputs', {}).items():
65 for filename, data in resources.get('outputs', {}).items():
66
66
67 # Determine where to write the file to
67 # Determine where to write the file to
68 dest = os.path.join(self.build_directory, filename)
68 dest = os.path.join(self.build_directory, filename)
69 path = os.path.dirname(dest)
69 path = os.path.dirname(dest)
70 self._makedir(path)
70 self._makedir(path)
71
71
72 # Write file
72 # Write file
73 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
73 self.log.debug("Writing %i bytes to support file %s", len(data), dest)
74 with io.open(dest, 'wb') as f:
74 with io.open(dest, 'wb') as f:
75 f.write(data)
75 f.write(data)
76
76
77 # Copy referenced files to output directory
77 # Copy referenced files to output directory
78 if self.build_directory:
78 if self.build_directory:
79 for filename in self.files:
79 for filename in self.files:
80
80
81 # Copy files that match search pattern
81 # Copy files that match search pattern
82 for matching_filename in glob.glob(filename):
82 for matching_filename in glob.glob(filename):
83
83
84 # Make sure folder exists.
84 # Make sure folder exists.
85 dest = os.path.join(self.build_directory, matching_filename)
85 dest = os.path.join(self.build_directory, matching_filename)
86 path = os.path.dirname(dest)
86 path = os.path.dirname(dest)
87 self._makedir(path)
87 self._makedir(path)
88
88
89 # Copy if destination is different.
89 # Copy if destination is different.
90 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
90 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
91 self.log.info("Linking %s -> %s", matching_filename, dest)
91 self.log.info("Linking %s -> %s", matching_filename, dest)
92 link_or_copy(matching_filename, dest)
92 link_or_copy(matching_filename, dest)
93
93
94 # Determine where to write conversion results.
94 # Determine where to write conversion results.
95 if output_extension is not None:
95 if output_extension is not None:
96 dest = notebook_name + '.' + output_extension
96 dest = notebook_name + output_extension
97 else:
97 else:
98 dest = notebook_name
98 dest = notebook_name
99 if self.build_directory:
99 if self.build_directory:
100 dest = os.path.join(self.build_directory, dest)
100 dest = os.path.join(self.build_directory, dest)
101
101
102 # Write conversion results.
102 # Write conversion results.
103 self.log.info("Writing %i bytes to %s", len(output), dest)
103 self.log.info("Writing %i bytes to %s", len(output), dest)
104 if isinstance(output, unicode_type):
104 if isinstance(output, unicode_type):
105 with io.open(dest, 'w', encoding='utf-8') as f:
105 with io.open(dest, 'w', encoding='utf-8') as f:
106 f.write(output)
106 f.write(output)
107 else:
107 else:
108 with io.open(dest, 'wb') as f:
108 with io.open(dest, 'wb') as f:
109 f.write(output)
109 f.write(output)
110
110
111 return dest
111 return dest
@@ -1,203 +1,203 b''
1 """
1 """
2 Module with tests for files
2 Module with tests for files
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18 import os
18 import os
19
19
20 from ...tests.base import TestsBase
20 from ...tests.base import TestsBase
21 from ..files import FilesWriter
21 from ..files import FilesWriter
22 from IPython.utils.py3compat import PY3
22 from IPython.utils.py3compat import PY3
23
23
24 if PY3:
24 if PY3:
25 from io import StringIO
25 from io import StringIO
26 else:
26 else:
27 from StringIO import StringIO
27 from StringIO import StringIO
28
28
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Class
31 # Class
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class Testfiles(TestsBase):
34 class Testfiles(TestsBase):
35 """Contains test functions for files.py"""
35 """Contains test functions for files.py"""
36
36
37 def test_basic_output(self):
37 def test_basic_output(self):
38 """Is FilesWriter basic output correct?"""
38 """Is FilesWriter basic output correct?"""
39
39
40 # Work in a temporary directory.
40 # Work in a temporary directory.
41 with self.create_temp_cwd():
41 with self.create_temp_cwd():
42
42
43 # Create the resoruces dictionary
43 # Create the resoruces dictionary
44 res = {}
44 res = {}
45
45
46 # Create files writer, test output
46 # Create files writer, test output
47 writer = FilesWriter()
47 writer = FilesWriter()
48 writer.write(u'y', res, notebook_name="z")
48 writer.write(u'y', res, notebook_name="z")
49
49
50 # Check the output of the file
50 # Check the output of the file
51 with open('z', 'r') as f:
51 with open('z', 'r') as f:
52 output = f.read()
52 output = f.read()
53 self.assertEqual(output, u'y')
53 self.assertEqual(output, u'y')
54
54
55 def test_ext(self):
55 def test_ext(self):
56 """Does the FilesWriter add the correct extension to the output?"""
56 """Does the FilesWriter add the correct extension to the output?"""
57
57
58 # Work in a temporary directory.
58 # Work in a temporary directory.
59 with self.create_temp_cwd():
59 with self.create_temp_cwd():
60
60
61 # Create the resoruces dictionary
61 # Create the resoruces dictionary
62 res = {'output_extension': 'txt'}
62 res = {'output_extension': '.txt'}
63
63
64 # Create files writer, test output
64 # Create files writer, test output
65 writer = FilesWriter()
65 writer = FilesWriter()
66 writer.write(u'y', res, notebook_name="z")
66 writer.write(u'y', res, notebook_name="z")
67
67
68 # Check the output of the file
68 # Check the output of the file
69 assert os.path.isfile('z.txt')
69 assert os.path.isfile('z.txt')
70 with open('z.txt', 'r') as f:
70 with open('z.txt', 'r') as f:
71 output = f.read()
71 output = f.read()
72 self.assertEqual(output, u'y')
72 self.assertEqual(output, u'y')
73
73
74
74
75 def test_extract(self):
75 def test_extract(self):
76 """Can FilesWriter write extracted figures correctly?"""
76 """Can FilesWriter write extracted figures correctly?"""
77
77
78 # Work in a temporary directory.
78 # Work in a temporary directory.
79 with self.create_temp_cwd():
79 with self.create_temp_cwd():
80
80
81 # Create the resoruces dictionary
81 # Create the resoruces dictionary
82 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
82 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
83
83
84 # Create files writer, test output
84 # Create files writer, test output
85 writer = FilesWriter()
85 writer = FilesWriter()
86 writer.write(u'y', res, notebook_name="z")
86 writer.write(u'y', res, notebook_name="z")
87
87
88 # Check the output of the file
88 # Check the output of the file
89 with open('z', 'r') as f:
89 with open('z', 'r') as f:
90 output = f.read()
90 output = f.read()
91 self.assertEqual(output, u'y')
91 self.assertEqual(output, u'y')
92
92
93 # Check the output of the extracted file
93 # Check the output of the extracted file
94 extracted_file_dest = os.path.join('z_files', 'a')
94 extracted_file_dest = os.path.join('z_files', 'a')
95 assert os.path.isfile(extracted_file_dest)
95 assert os.path.isfile(extracted_file_dest)
96 with open(extracted_file_dest, 'r') as f:
96 with open(extracted_file_dest, 'r') as f:
97 output = f.read()
97 output = f.read()
98 self.assertEqual(output, 'b')
98 self.assertEqual(output, 'b')
99
99
100
100
101 def test_builddir(self):
101 def test_builddir(self):
102 """Can FilesWriter write to a build dir correctly?"""
102 """Can FilesWriter write to a build dir correctly?"""
103
103
104 # Work in a temporary directory.
104 # Work in a temporary directory.
105 with self.create_temp_cwd():
105 with self.create_temp_cwd():
106
106
107 # Create the resoruces dictionary
107 # Create the resoruces dictionary
108 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
108 res = {'outputs': {os.path.join('z_files', 'a'): b'b'}}
109
109
110 # Create files writer, test output
110 # Create files writer, test output
111 writer = FilesWriter()
111 writer = FilesWriter()
112 writer.build_directory = u'build'
112 writer.build_directory = u'build'
113 writer.write(u'y', res, notebook_name="z")
113 writer.write(u'y', res, notebook_name="z")
114
114
115 # Check the output of the file
115 # Check the output of the file
116 assert os.path.isdir(writer.build_directory)
116 assert os.path.isdir(writer.build_directory)
117 dest = os.path.join(writer.build_directory, 'z')
117 dest = os.path.join(writer.build_directory, 'z')
118 with open(dest, 'r') as f:
118 with open(dest, 'r') as f:
119 output = f.read()
119 output = f.read()
120 self.assertEqual(output, u'y')
120 self.assertEqual(output, u'y')
121
121
122 # Check the output of the extracted file
122 # Check the output of the extracted file
123 extracted_file_dest = os.path.join(writer.build_directory, 'z_files', 'a')
123 extracted_file_dest = os.path.join(writer.build_directory, 'z_files', 'a')
124 assert os.path.isfile(extracted_file_dest)
124 assert os.path.isfile(extracted_file_dest)
125 with open(extracted_file_dest, 'r') as f:
125 with open(extracted_file_dest, 'r') as f:
126 output = f.read()
126 output = f.read()
127 self.assertEqual(output, 'b')
127 self.assertEqual(output, 'b')
128
128
129
129
130 def test_links(self):
130 def test_links(self):
131 """Can the FilesWriter handle linked files correctly?"""
131 """Can the FilesWriter handle linked files correctly?"""
132
132
133 # Work in a temporary directory.
133 # Work in a temporary directory.
134 with self.create_temp_cwd():
134 with self.create_temp_cwd():
135
135
136 # Create test file
136 # Create test file
137 os.mkdir('sub')
137 os.mkdir('sub')
138 with open(os.path.join('sub', 'c'), 'w') as f:
138 with open(os.path.join('sub', 'c'), 'w') as f:
139 f.write('d')
139 f.write('d')
140
140
141 # Create the resoruces dictionary
141 # Create the resoruces dictionary
142 res = {}
142 res = {}
143
143
144 # Create files writer, test output
144 # Create files writer, test output
145 writer = FilesWriter()
145 writer = FilesWriter()
146 writer.files = [os.path.join('sub', 'c')]
146 writer.files = [os.path.join('sub', 'c')]
147 writer.build_directory = u'build'
147 writer.build_directory = u'build'
148 writer.write(u'y', res, notebook_name="z")
148 writer.write(u'y', res, notebook_name="z")
149
149
150 # Check the output of the file
150 # Check the output of the file
151 assert os.path.isdir(writer.build_directory)
151 assert os.path.isdir(writer.build_directory)
152 dest = os.path.join(writer.build_directory, 'z')
152 dest = os.path.join(writer.build_directory, 'z')
153 with open(dest, 'r') as f:
153 with open(dest, 'r') as f:
154 output = f.read()
154 output = f.read()
155 self.assertEqual(output, u'y')
155 self.assertEqual(output, u'y')
156
156
157 # Check to make sure the linked file was copied
157 # Check to make sure the linked file was copied
158 path = os.path.join(writer.build_directory, 'sub')
158 path = os.path.join(writer.build_directory, 'sub')
159 assert os.path.isdir(path)
159 assert os.path.isdir(path)
160 dest = os.path.join(path, 'c')
160 dest = os.path.join(path, 'c')
161 assert os.path.isfile(dest)
161 assert os.path.isfile(dest)
162 with open(dest, 'r') as f:
162 with open(dest, 'r') as f:
163 output = f.read()
163 output = f.read()
164 self.assertEqual(output, 'd')
164 self.assertEqual(output, 'd')
165
165
166 def test_glob(self):
166 def test_glob(self):
167 """Can the FilesWriter handle globbed files correctly?"""
167 """Can the FilesWriter handle globbed files correctly?"""
168
168
169 # Work in a temporary directory.
169 # Work in a temporary directory.
170 with self.create_temp_cwd():
170 with self.create_temp_cwd():
171
171
172 # Create test files
172 # Create test files
173 os.mkdir('sub')
173 os.mkdir('sub')
174 with open(os.path.join('sub', 'c'), 'w') as f:
174 with open(os.path.join('sub', 'c'), 'w') as f:
175 f.write('e')
175 f.write('e')
176 with open(os.path.join('sub', 'd'), 'w') as f:
176 with open(os.path.join('sub', 'd'), 'w') as f:
177 f.write('e')
177 f.write('e')
178
178
179 # Create the resoruces dictionary
179 # Create the resoruces dictionary
180 res = {}
180 res = {}
181
181
182 # Create files writer, test output
182 # Create files writer, test output
183 writer = FilesWriter()
183 writer = FilesWriter()
184 writer.files = ['sub/*']
184 writer.files = ['sub/*']
185 writer.build_directory = u'build'
185 writer.build_directory = u'build'
186 writer.write(u'y', res, notebook_name="z")
186 writer.write(u'y', res, notebook_name="z")
187
187
188 # Check the output of the file
188 # Check the output of the file
189 assert os.path.isdir(writer.build_directory)
189 assert os.path.isdir(writer.build_directory)
190 dest = os.path.join(writer.build_directory, 'z')
190 dest = os.path.join(writer.build_directory, 'z')
191 with open(dest, 'r') as f:
191 with open(dest, 'r') as f:
192 output = f.read()
192 output = f.read()
193 self.assertEqual(output, u'y')
193 self.assertEqual(output, u'y')
194
194
195 # Check to make sure the globbed files were copied
195 # Check to make sure the globbed files were copied
196 path = os.path.join(writer.build_directory, 'sub')
196 path = os.path.join(writer.build_directory, 'sub')
197 assert os.path.isdir(path)
197 assert os.path.isdir(path)
198 for filename in ['c', 'd']:
198 for filename in ['c', 'd']:
199 dest = os.path.join(path, filename)
199 dest = os.path.join(path, filename)
200 assert os.path.isfile(dest)
200 assert os.path.isfile(dest)
201 with open(dest, 'r') as f:
201 with open(dest, 'r') as f:
202 output = f.read()
202 output = f.read()
203 self.assertEqual(output, 'e')
203 self.assertEqual(output, 'e')
@@ -1,1184 +1,1192 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Versioning
8 Versioning
9 ==========
9 ==========
10
10
11 The IPython message specification is versioned independently of IPython.
11 The IPython message specification is versioned independently of IPython.
12 The current version of the specification is 5.0.
12 The current version of the specification is 5.0.
13
13
14
14
15 Introduction
15 Introduction
16 ============
16 ============
17
17
18 This document explains the basic communications design and messaging
18 This document explains the basic communications design and messaging
19 specification for how the various IPython objects interact over a network
19 specification for how the various IPython objects interact over a network
20 transport. The current implementation uses the ZeroMQ_ library for messaging
20 transport. The current implementation uses the ZeroMQ_ library for messaging
21 within and between hosts.
21 within and between hosts.
22
22
23 .. Note::
23 .. Note::
24
24
25 This document should be considered the authoritative description of the
25 This document should be considered the authoritative description of the
26 IPython messaging protocol, and all developers are strongly encouraged to
26 IPython messaging protocol, and all developers are strongly encouraged to
27 keep it updated as the implementation evolves, so that we have a single
27 keep it updated as the implementation evolves, so that we have a single
28 common reference for all protocol details.
28 common reference for all protocol details.
29
29
30 The basic design is explained in the following diagram:
30 The basic design is explained in the following diagram:
31
31
32 .. image:: figs/frontend-kernel.png
32 .. image:: figs/frontend-kernel.png
33 :width: 450px
33 :width: 450px
34 :alt: IPython kernel/frontend messaging architecture.
34 :alt: IPython kernel/frontend messaging architecture.
35 :align: center
35 :align: center
36 :target: ../_images/frontend-kernel.png
36 :target: ../_images/frontend-kernel.png
37
37
38 A single kernel can be simultaneously connected to one or more frontends. The
38 A single kernel can be simultaneously connected to one or more frontends. The
39 kernel has three sockets that serve the following functions:
39 kernel has three sockets that serve the following functions:
40
40
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
42 frontends, and this is the socket where requests for code execution, object
42 frontends, and this is the socket where requests for code execution, object
43 information, prompts, etc. are made to the kernel by any frontend. The
43 information, prompts, etc. are made to the kernel by any frontend. The
44 communication on this socket is a sequence of request/reply actions from
44 communication on this socket is a sequence of request/reply actions from
45 each frontend and the kernel.
45 each frontend and the kernel.
46
46
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
49 client over the shell socket and its own requests on the stdin socket. There
49 client over the shell socket and its own requests on the stdin socket. There
50 are a number of actions in Python which generate side effects: :func:`print`
50 are a number of actions in Python which generate side effects: :func:`print`
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
52 a multi-client scenario, we want all frontends to be able to know what each
52 a multi-client scenario, we want all frontends to be able to know what each
53 other has sent to the kernel (this can be useful in collaborative scenarios,
53 other has sent to the kernel (this can be useful in collaborative scenarios,
54 for example). This socket allows both side effects and the information
54 for example). This socket allows both side effects and the information
55 about communications taking place with one client over the shell channel
55 about communications taking place with one client over the shell channel
56 to be made available to all clients in a uniform manner.
56 to be made available to all clients in a uniform manner.
57
57
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
61 for the kernel while this communication is happening (illustrated in the
61 for the kernel while this communication is happening (illustrated in the
62 figure by the black outline around the central keyboard). In practice,
62 figure by the black outline around the central keyboard). In practice,
63 frontends may display such kernel requests using a special input widget or
63 frontends may display such kernel requests using a special input widget or
64 otherwise indicating that the user is to type input for the kernel instead
64 otherwise indicating that the user is to type input for the kernel instead
65 of normal commands in the frontend.
65 of normal commands in the frontend.
66
66
67 All messages are tagged with enough information (details below) for clients
67 All messages are tagged with enough information (details below) for clients
68 to know which messages come from their own interaction with the kernel and
68 to know which messages come from their own interaction with the kernel and
69 which ones are from other clients, so they can display each type
69 which ones are from other clients, so they can display each type
70 appropriately.
70 appropriately.
71
71
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
74
74
75 The actual format of the messages allowed on each of these channels is
75 The actual format of the messages allowed on each of these channels is
76 specified below. Messages are dicts of dicts with string keys and values that
76 specified below. Messages are dicts of dicts with string keys and values that
77 are reasonably representable in JSON. Our current implementation uses JSON
77 are reasonably representable in JSON. Our current implementation uses JSON
78 explicitly as its message format, but this shouldn't be considered a permanent
78 explicitly as its message format, but this shouldn't be considered a permanent
79 feature. As we've discovered that JSON has non-trivial performance issues due
79 feature. As we've discovered that JSON has non-trivial performance issues due
80 to excessive copying, we may in the future move to a pure pickle-based raw
80 to excessive copying, we may in the future move to a pure pickle-based raw
81 message format. However, it should be possible to easily convert from the raw
81 message format. However, it should be possible to easily convert from the raw
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
83 As long as it's easy to make a JSON version of the objects that is a faithful
83 As long as it's easy to make a JSON version of the objects that is a faithful
84 representation of all the data, we can communicate with such clients.
84 representation of all the data, we can communicate with such clients.
85
85
86 .. Note::
86 .. Note::
87
87
88 Not all of these have yet been fully fleshed out, but the key ones are, see
88 Not all of these have yet been fully fleshed out, but the key ones are, see
89 kernel and frontend files for actual implementation details.
89 kernel and frontend files for actual implementation details.
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 A message is defined by the following four-dictionary structure::
94 A message is defined by the following four-dictionary structure::
95
95
96 {
96 {
97 # The message header contains a pair of unique identifiers for the
97 # The message header contains a pair of unique identifiers for the
98 # originating session and the actual message id, in addition to the
98 # originating session and the actual message id, in addition to the
99 # username for the process that generated the message. This is useful in
99 # username for the process that generated the message. This is useful in
100 # collaborative settings where multiple users may be interacting with the
100 # collaborative settings where multiple users may be interacting with the
101 # same kernel simultaneously, so that frontends can label the various
101 # same kernel simultaneously, so that frontends can label the various
102 # messages in a meaningful way.
102 # messages in a meaningful way.
103 'header' : {
103 'header' : {
104 'msg_id' : uuid,
104 'msg_id' : uuid,
105 'username' : str,
105 'username' : str,
106 'session' : uuid,
106 'session' : uuid,
107 # All recognized message type strings are listed below.
107 # All recognized message type strings are listed below.
108 'msg_type' : str,
108 'msg_type' : str,
109 # the message protocol version
109 # the message protocol version
110 'version' : '5.0',
110 'version' : '5.0',
111 },
111 },
112
112
113 # In a chain of messages, the header from the parent is copied so that
113 # In a chain of messages, the header from the parent is copied so that
114 # clients can track where messages come from.
114 # clients can track where messages come from.
115 'parent_header' : dict,
115 'parent_header' : dict,
116
116
117 # Any metadata associated with the message.
117 # Any metadata associated with the message.
118 'metadata' : dict,
118 'metadata' : dict,
119
119
120 # The actual content of the message must be a dict, whose structure
120 # The actual content of the message must be a dict, whose structure
121 # depends on the message type.
121 # depends on the message type.
122 'content' : dict,
122 'content' : dict,
123 }
123 }
124
124
125 .. versionchanged:: 5.0
125 .. versionchanged:: 5.0
126
126
127 ``version`` key added to the header.
127 ``version`` key added to the header.
128
128
129 .. _wire_protocol:
129 .. _wire_protocol:
130
130
131 The Wire Protocol
131 The Wire Protocol
132 =================
132 =================
133
133
134
134
135 This message format exists at a high level,
135 This message format exists at a high level,
136 but does not describe the actual *implementation* at the wire level in zeromq.
136 but does not describe the actual *implementation* at the wire level in zeromq.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
138
138
139 .. note::
139 .. note::
140
140
141 This section should only be relevant to non-Python consumers of the protocol.
141 This section should only be relevant to non-Python consumers of the protocol.
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
143 in the :class:`IPython.kernel.zmq.session.Session` object.
143 in the :class:`IPython.kernel.zmq.session.Session` object.
144
144
145 Every message is serialized to a sequence of at least six blobs of bytes:
145 Every message is serialized to a sequence of at least six blobs of bytes:
146
146
147 .. sourcecode:: python
147 .. sourcecode:: python
148
148
149 [
149 [
150 b'u-u-i-d', # zmq identity(ies)
150 b'u-u-i-d', # zmq identity(ies)
151 b'<IDS|MSG>', # delimiter
151 b'<IDS|MSG>', # delimiter
152 b'baddad42', # HMAC signature
152 b'baddad42', # HMAC signature
153 b'{header}', # serialized header dict
153 b'{header}', # serialized header dict
154 b'{parent_header}', # serialized parent header dict
154 b'{parent_header}', # serialized parent header dict
155 b'{metadata}', # serialized metadata dict
155 b'{metadata}', # serialized metadata dict
156 b'{content}, # serialized content dict
156 b'{content}, # serialized content dict
157 b'blob', # extra raw data buffer(s)
157 b'blob', # extra raw data buffer(s)
158 ...
158 ...
159 ]
159 ]
160
160
161 The front of the message is the ZeroMQ routing prefix,
161 The front of the message is the ZeroMQ routing prefix,
162 which can be zero or more socket identities.
162 which can be zero or more socket identities.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
164 In the case of IOPub, there should be just one prefix component,
164 In the case of IOPub, there should be just one prefix component,
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
166
166
167 .. note::
167 .. note::
168
168
169 In most cases, the IOPub topics are irrelevant and completely ignored,
169 In most cases, the IOPub topics are irrelevant and completely ignored,
170 because frontends just subscribe to all topics.
170 because frontends just subscribe to all topics.
171 The convention used in the IPython kernel is to use the msg_type as the topic,
171 The convention used in the IPython kernel is to use the msg_type as the topic,
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
173
173
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
175 If authentication is disabled, this should be an empty string.
175 If authentication is disabled, this should be an empty string.
176 By default, the hashing function used for computing these signatures is sha256.
176 By default, the hashing function used for computing these signatures is sha256.
177
177
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
179
179
180 .. note::
180 .. note::
181
181
182 To disable authentication and signature checking,
182 To disable authentication and signature checking,
183 set the `key` field of a connection file to an empty string.
183 set the `key` field of a connection file to an empty string.
184
184
185 The signature is the HMAC hex digest of the concatenation of:
185 The signature is the HMAC hex digest of the concatenation of:
186
186
187 - A shared key (typically the ``key`` field of a connection file)
187 - A shared key (typically the ``key`` field of a connection file)
188 - The serialized header dict
188 - The serialized header dict
189 - The serialized parent header dict
189 - The serialized parent header dict
190 - The serialized metadata dict
190 - The serialized metadata dict
191 - The serialized content dict
191 - The serialized content dict
192
192
193 In Python, this is implemented via:
193 In Python, this is implemented via:
194
194
195 .. sourcecode:: python
195 .. sourcecode:: python
196
196
197 # once:
197 # once:
198 digester = HMAC(key, digestmod=hashlib.sha256)
198 digester = HMAC(key, digestmod=hashlib.sha256)
199
199
200 # for each message
200 # for each message
201 d = digester.copy()
201 d = digester.copy()
202 for serialized_dict in (header, parent, metadata, content):
202 for serialized_dict in (header, parent, metadata, content):
203 d.update(serialized_dict)
203 d.update(serialized_dict)
204 signature = d.hexdigest()
204 signature = d.hexdigest()
205
205
206 After the signature is the actual message, always in four frames of bytes.
206 After the signature is the actual message, always in four frames of bytes.
207 The four dictionaries that compose a message are serialized separately,
207 The four dictionaries that compose a message are serialized separately,
208 in the order of header, parent header, metadata, and content.
208 in the order of header, parent header, metadata, and content.
209 These can be serialized by any function that turns a dict into bytes.
209 These can be serialized by any function that turns a dict into bytes.
210 The default and most common serialization is JSON, but msgpack and pickle
210 The default and most common serialization is JSON, but msgpack and pickle
211 are common alternatives.
211 are common alternatives.
212
212
213 After the serialized dicts are zero to many raw data buffers,
213 After the serialized dicts are zero to many raw data buffers,
214 which can be used by message types that support binary data (mainly apply and data_pub).
214 which can be used by message types that support binary data (mainly apply and data_pub).
215
215
216
216
217 Python functional API
217 Python functional API
218 =====================
218 =====================
219
219
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
221 should develop, at a few key points, functional forms of all the requests that
221 should develop, at a few key points, functional forms of all the requests that
222 take arguments in this manner and automatically construct the necessary dict
222 take arguments in this manner and automatically construct the necessary dict
223 for sending.
223 for sending.
224
224
225 In addition, the Python implementation of the message specification extends
225 In addition, the Python implementation of the message specification extends
226 messages upon deserialization to the following form for convenience::
226 messages upon deserialization to the following form for convenience::
227
227
228 {
228 {
229 'header' : dict,
229 'header' : dict,
230 # The msg's unique identifier and type are always stored in the header,
230 # The msg's unique identifier and type are always stored in the header,
231 # but the Python implementation copies them to the top level.
231 # but the Python implementation copies them to the top level.
232 'msg_id' : uuid,
232 'msg_id' : uuid,
233 'msg_type' : str,
233 'msg_type' : str,
234 'parent_header' : dict,
234 'parent_header' : dict,
235 'content' : dict,
235 'content' : dict,
236 'metadata' : dict,
236 'metadata' : dict,
237 }
237 }
238
238
239 All messages sent to or received by any IPython process should have this
239 All messages sent to or received by any IPython process should have this
240 extended structure.
240 extended structure.
241
241
242
242
243 Messages on the shell ROUTER/DEALER sockets
243 Messages on the shell ROUTER/DEALER sockets
244 ===========================================
244 ===========================================
245
245
246 .. _execute:
246 .. _execute:
247
247
248 Execute
248 Execute
249 -------
249 -------
250
250
251 This message type is used by frontends to ask the kernel to execute code on
251 This message type is used by frontends to ask the kernel to execute code on
252 behalf of the user, in a namespace reserved to the user's variables (and thus
252 behalf of the user, in a namespace reserved to the user's variables (and thus
253 separate from the kernel's own internal code and variables).
253 separate from the kernel's own internal code and variables).
254
254
255 Message type: ``execute_request``::
255 Message type: ``execute_request``::
256
256
257 content = {
257 content = {
258 # Source code to be executed by the kernel, one or more lines.
258 # Source code to be executed by the kernel, one or more lines.
259 'code' : str,
259 'code' : str,
260
260
261 # A boolean flag which, if True, signals the kernel to execute
261 # A boolean flag which, if True, signals the kernel to execute
262 # this code as quietly as possible.
262 # this code as quietly as possible.
263 # silent=True forces store_history to be False,
263 # silent=True forces store_history to be False,
264 # and will *not*:
264 # and will *not*:
265 # - broadcast output on the IOPUB channel
265 # - broadcast output on the IOPUB channel
266 # - have an execute_result
266 # - have an execute_result
267 # The default is False.
267 # The default is False.
268 'silent' : bool,
268 'silent' : bool,
269
269
270 # A boolean flag which, if True, signals the kernel to populate history
270 # A boolean flag which, if True, signals the kernel to populate history
271 # The default is True if silent is False. If silent is True, store_history
271 # The default is True if silent is False. If silent is True, store_history
272 # is forced to be False.
272 # is forced to be False.
273 'store_history' : bool,
273 'store_history' : bool,
274
274
275 # A dict mapping names to expressions to be evaluated in the
275 # A dict mapping names to expressions to be evaluated in the
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
277 # See the display_data content for the structure of the representation data.
277 # See the display_data content for the structure of the representation data.
278 'user_expressions' : dict,
278 'user_expressions' : dict,
279
279
280 # Some frontends do not support stdin requests.
280 # Some frontends do not support stdin requests.
281 # If raw_input is called from code executed from such a frontend,
281 # If raw_input is called from code executed from such a frontend,
282 # a StdinNotImplementedError will be raised.
282 # a StdinNotImplementedError will be raised.
283 'allow_stdin' : True,
283 'allow_stdin' : True,
284 }
284 }
285
285
286 .. versionchanged:: 5.0
286 .. versionchanged:: 5.0
287
287
288 ``user_variables`` removed, because it is redundant with user_expressions.
288 ``user_variables`` removed, because it is redundant with user_expressions.
289
289
290 The ``code`` field contains a single string (possibly multiline) to be executed.
290 The ``code`` field contains a single string (possibly multiline) to be executed.
291
291
292 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
292 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
293 the notion of a prompt string that allowed arbitrary code to be evaluated, and
293 the notion of a prompt string that allowed arbitrary code to be evaluated, and
294 this was put to good use by many in creating prompts that displayed system
294 this was put to good use by many in creating prompts that displayed system
295 status, path information, and even more esoteric uses like remote instrument
295 status, path information, and even more esoteric uses like remote instrument
296 status acquired over the network. But now that IPython has a clean separation
296 status acquired over the network. But now that IPython has a clean separation
297 between the kernel and the clients, the kernel has no prompt knowledge; prompts
297 between the kernel and the clients, the kernel has no prompt knowledge; prompts
298 are a frontend feature, and it should be even possible for different
298 are a frontend feature, and it should be even possible for different
299 frontends to display different prompts while interacting with the same kernel.
299 frontends to display different prompts while interacting with the same kernel.
300 ``user_expressions`` can be used to retrieve this information.
300 ``user_expressions`` can be used to retrieve this information.
301
301
302 Any error in evaluating any expression in ``user_expressions`` will result in
302 Any error in evaluating any expression in ``user_expressions`` will result in
303 only that key containing a standard error message, of the form::
303 only that key containing a standard error message, of the form::
304
304
305 {
305 {
306 'status' : 'error',
306 'status' : 'error',
307 'ename' : 'NameError',
307 'ename' : 'NameError',
308 'evalue' : 'foo',
308 'evalue' : 'foo',
309 'traceback' : ...
309 'traceback' : ...
310 }
310 }
311
311
312 .. Note::
312 .. Note::
313
313
314 In order to obtain the current execution counter for the purposes of
314 In order to obtain the current execution counter for the purposes of
315 displaying input prompts, frontends may make an execution request with an
315 displaying input prompts, frontends may make an execution request with an
316 empty code string and ``silent=True``.
316 empty code string and ``silent=True``.
317
317
318 Upon completion of the execution request, the kernel *always* sends a reply,
318 Upon completion of the execution request, the kernel *always* sends a reply,
319 with a status code indicating what happened and additional data depending on
319 with a status code indicating what happened and additional data depending on
320 the outcome. See :ref:`below <execution_results>` for the possible return
320 the outcome. See :ref:`below <execution_results>` for the possible return
321 codes and associated data.
321 codes and associated data.
322
322
323 .. seealso::
323 .. seealso::
324
324
325 :ref:`execution_semantics`
325 :ref:`execution_semantics`
326
326
327 .. _execution_counter:
327 .. _execution_counter:
328
328
329 Execution counter (prompt number)
329 Execution counter (prompt number)
330 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
330 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331
331
332 The kernel should have a single, monotonically increasing counter of all execution
332 The kernel should have a single, monotonically increasing counter of all execution
333 requests that are made with ``store_history=True``. This counter is used to populate
333 requests that are made with ``store_history=True``. This counter is used to populate
334 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
334 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
335 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
335 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
336
336
337 .. _execution_results:
337 .. _execution_results:
338
338
339 Execution results
339 Execution results
340 ~~~~~~~~~~~~~~~~~
340 ~~~~~~~~~~~~~~~~~
341
341
342 Message type: ``execute_reply``::
342 Message type: ``execute_reply``::
343
343
344 content = {
344 content = {
345 # One of: 'ok' OR 'error' OR 'abort'
345 # One of: 'ok' OR 'error' OR 'abort'
346 'status' : str,
346 'status' : str,
347
347
348 # The global kernel counter that increases by one with each request that
348 # The global kernel counter that increases by one with each request that
349 # stores history. This will typically be used by clients to display
349 # stores history. This will typically be used by clients to display
350 # prompt numbers to the user. If the request did not store history, this will
350 # prompt numbers to the user. If the request did not store history, this will
351 # be the current value of the counter in the kernel.
351 # be the current value of the counter in the kernel.
352 'execution_count' : int,
352 'execution_count' : int,
353 }
353 }
354
354
355 When status is 'ok', the following extra fields are present::
355 When status is 'ok', the following extra fields are present::
356
356
357 {
357 {
358 # 'payload' will be a list of payload dicts, and is optional.
358 # 'payload' will be a list of payload dicts, and is optional.
359 # payloads are considered deprecated.
359 # payloads are considered deprecated.
360 # The only requirement of each payload dict is that it have a 'source' key,
360 # The only requirement of each payload dict is that it have a 'source' key,
361 # which is a string classifying the payload (e.g. 'page').
361 # which is a string classifying the payload (e.g. 'page').
362
362
363 'payload' : list(dict),
363 'payload' : list(dict),
364
364
365 # Results for the user_expressions.
365 # Results for the user_expressions.
366 'user_expressions' : dict,
366 'user_expressions' : dict,
367 }
367 }
368
368
369 .. versionchanged:: 5.0
369 .. versionchanged:: 5.0
370
370
371 ``user_variables`` is removed, use user_expressions instead.
371 ``user_variables`` is removed, use user_expressions instead.
372
372
373 When status is 'error', the following extra fields are present::
373 When status is 'error', the following extra fields are present::
374
374
375 {
375 {
376 'ename' : str, # Exception name, as a string
376 'ename' : str, # Exception name, as a string
377 'evalue' : str, # Exception value, as a string
377 'evalue' : str, # Exception value, as a string
378
378
379 # The traceback will contain a list of frames, represented each as a
379 # The traceback will contain a list of frames, represented each as a
380 # string. For now we'll stick to the existing design of ultraTB, which
380 # string. For now we'll stick to the existing design of ultraTB, which
381 # controls exception level of detail statefully. But eventually we'll
381 # controls exception level of detail statefully. But eventually we'll
382 # want to grow into a model where more information is collected and
382 # want to grow into a model where more information is collected and
383 # packed into the traceback object, with clients deciding how little or
383 # packed into the traceback object, with clients deciding how little or
384 # how much of it to unpack. But for now, let's start with a simple list
384 # how much of it to unpack. But for now, let's start with a simple list
385 # of strings, since that requires only minimal changes to ultratb as
385 # of strings, since that requires only minimal changes to ultratb as
386 # written.
386 # written.
387 'traceback' : list,
387 'traceback' : list,
388 }
388 }
389
389
390
390
391 When status is 'abort', there are for now no additional data fields. This
391 When status is 'abort', there are for now no additional data fields. This
392 happens when the kernel was interrupted by a signal.
392 happens when the kernel was interrupted by a signal.
393
393
394 Payloads
394 Payloads
395 ********
395 ********
396
396
397 .. admonition:: Execution payloads
397 .. admonition:: Execution payloads
398
398
399 Payloads are considered deprecated, though their replacement is not yet implemented.
399 Payloads are considered deprecated, though their replacement is not yet implemented.
400
400
401 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
401 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
402
402
403 **page**: display data in a pager.
403 **page**: display data in a pager.
404
404
405 Pager output is used for introspection, or other displayed information that's not considered output.
405 Pager output is used for introspection, or other displayed information that's not considered output.
406 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
406 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
407 and are not included in notebook documents.
407 and are not included in notebook documents.
408
408
409 .. sourcecode:: python
409 .. sourcecode:: python
410
410
411 {
411 {
412 "source": "page",
412 "source": "page",
413 # mime-bundle of data to display in the pager.
413 # mime-bundle of data to display in the pager.
414 # Must include text/plain.
414 # Must include text/plain.
415 "data": mimebundle,
415 "data": mimebundle,
416 # line offset to start from
416 # line offset to start from
417 "start": int,
417 "start": int,
418 }
418 }
419
419
420 **set_next_input**: create a new output
420 **set_next_input**: create a new output
421
421
422 used to create new cells in the notebook,
422 used to create new cells in the notebook,
423 or set the next input in a console interface.
423 or set the next input in a console interface.
424 The main example being ``%load``.
424 The main example being ``%load``.
425
425
426 .. sourcecode:: python
426 .. sourcecode:: python
427
427
428 {
428 {
429 "source": "set_next_input",
429 "source": "set_next_input",
430 # the text contents of the cell to create
430 # the text contents of the cell to create
431 "text": "some cell content",
431 "text": "some cell content",
432 }
432 }
433
433
434 **edit**: open a file for editing.
434 **edit**: open a file for editing.
435
435
436 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
436 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
437
437
438 .. sourcecode:: python
438 .. sourcecode:: python
439
439
440 {
440 {
441 "source": "edit",
441 "source": "edit",
442 "filename": "/path/to/file.py", # the file to edit
442 "filename": "/path/to/file.py", # the file to edit
443 "line_number": int, # the line number to start with
443 "line_number": int, # the line number to start with
444 }
444 }
445
445
446 **ask_exit**: instruct the frontend to prompt the user for exit
446 **ask_exit**: instruct the frontend to prompt the user for exit
447
447
448 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
448 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
449 Only for console frontends.
449 Only for console frontends.
450
450
451 .. sourcecode:: python
451 .. sourcecode:: python
452
452
453 {
453 {
454 "source": "ask_exit",
454 "source": "ask_exit",
455 # whether the kernel should be left running, only closing the client
455 # whether the kernel should be left running, only closing the client
456 "keepkernel": bool,
456 "keepkernel": bool,
457 }
457 }
458
458
459
459
460 .. _msging_inspection:
460 .. _msging_inspection:
461
461
462 Introspection
462 Introspection
463 -------------
463 -------------
464
464
465 Code can be inspected to show useful information to the user.
465 Code can be inspected to show useful information to the user.
466 It is up to the Kernel to decide what information should be displayed, and its formatting.
466 It is up to the Kernel to decide what information should be displayed, and its formatting.
467
467
468 Message type: ``inspect_request``::
468 Message type: ``inspect_request``::
469
469
470 content = {
470 content = {
471 # The code context in which introspection is requested
471 # The code context in which introspection is requested
472 # this may be up to an entire multiline cell.
472 # this may be up to an entire multiline cell.
473 'code' : str,
473 'code' : str,
474
474
475 # The cursor position within 'code' (in unicode characters) where inspection is requested
475 # The cursor position within 'code' (in unicode characters) where inspection is requested
476 'cursor_pos' : int,
476 'cursor_pos' : int,
477
477
478 # The level of detail desired. In IPython, the default (0) is equivalent to typing
478 # The level of detail desired. In IPython, the default (0) is equivalent to typing
479 # 'x?' at the prompt, 1 is equivalent to 'x??'.
479 # 'x?' at the prompt, 1 is equivalent to 'x??'.
480 # The difference is up to kernels, but in IPython level 1 includes the source code
480 # The difference is up to kernels, but in IPython level 1 includes the source code
481 # if available.
481 # if available.
482 'detail_level' : 0 or 1,
482 'detail_level' : 0 or 1,
483 }
483 }
484
484
485 .. versionchanged:: 5.0
485 .. versionchanged:: 5.0
486
486
487 ``object_info_request`` renamed to ``inspect_request``.
487 ``object_info_request`` renamed to ``inspect_request``.
488
488
489 .. versionchanged:: 5.0
489 .. versionchanged:: 5.0
490
490
491 ``name`` key replaced with ``code`` and ``cursor_pos``,
491 ``name`` key replaced with ``code`` and ``cursor_pos``,
492 moving the lexing responsibility to the kernel.
492 moving the lexing responsibility to the kernel.
493
493
494 The reply is a mime-bundle, like a `display_data`_ message,
494 The reply is a mime-bundle, like a `display_data`_ message,
495 which should be a formatted representation of information about the context.
495 which should be a formatted representation of information about the context.
496 In the notebook, this is used to show tooltips over function calls, etc.
496 In the notebook, this is used to show tooltips over function calls, etc.
497
497
498 Message type: ``inspect_reply``::
498 Message type: ``inspect_reply``::
499
499
500 content = {
500 content = {
501 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
501 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
502 'status' : 'ok',
502 'status' : 'ok',
503
503
504 # data can be empty if nothing is found
504 # data can be empty if nothing is found
505 'data' : dict,
505 'data' : dict,
506 'metadata' : dict,
506 'metadata' : dict,
507 }
507 }
508
508
509 .. versionchanged:: 5.0
509 .. versionchanged:: 5.0
510
510
511 ``object_info_reply`` renamed to ``inspect_reply``.
511 ``object_info_reply`` renamed to ``inspect_reply``.
512
512
513 .. versionchanged:: 5.0
513 .. versionchanged:: 5.0
514
514
515 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
515 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
516
516
517 .. _msging_completion:
517 .. _msging_completion:
518
518
519 Completion
519 Completion
520 ----------
520 ----------
521
521
522 Message type: ``complete_request``::
522 Message type: ``complete_request``::
523
523
524 content = {
524 content = {
525 # The code context in which completion is requested
525 # The code context in which completion is requested
526 # this may be up to an entire multiline cell, such as
526 # this may be up to an entire multiline cell, such as
527 # 'foo = a.isal'
527 # 'foo = a.isal'
528 'code' : str,
528 'code' : str,
529
529
530 # The cursor position within 'code' (in unicode characters) where completion is requested
530 # The cursor position within 'code' (in unicode characters) where completion is requested
531 'cursor_pos' : int,
531 'cursor_pos' : int,
532 }
532 }
533
533
534 .. versionchanged:: 5.0
534 .. versionchanged:: 5.0
535
535
536 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
536 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
537 Lexing is up to the kernel.
537 Lexing is up to the kernel.
538
538
539
539
540 Message type: ``complete_reply``::
540 Message type: ``complete_reply``::
541
541
542 content = {
542 content = {
543 # The list of all matches to the completion request, such as
543 # The list of all matches to the completion request, such as
544 # ['a.isalnum', 'a.isalpha'] for the above example.
544 # ['a.isalnum', 'a.isalpha'] for the above example.
545 'matches' : list,
545 'matches' : list,
546
546
547 # The range of text that should be replaced by the above matches when a completion is accepted.
547 # The range of text that should be replaced by the above matches when a completion is accepted.
548 # typically cursor_end is the same as cursor_pos in the request.
548 # typically cursor_end is the same as cursor_pos in the request.
549 'cursor_start' : int,
549 'cursor_start' : int,
550 'cursor_end' : int,
550 'cursor_end' : int,
551
551
552 # Information that frontend plugins might use for extra display information about completions.
552 # Information that frontend plugins might use for extra display information about completions.
553 'metadata' : dict,
553 'metadata' : dict,
554
554
555 # status should be 'ok' unless an exception was raised during the request,
555 # status should be 'ok' unless an exception was raised during the request,
556 # in which case it should be 'error', along with the usual error message content
556 # in which case it should be 'error', along with the usual error message content
557 # in other messages.
557 # in other messages.
558 'status' : 'ok'
558 'status' : 'ok'
559 }
559 }
560
560
561 .. versionchanged:: 5.0
561 .. versionchanged:: 5.0
562
562
563 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
563 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
564 - ``metadata`` is added for extended information.
564 - ``metadata`` is added for extended information.
565
565
566 .. _msging_history:
566 .. _msging_history:
567
567
568 History
568 History
569 -------
569 -------
570
570
571 For clients to explicitly request history from a kernel. The kernel has all
571 For clients to explicitly request history from a kernel. The kernel has all
572 the actual execution history stored in a single location, so clients can
572 the actual execution history stored in a single location, so clients can
573 request it from the kernel when needed.
573 request it from the kernel when needed.
574
574
575 Message type: ``history_request``::
575 Message type: ``history_request``::
576
576
577 content = {
577 content = {
578
578
579 # If True, also return output history in the resulting dict.
579 # If True, also return output history in the resulting dict.
580 'output' : bool,
580 'output' : bool,
581
581
582 # If True, return the raw input history, else the transformed input.
582 # If True, return the raw input history, else the transformed input.
583 'raw' : bool,
583 'raw' : bool,
584
584
585 # So far, this can be 'range', 'tail' or 'search'.
585 # So far, this can be 'range', 'tail' or 'search'.
586 'hist_access_type' : str,
586 'hist_access_type' : str,
587
587
588 # If hist_access_type is 'range', get a range of input cells. session can
588 # If hist_access_type is 'range', get a range of input cells. session can
589 # be a positive session number, or a negative number to count back from
589 # be a positive session number, or a negative number to count back from
590 # the current session.
590 # the current session.
591 'session' : int,
591 'session' : int,
592 # start and stop are line numbers within that session.
592 # start and stop are line numbers within that session.
593 'start' : int,
593 'start' : int,
594 'stop' : int,
594 'stop' : int,
595
595
596 # If hist_access_type is 'tail' or 'search', get the last n cells.
596 # If hist_access_type is 'tail' or 'search', get the last n cells.
597 'n' : int,
597 'n' : int,
598
598
599 # If hist_access_type is 'search', get cells matching the specified glob
599 # If hist_access_type is 'search', get cells matching the specified glob
600 # pattern (with * and ? as wildcards).
600 # pattern (with * and ? as wildcards).
601 'pattern' : str,
601 'pattern' : str,
602
602
603 # If hist_access_type is 'search' and unique is true, do not
603 # If hist_access_type is 'search' and unique is true, do not
604 # include duplicated history. Default is false.
604 # include duplicated history. Default is false.
605 'unique' : bool,
605 'unique' : bool,
606
606
607 }
607 }
608
608
609 .. versionadded:: 4.0
609 .. versionadded:: 4.0
610 The key ``unique`` for ``history_request``.
610 The key ``unique`` for ``history_request``.
611
611
612 Message type: ``history_reply``::
612 Message type: ``history_reply``::
613
613
614 content = {
614 content = {
615 # A list of 3 tuples, either:
615 # A list of 3 tuples, either:
616 # (session, line_number, input) or
616 # (session, line_number, input) or
617 # (session, line_number, (input, output)),
617 # (session, line_number, (input, output)),
618 # depending on whether output was False or True, respectively.
618 # depending on whether output was False or True, respectively.
619 'history' : list,
619 'history' : list,
620 }
620 }
621
621
622 .. _msging_is_complete:
622 .. _msging_is_complete:
623
623
624 Code completeness
624 Code completeness
625 -----------------
625 -----------------
626
626
627 .. versionadded:: 5.0
627 .. versionadded:: 5.0
628
628
629 When the user enters a line in a console style interface, the console must
629 When the user enters a line in a console style interface, the console must
630 decide whether to immediately execute the current code, or whether to show a
630 decide whether to immediately execute the current code, or whether to show a
631 continuation prompt for further input. For instance, in Python ``a = 5`` would
631 continuation prompt for further input. For instance, in Python ``a = 5`` would
632 be executed immediately, while ``for i in range(5):`` would expect further input.
632 be executed immediately, while ``for i in range(5):`` would expect further input.
633
633
634 There are four possible replies:
634 There are four possible replies:
635
635
636 - *complete* code is ready to be executed
636 - *complete* code is ready to be executed
637 - *incomplete* code should prompt for another line
637 - *incomplete* code should prompt for another line
638 - *invalid* code will typically be sent for execution, so that the user sees the
638 - *invalid* code will typically be sent for execution, so that the user sees the
639 error soonest.
639 error soonest.
640 - *unknown* - if the kernel is not able to determine this. The frontend should
640 - *unknown* - if the kernel is not able to determine this. The frontend should
641 also handle the kernel not replying promptly. It may default to sending the
641 also handle the kernel not replying promptly. It may default to sending the
642 code for execution, or it may implement simple fallback heuristics for whether
642 code for execution, or it may implement simple fallback heuristics for whether
643 to execute the code (e.g. execute after a blank line).
643 to execute the code (e.g. execute after a blank line).
644
644
645 Frontends may have ways to override this, forcing the code to be sent for
645 Frontends may have ways to override this, forcing the code to be sent for
646 execution or forcing a continuation prompt.
646 execution or forcing a continuation prompt.
647
647
648 Message type: ``is_complete_request``::
648 Message type: ``is_complete_request``::
649
649
650 content = {
650 content = {
651 # The code entered so far as a multiline string
651 # The code entered so far as a multiline string
652 'code' : str,
652 'code' : str,
653 }
653 }
654
654
655 Message type: ``is_complete_reply``::
655 Message type: ``is_complete_reply``::
656
656
657 content = {
657 content = {
658 # One of 'complete', 'incomplete', 'invalid', 'unknown'
658 # One of 'complete', 'incomplete', 'invalid', 'unknown'
659 'status' : str,
659 'status' : str,
660
660
661 # If status is 'incomplete', indent should contain the characters to use
661 # If status is 'incomplete', indent should contain the characters to use
662 # to indent the next line. This is only a hint: frontends may ignore it
662 # to indent the next line. This is only a hint: frontends may ignore it
663 # and use their own autoindentation rules. For other statuses, this
663 # and use their own autoindentation rules. For other statuses, this
664 # field does not exist.
664 # field does not exist.
665 'indent': str,
665 'indent': str,
666 }
666 }
667
667
668 Connect
668 Connect
669 -------
669 -------
670
670
671 When a client connects to the request/reply socket of the kernel, it can issue
671 When a client connects to the request/reply socket of the kernel, it can issue
672 a connect request to get basic information about the kernel, such as the ports
672 a connect request to get basic information about the kernel, such as the ports
673 the other ZeroMQ sockets are listening on. This allows clients to only have
673 the other ZeroMQ sockets are listening on. This allows clients to only have
674 to know about a single port (the shell channel) to connect to a kernel.
674 to know about a single port (the shell channel) to connect to a kernel.
675
675
676 Message type: ``connect_request``::
676 Message type: ``connect_request``::
677
677
678 content = {
678 content = {
679 }
679 }
680
680
681 Message type: ``connect_reply``::
681 Message type: ``connect_reply``::
682
682
683 content = {
683 content = {
684 'shell_port' : int, # The port the shell ROUTER socket is listening on.
684 'shell_port' : int, # The port the shell ROUTER socket is listening on.
685 'iopub_port' : int, # The port the PUB socket is listening on.
685 'iopub_port' : int, # The port the PUB socket is listening on.
686 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
686 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
687 'hb_port' : int, # The port the heartbeat socket is listening on.
687 'hb_port' : int, # The port the heartbeat socket is listening on.
688 }
688 }
689
689
690 .. _msging_kernel_info:
690 .. _msging_kernel_info:
691
691
692 Kernel info
692 Kernel info
693 -----------
693 -----------
694
694
695 If a client needs to know information about the kernel, it can
695 If a client needs to know information about the kernel, it can
696 make a request of the kernel's information.
696 make a request of the kernel's information.
697 This message can be used to fetch core information of the
697 This message can be used to fetch core information of the
698 kernel, including language (e.g., Python), language version number and
698 kernel, including language (e.g., Python), language version number and
699 IPython version number, and the IPython message spec version number.
699 IPython version number, and the IPython message spec version number.
700
700
701 Message type: ``kernel_info_request``::
701 Message type: ``kernel_info_request``::
702
702
703 content = {
703 content = {
704 }
704 }
705
705
706 Message type: ``kernel_info_reply``::
706 Message type: ``kernel_info_reply``::
707
707
708 content = {
708 content = {
709 # Version of messaging protocol.
709 # Version of messaging protocol.
710 # The first integer indicates major version. It is incremented when
710 # The first integer indicates major version. It is incremented when
711 # there is any backward incompatible change.
711 # there is any backward incompatible change.
712 # The second integer indicates minor version. It is incremented when
712 # The second integer indicates minor version. It is incremented when
713 # there is any backward compatible change.
713 # there is any backward compatible change.
714 'protocol_version': 'X.Y.Z',
714 'protocol_version': 'X.Y.Z',
715
715
716 # The kernel implementation name
716 # The kernel implementation name
717 # (e.g. 'ipython' for the IPython kernel)
717 # (e.g. 'ipython' for the IPython kernel)
718 'implementation': str,
718 'implementation': str,
719
719
720 # Implementation version number.
720 # Implementation version number.
721 # The version number of the kernel's implementation
721 # The version number of the kernel's implementation
722 # (e.g. IPython.__version__ for the IPython kernel)
722 # (e.g. IPython.__version__ for the IPython kernel)
723 'implementation_version': 'X.Y.Z',
723 'implementation_version': 'X.Y.Z',
724
724
725 # Programming language in which kernel is implemented.
725 # Programming language in which kernel is implemented.
726 # Kernel included in IPython returns 'python'.
726 # Kernel included in IPython returns 'python'.
727 'language': str,
727 'language': str,
728
728
729 # Language version number.
729 # Language version number.
730 # It is Python version number (e.g., '2.7.3') for the kernel
730 # It is Python version number (e.g., '2.7.3') for the kernel
731 # included in IPython.
731 # included in IPython.
732 'language_version': 'X.Y.Z',
732 'language_version': 'X.Y.Z',
733
733
734 # Information about the language of code for the kernel
734 # Information about the language of code for the kernel
735 'language_info': {
735 'language_info': {
736 'mimetype': str,
736 'mimetype': str,
737
737
738 # Extension without the dot, e.g. 'py'
739 'file_extension': str,
740
738 # Pygments lexer, for highlighting
741 # Pygments lexer, for highlighting
739 # Only needed if it differs from the top level 'language' field.
742 # Only needed if it differs from the top level 'language' field.
740 'pygments_lexer': str,
743 'pygments_lexer': str,
741
744
742 # Codemirror mode, for for highlighting in the notebook.
745 # Codemirror mode, for for highlighting in the notebook.
743 # Only needed if it differs from the top level 'language' field.
746 # Only needed if it differs from the top level 'language' field.
744 'codemirror_mode': str or dict,
747 'codemirror_mode': str or dict,
748
749 # Nbconvert exporter, if notebooks written with this kernel should
750 # be exported with something other than the general 'script'
751 # exporter.
752 'nbconvert_exporter': str,
745 },
753 },
746
754
747 # A banner of information about the kernel,
755 # A banner of information about the kernel,
748 # which may be desplayed in console environments.
756 # which may be desplayed in console environments.
749 'banner' : str,
757 'banner' : str,
750
758
751 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
759 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
752 # These will be displayed in the help menu in the notebook UI.
760 # These will be displayed in the help menu in the notebook UI.
753 'help_links': [
761 'help_links': [
754 {'text': str, 'url': str}
762 {'text': str, 'url': str}
755 ],
763 ],
756 }
764 }
757
765
758 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
766 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
759 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
767 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
760
768
761 .. versionchanged:: 5.0
769 .. versionchanged:: 5.0
762
770
763 Versions changed from lists of integers to strings.
771 Versions changed from lists of integers to strings.
764
772
765 .. versionchanged:: 5.0
773 .. versionchanged:: 5.0
766
774
767 ``ipython_version`` is removed.
775 ``ipython_version`` is removed.
768
776
769 .. versionchanged:: 5.0
777 .. versionchanged:: 5.0
770
778
771 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
779 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
772 and ``help_links`` keys are added.
780 and ``help_links`` keys are added.
773
781
774 .. _msging_shutdown:
782 .. _msging_shutdown:
775
783
776 Kernel shutdown
784 Kernel shutdown
777 ---------------
785 ---------------
778
786
779 The clients can request the kernel to shut itself down; this is used in
787 The clients can request the kernel to shut itself down; this is used in
780 multiple cases:
788 multiple cases:
781
789
782 - when the user chooses to close the client application via a menu or window
790 - when the user chooses to close the client application via a menu or window
783 control.
791 control.
784 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
792 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
785 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
793 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
786 IPythonQt client) to force a kernel restart to get a clean kernel without
794 IPythonQt client) to force a kernel restart to get a clean kernel without
787 losing client-side state like history or inlined figures.
795 losing client-side state like history or inlined figures.
788
796
789 The client sends a shutdown request to the kernel, and once it receives the
797 The client sends a shutdown request to the kernel, and once it receives the
790 reply message (which is otherwise empty), it can assume that the kernel has
798 reply message (which is otherwise empty), it can assume that the kernel has
791 completed shutdown safely.
799 completed shutdown safely.
792
800
793 Upon their own shutdown, client applications will typically execute a last
801 Upon their own shutdown, client applications will typically execute a last
794 minute sanity check and forcefully terminate any kernel that is still alive, to
802 minute sanity check and forcefully terminate any kernel that is still alive, to
795 avoid leaving stray processes in the user's machine.
803 avoid leaving stray processes in the user's machine.
796
804
797 Message type: ``shutdown_request``::
805 Message type: ``shutdown_request``::
798
806
799 content = {
807 content = {
800 'restart' : bool # whether the shutdown is final, or precedes a restart
808 'restart' : bool # whether the shutdown is final, or precedes a restart
801 }
809 }
802
810
803 Message type: ``shutdown_reply``::
811 Message type: ``shutdown_reply``::
804
812
805 content = {
813 content = {
806 'restart' : bool # whether the shutdown is final, or precedes a restart
814 'restart' : bool # whether the shutdown is final, or precedes a restart
807 }
815 }
808
816
809 .. Note::
817 .. Note::
810
818
811 When the clients detect a dead kernel thanks to inactivity on the heartbeat
819 When the clients detect a dead kernel thanks to inactivity on the heartbeat
812 socket, they simply send a forceful process termination signal, since a dead
820 socket, they simply send a forceful process termination signal, since a dead
813 process is unlikely to respond in any useful way to messages.
821 process is unlikely to respond in any useful way to messages.
814
822
815
823
816 Messages on the PUB/SUB socket
824 Messages on the PUB/SUB socket
817 ==============================
825 ==============================
818
826
819 Streams (stdout, stderr, etc)
827 Streams (stdout, stderr, etc)
820 ------------------------------
828 ------------------------------
821
829
822 Message type: ``stream``::
830 Message type: ``stream``::
823
831
824 content = {
832 content = {
825 # The name of the stream is one of 'stdout', 'stderr'
833 # The name of the stream is one of 'stdout', 'stderr'
826 'name' : str,
834 'name' : str,
827
835
828 # The text is an arbitrary string to be written to that stream
836 # The text is an arbitrary string to be written to that stream
829 'text' : str,
837 'text' : str,
830 }
838 }
831
839
832 .. versionchanged:: 5.0
840 .. versionchanged:: 5.0
833
841
834 'data' key renamed to 'text' for conistency with the notebook format.
842 'data' key renamed to 'text' for conistency with the notebook format.
835
843
836 Display Data
844 Display Data
837 ------------
845 ------------
838
846
839 This type of message is used to bring back data that should be displayed (text,
847 This type of message is used to bring back data that should be displayed (text,
840 html, svg, etc.) in the frontends. This data is published to all frontends.
848 html, svg, etc.) in the frontends. This data is published to all frontends.
841 Each message can have multiple representations of the data; it is up to the
849 Each message can have multiple representations of the data; it is up to the
842 frontend to decide which to use and how. A single message should contain all
850 frontend to decide which to use and how. A single message should contain all
843 possible representations of the same information. Each representation should
851 possible representations of the same information. Each representation should
844 be a JSON'able data structure, and should be a valid MIME type.
852 be a JSON'able data structure, and should be a valid MIME type.
845
853
846 Some questions remain about this design:
854 Some questions remain about this design:
847
855
848 * Do we use this message type for execute_result/displayhook? Probably not, because
856 * Do we use this message type for execute_result/displayhook? Probably not, because
849 the displayhook also has to handle the Out prompt display. On the other hand
857 the displayhook also has to handle the Out prompt display. On the other hand
850 we could put that information into the metadata section.
858 we could put that information into the metadata section.
851
859
852 .. _display_data:
860 .. _display_data:
853
861
854 Message type: ``display_data``::
862 Message type: ``display_data``::
855
863
856 content = {
864 content = {
857
865
858 # Who create the data
866 # Who create the data
859 'source' : str,
867 'source' : str,
860
868
861 # The data dict contains key/value pairs, where the keys are MIME
869 # The data dict contains key/value pairs, where the keys are MIME
862 # types and the values are the raw data of the representation in that
870 # types and the values are the raw data of the representation in that
863 # format.
871 # format.
864 'data' : dict,
872 'data' : dict,
865
873
866 # Any metadata that describes the data
874 # Any metadata that describes the data
867 'metadata' : dict
875 'metadata' : dict
868 }
876 }
869
877
870
878
871 The ``metadata`` contains any metadata that describes the output.
879 The ``metadata`` contains any metadata that describes the output.
872 Global keys are assumed to apply to the output as a whole.
880 Global keys are assumed to apply to the output as a whole.
873 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
881 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
874 which are interpreted as applying only to output of that type.
882 which are interpreted as applying only to output of that type.
875 Third parties should put any data they write into a single dict
883 Third parties should put any data they write into a single dict
876 with a reasonably unique name to avoid conflicts.
884 with a reasonably unique name to avoid conflicts.
877
885
878 The only metadata keys currently defined in IPython are the width and height
886 The only metadata keys currently defined in IPython are the width and height
879 of images::
887 of images::
880
888
881 metadata = {
889 metadata = {
882 'image/png' : {
890 'image/png' : {
883 'width': 640,
891 'width': 640,
884 'height': 480
892 'height': 480
885 }
893 }
886 }
894 }
887
895
888
896
889 .. versionchanged:: 5.0
897 .. versionchanged:: 5.0
890
898
891 `application/json` data should be unpacked JSON data,
899 `application/json` data should be unpacked JSON data,
892 not double-serialized as a JSON string.
900 not double-serialized as a JSON string.
893
901
894
902
895 Raw Data Publication
903 Raw Data Publication
896 --------------------
904 --------------------
897
905
898 ``display_data`` lets you publish *representations* of data, such as images and html.
906 ``display_data`` lets you publish *representations* of data, such as images and html.
899 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
907 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
900
908
901 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
909 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
902
910
903 .. sourcecode:: python
911 .. sourcecode:: python
904
912
905 from IPython.kernel.zmq.datapub import publish_data
913 from IPython.kernel.zmq.datapub import publish_data
906 ns = dict(x=my_array)
914 ns = dict(x=my_array)
907 publish_data(ns)
915 publish_data(ns)
908
916
909
917
910 Message type: ``data_pub``::
918 Message type: ``data_pub``::
911
919
912 content = {
920 content = {
913 # the keys of the data dict, after it has been unserialized
921 # the keys of the data dict, after it has been unserialized
914 'keys' : ['a', 'b']
922 'keys' : ['a', 'b']
915 }
923 }
916 # the namespace dict will be serialized in the message buffers,
924 # the namespace dict will be serialized in the message buffers,
917 # which will have a length of at least one
925 # which will have a length of at least one
918 buffers = [b'pdict', ...]
926 buffers = [b'pdict', ...]
919
927
920
928
921 The interpretation of a sequence of data_pub messages for a given parent request should be
929 The interpretation of a sequence of data_pub messages for a given parent request should be
922 to update a single namespace with subsequent results.
930 to update a single namespace with subsequent results.
923
931
924 .. note::
932 .. note::
925
933
926 No frontends directly handle data_pub messages at this time.
934 No frontends directly handle data_pub messages at this time.
927 It is currently only used by the client/engines in :mod:`IPython.parallel`,
935 It is currently only used by the client/engines in :mod:`IPython.parallel`,
928 where engines may publish *data* to the Client,
936 where engines may publish *data* to the Client,
929 of which the Client can then publish *representations* via ``display_data``
937 of which the Client can then publish *representations* via ``display_data``
930 to various frontends.
938 to various frontends.
931
939
932 Code inputs
940 Code inputs
933 -----------
941 -----------
934
942
935 To let all frontends know what code is being executed at any given time, these
943 To let all frontends know what code is being executed at any given time, these
936 messages contain a re-broadcast of the ``code`` portion of an
944 messages contain a re-broadcast of the ``code`` portion of an
937 :ref:`execute_request <execute>`, along with the :ref:`execution_count
945 :ref:`execute_request <execute>`, along with the :ref:`execution_count
938 <execution_counter>`.
946 <execution_counter>`.
939
947
940 Message type: ``execute_input``::
948 Message type: ``execute_input``::
941
949
942 content = {
950 content = {
943 'code' : str, # Source code to be executed, one or more lines
951 'code' : str, # Source code to be executed, one or more lines
944
952
945 # The counter for this execution is also provided so that clients can
953 # The counter for this execution is also provided so that clients can
946 # display it, since IPython automatically creates variables called _iN
954 # display it, since IPython automatically creates variables called _iN
947 # (for input prompt In[N]).
955 # (for input prompt In[N]).
948 'execution_count' : int
956 'execution_count' : int
949 }
957 }
950
958
951 .. versionchanged:: 5.0
959 .. versionchanged:: 5.0
952
960
953 ``pyin`` is renamed to ``execute_input``.
961 ``pyin`` is renamed to ``execute_input``.
954
962
955
963
956 Execution results
964 Execution results
957 -----------------
965 -----------------
958
966
959 Results of an execution are published as an ``execute_result``.
967 Results of an execution are published as an ``execute_result``.
960 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
968 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
961
969
962 Results can have multiple simultaneous formats depending on its
970 Results can have multiple simultaneous formats depending on its
963 configuration. A plain text representation should always be provided
971 configuration. A plain text representation should always be provided
964 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
972 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
965 according to its capabilities.
973 according to its capabilities.
966 Frontends should ignore mime-types they do not understand. The data itself is
974 Frontends should ignore mime-types they do not understand. The data itself is
967 any JSON object and depends on the format. It is often, but not always a string.
975 any JSON object and depends on the format. It is often, but not always a string.
968
976
969 Message type: ``execute_result``::
977 Message type: ``execute_result``::
970
978
971 content = {
979 content = {
972
980
973 # The counter for this execution is also provided so that clients can
981 # The counter for this execution is also provided so that clients can
974 # display it, since IPython automatically creates variables called _N
982 # display it, since IPython automatically creates variables called _N
975 # (for prompt N).
983 # (for prompt N).
976 'execution_count' : int,
984 'execution_count' : int,
977
985
978 # data and metadata are identical to a display_data message.
986 # data and metadata are identical to a display_data message.
979 # the object being displayed is that passed to the display hook,
987 # the object being displayed is that passed to the display hook,
980 # i.e. the *result* of the execution.
988 # i.e. the *result* of the execution.
981 'data' : dict,
989 'data' : dict,
982 'metadata' : dict,
990 'metadata' : dict,
983 }
991 }
984
992
985 Execution errors
993 Execution errors
986 ----------------
994 ----------------
987
995
988 When an error occurs during code execution
996 When an error occurs during code execution
989
997
990 Message type: ``error``::
998 Message type: ``error``::
991
999
992 content = {
1000 content = {
993 # Similar content to the execute_reply messages for the 'error' case,
1001 # Similar content to the execute_reply messages for the 'error' case,
994 # except the 'status' field is omitted.
1002 # except the 'status' field is omitted.
995 }
1003 }
996
1004
997 .. versionchanged:: 5.0
1005 .. versionchanged:: 5.0
998
1006
999 ``pyerr`` renamed to ``error``
1007 ``pyerr`` renamed to ``error``
1000
1008
1001 Kernel status
1009 Kernel status
1002 -------------
1010 -------------
1003
1011
1004 This message type is used by frontends to monitor the status of the kernel.
1012 This message type is used by frontends to monitor the status of the kernel.
1005
1013
1006 Message type: ``status``::
1014 Message type: ``status``::
1007
1015
1008 content = {
1016 content = {
1009 # When the kernel starts to handle a message, it will enter the 'busy'
1017 # When the kernel starts to handle a message, it will enter the 'busy'
1010 # state and when it finishes, it will enter the 'idle' state.
1018 # state and when it finishes, it will enter the 'idle' state.
1011 # The kernel will publish state 'starting' exactly once at process startup.
1019 # The kernel will publish state 'starting' exactly once at process startup.
1012 execution_state : ('busy', 'idle', 'starting')
1020 execution_state : ('busy', 'idle', 'starting')
1013 }
1021 }
1014
1022
1015 .. versionchanged:: 5.0
1023 .. versionchanged:: 5.0
1016
1024
1017 Busy and idle messages should be sent before/after handling every message,
1025 Busy and idle messages should be sent before/after handling every message,
1018 not just execution.
1026 not just execution.
1019
1027
1020 Clear output
1028 Clear output
1021 ------------
1029 ------------
1022
1030
1023 This message type is used to clear the output that is visible on the frontend.
1031 This message type is used to clear the output that is visible on the frontend.
1024
1032
1025 Message type: ``clear_output``::
1033 Message type: ``clear_output``::
1026
1034
1027 content = {
1035 content = {
1028
1036
1029 # Wait to clear the output until new output is available. Clears the
1037 # Wait to clear the output until new output is available. Clears the
1030 # existing output immediately before the new output is displayed.
1038 # existing output immediately before the new output is displayed.
1031 # Useful for creating simple animations with minimal flickering.
1039 # Useful for creating simple animations with minimal flickering.
1032 'wait' : bool,
1040 'wait' : bool,
1033 }
1041 }
1034
1042
1035 .. versionchanged:: 4.1
1043 .. versionchanged:: 4.1
1036
1044
1037 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1045 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1038 and ``wait`` is added.
1046 and ``wait`` is added.
1039 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1047 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1040 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1048 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1041
1049
1042
1050
1043 Messages on the stdin ROUTER/DEALER sockets
1051 Messages on the stdin ROUTER/DEALER sockets
1044 ===========================================
1052 ===========================================
1045
1053
1046 This is a socket where the request/reply pattern goes in the opposite direction:
1054 This is a socket where the request/reply pattern goes in the opposite direction:
1047 from the kernel to a *single* frontend, and its purpose is to allow
1055 from the kernel to a *single* frontend, and its purpose is to allow
1048 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1056 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1049 to be fulfilled by the client. The request should be made to the frontend that
1057 to be fulfilled by the client. The request should be made to the frontend that
1050 made the execution request that prompted ``raw_input`` to be called. For now we
1058 made the execution request that prompted ``raw_input`` to be called. For now we
1051 will keep these messages as simple as possible, since they only mean to convey
1059 will keep these messages as simple as possible, since they only mean to convey
1052 the ``raw_input(prompt)`` call.
1060 the ``raw_input(prompt)`` call.
1053
1061
1054 Message type: ``input_request``::
1062 Message type: ``input_request``::
1055
1063
1056 content = {
1064 content = {
1057 # the text to show at the prompt
1065 # the text to show at the prompt
1058 'prompt' : str,
1066 'prompt' : str,
1059 # Is the request for a password?
1067 # Is the request for a password?
1060 # If so, the frontend shouldn't echo input.
1068 # If so, the frontend shouldn't echo input.
1061 'password' : bool
1069 'password' : bool
1062 }
1070 }
1063
1071
1064 Message type: ``input_reply``::
1072 Message type: ``input_reply``::
1065
1073
1066 content = { 'value' : str }
1074 content = { 'value' : str }
1067
1075
1068
1076
1069 When ``password`` is True, the frontend should not echo the input as it is entered.
1077 When ``password`` is True, the frontend should not echo the input as it is entered.
1070
1078
1071 .. versionchanged:: 5.0
1079 .. versionchanged:: 5.0
1072
1080
1073 ``password`` key added.
1081 ``password`` key added.
1074
1082
1075 .. note::
1083 .. note::
1076
1084
1077 The stdin socket of the client is required to have the same zmq IDENTITY
1085 The stdin socket of the client is required to have the same zmq IDENTITY
1078 as the client's shell socket.
1086 as the client's shell socket.
1079 Because of this, the ``input_request`` must be sent with the same IDENTITY
1087 Because of this, the ``input_request`` must be sent with the same IDENTITY
1080 routing prefix as the ``execute_reply`` in order for the frontend to receive
1088 routing prefix as the ``execute_reply`` in order for the frontend to receive
1081 the message.
1089 the message.
1082
1090
1083 .. note::
1091 .. note::
1084
1092
1085 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1093 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1086 practice the kernel should behave like an interactive program. When a
1094 practice the kernel should behave like an interactive program. When a
1087 program is opened on the console, the keyboard effectively takes over the
1095 program is opened on the console, the keyboard effectively takes over the
1088 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1096 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1089 Since the IPython kernel effectively behaves like a console program (albeit
1097 Since the IPython kernel effectively behaves like a console program (albeit
1090 one whose "keyboard" is actually living in a separate process and
1098 one whose "keyboard" is actually living in a separate process and
1091 transported over the zmq connection), raw ``stdin`` isn't expected to be
1099 transported over the zmq connection), raw ``stdin`` isn't expected to be
1092 available.
1100 available.
1093
1101
1094 .. _kernel_heartbeat:
1102 .. _kernel_heartbeat:
1095
1103
1096 Heartbeat for kernels
1104 Heartbeat for kernels
1097 =====================
1105 =====================
1098
1106
1099 Clients send ping messages on a REQ socket, which are echoed right back
1107 Clients send ping messages on a REQ socket, which are echoed right back
1100 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1108 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1101
1109
1102
1110
1103 Custom Messages
1111 Custom Messages
1104 ===============
1112 ===============
1105
1113
1106 .. versionadded:: 4.1
1114 .. versionadded:: 4.1
1107
1115
1108 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1116 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1109 and Kernel-side components, and allow them to communicate with each other.
1117 and Kernel-side components, and allow them to communicate with each other.
1110 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1118 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1111 and can communicate in either direction.
1119 and can communicate in either direction.
1112
1120
1113 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1121 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1114 and no messages expect a reply.
1122 and no messages expect a reply.
1115 The Kernel listens for these messages on the Shell channel,
1123 The Kernel listens for these messages on the Shell channel,
1116 and the Frontend listens for them on the IOPub channel.
1124 and the Frontend listens for them on the IOPub channel.
1117
1125
1118 Opening a Comm
1126 Opening a Comm
1119 --------------
1127 --------------
1120
1128
1121 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1129 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1122
1130
1123 {
1131 {
1124 'comm_id' : 'u-u-i-d',
1132 'comm_id' : 'u-u-i-d',
1125 'target_name' : 'my_comm',
1133 'target_name' : 'my_comm',
1126 'data' : {}
1134 'data' : {}
1127 }
1135 }
1128
1136
1129 Every Comm has an ID and a target name.
1137 Every Comm has an ID and a target name.
1130 The code handling the message on the receiving side is responsible for maintaining a mapping
1138 The code handling the message on the receiving side is responsible for maintaining a mapping
1131 of target_name keys to constructors.
1139 of target_name keys to constructors.
1132 After a ``comm_open`` message has been sent,
1140 After a ``comm_open`` message has been sent,
1133 there should be a corresponding Comm instance on both sides.
1141 there should be a corresponding Comm instance on both sides.
1134 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1142 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1135
1143
1136 If the ``target_name`` key is not found on the receiving side,
1144 If the ``target_name`` key is not found on the receiving side,
1137 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1145 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1138
1146
1139 Comm Messages
1147 Comm Messages
1140 -------------
1148 -------------
1141
1149
1142 Comm messages are one-way communications to update comm state,
1150 Comm messages are one-way communications to update comm state,
1143 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1151 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1144
1152
1145 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1153 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1146
1154
1147 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1155 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1148
1156
1149 Message type: ``comm_msg``::
1157 Message type: ``comm_msg``::
1150
1158
1151 {
1159 {
1152 'comm_id' : 'u-u-i-d',
1160 'comm_id' : 'u-u-i-d',
1153 'data' : {}
1161 'data' : {}
1154 }
1162 }
1155
1163
1156 Tearing Down Comms
1164 Tearing Down Comms
1157 ------------------
1165 ------------------
1158
1166
1159 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1167 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1160 This is done with a ``comm_close`` message.
1168 This is done with a ``comm_close`` message.
1161
1169
1162 Message type: ``comm_close``::
1170 Message type: ``comm_close``::
1163
1171
1164 {
1172 {
1165 'comm_id' : 'u-u-i-d',
1173 'comm_id' : 'u-u-i-d',
1166 'data' : {}
1174 'data' : {}
1167 }
1175 }
1168
1176
1169 Output Side Effects
1177 Output Side Effects
1170 -------------------
1178 -------------------
1171
1179
1172 Since comm messages can execute arbitrary user code,
1180 Since comm messages can execute arbitrary user code,
1173 handlers should set the parent header and publish status busy / idle,
1181 handlers should set the parent header and publish status busy / idle,
1174 just like an execute request.
1182 just like an execute request.
1175
1183
1176
1184
1177 To Do
1185 To Do
1178 =====
1186 =====
1179
1187
1180 Missing things include:
1188 Missing things include:
1181
1189
1182 * Important: finish thinking through the payload concept and API.
1190 * Important: finish thinking through the payload concept and API.
1183
1191
1184 .. include:: ../links.txt
1192 .. include:: ../links.txt
@@ -1,174 +1,175 b''
1 Making simple Python wrapper kernels
1 Making simple Python wrapper kernels
2 ====================================
2 ====================================
3
3
4 .. versionadded:: 3.0
4 .. versionadded:: 3.0
5
5
6 You can now re-use the kernel machinery in IPython to easily make new kernels.
6 You can now re-use the kernel machinery in IPython to easily make new kernels.
7 This is useful for languages that have Python bindings, such as `Octave
7 This is useful for languages that have Python bindings, such as `Octave
8 <http://www.gnu.org/software/octave/>`_ (via
8 <http://www.gnu.org/software/octave/>`_ (via
9 `Oct2Py <http://blink1073.github.io/oct2py/docs/index.html>`_), or languages
9 `Oct2Py <http://blink1073.github.io/oct2py/docs/index.html>`_), or languages
10 where the REPL can be controlled in a tty using `pexpect <http://pexpect.readthedocs.org/en/latest/>`_,
10 where the REPL can be controlled in a tty using `pexpect <http://pexpect.readthedocs.org/en/latest/>`_,
11 such as bash.
11 such as bash.
12
12
13 .. seealso::
13 .. seealso::
14
14
15 `bash_kernel <https://github.com/takluyver/bash_kernel>`_
15 `bash_kernel <https://github.com/takluyver/bash_kernel>`_
16 A simple kernel for bash, written using this machinery
16 A simple kernel for bash, written using this machinery
17
17
18 Required steps
18 Required steps
19 --------------
19 --------------
20
20
21 Subclass :class:`IPython.kernel.zmq.kernelbase.Kernel`, and implement the
21 Subclass :class:`IPython.kernel.zmq.kernelbase.Kernel`, and implement the
22 following methods and attributes:
22 following methods and attributes:
23
23
24 .. class:: MyKernel
24 .. class:: MyKernel
25
25
26 .. attribute:: implementation
26 .. attribute:: implementation
27 implementation_version
27 implementation_version
28 language
28 language
29 language_version
29 language_version
30 banner
30 banner
31
31
32 Information for :ref:`msging_kernel_info` replies. 'Implementation' refers
32 Information for :ref:`msging_kernel_info` replies. 'Implementation' refers
33 to the kernel (e.g. IPython), and 'language' refers to the language it
33 to the kernel (e.g. IPython), and 'language' refers to the language it
34 interprets (e.g. Python). The 'banner' is displayed to the user in console
34 interprets (e.g. Python). The 'banner' is displayed to the user in console
35 UIs before the first prompt. All of these values are strings.
35 UIs before the first prompt. All of these values are strings.
36
36
37 .. attribute:: language_info
37 .. attribute:: language_info
38
38
39 Language information for :ref:`msging_kernel_info` replies, in a dictionary.
39 Language information for :ref:`msging_kernel_info` replies, in a dictionary.
40 This should contain the key ``mimetype`` with the mimetype of code in the
40 This should contain the key ``mimetype`` with the mimetype of code in the
41 target language (e.g. ``'text/x-python'``). It may also contain keys
41 target language (e.g. ``'text/x-python'``), and ``file_extension`` (e.g.
42 ``codemirror_mode`` and ``pygments_lexer`` if they need to differ from
42 ``'py'``).
43 :attr:`language`.
43 It may also contain keys ``codemirror_mode`` and ``pygments_lexer`` if they
44 need to differ from :attr:`language`.
44
45
45 Other keys may be added to this later.
46 Other keys may be added to this later.
46
47
47 .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
48 .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
48
49
49 Execute user code.
50 Execute user code.
50
51
51 :param str code: The code to be executed.
52 :param str code: The code to be executed.
52 :param bool silent: Whether to display output.
53 :param bool silent: Whether to display output.
53 :param bool store_history: Whether to record this code in history and
54 :param bool store_history: Whether to record this code in history and
54 increase the execution count. If silent is True, this is implicitly
55 increase the execution count. If silent is True, this is implicitly
55 False.
56 False.
56 :param dict user_expressions: Mapping of names to expressions to evaluate
57 :param dict user_expressions: Mapping of names to expressions to evaluate
57 after the code has run. You can ignore this if you need to.
58 after the code has run. You can ignore this if you need to.
58 :param bool allow_stdin: Whether the frontend can provide input on request
59 :param bool allow_stdin: Whether the frontend can provide input on request
59 (e.g. for Python's :func:`raw_input`).
60 (e.g. for Python's :func:`raw_input`).
60
61
61 Your method should return a dict containing the fields described in
62 Your method should return a dict containing the fields described in
62 :ref:`execution_results`. To display output, it can send messages
63 :ref:`execution_results`. To display output, it can send messages
63 using :meth:`~IPython.kernel.zmq.kernelbase.Kernel.send_response`.
64 using :meth:`~IPython.kernel.zmq.kernelbase.Kernel.send_response`.
64 See :doc:`messaging` for details of the different message types.
65 See :doc:`messaging` for details of the different message types.
65
66
66 To launch your kernel, add this at the end of your module::
67 To launch your kernel, add this at the end of your module::
67
68
68 if __name__ == '__main__':
69 if __name__ == '__main__':
69 from IPython.kernel.zmq.kernelapp import IPKernelApp
70 from IPython.kernel.zmq.kernelapp import IPKernelApp
70 IPKernelApp.launch_instance(kernel_class=MyKernel)
71 IPKernelApp.launch_instance(kernel_class=MyKernel)
71
72
72 Example
73 Example
73 -------
74 -------
74
75
75 ``echokernel.py`` will simply echo any input it's given to stdout::
76 ``echokernel.py`` will simply echo any input it's given to stdout::
76
77
77 from IPython.kernel.zmq.kernelbase import Kernel
78 from IPython.kernel.zmq.kernelbase import Kernel
78
79
79 class EchoKernel(Kernel):
80 class EchoKernel(Kernel):
80 implementation = 'Echo'
81 implementation = 'Echo'
81 implementation_version = '1.0'
82 implementation_version = '1.0'
82 language = 'no-op'
83 language = 'no-op'
83 language_version = '0.1'
84 language_version = '0.1'
84 language_info = {'mimetype': 'text/plain'}
85 language_info = {'mimetype': 'text/plain'}
85 banner = "Echo kernel - as useful as a parrot"
86 banner = "Echo kernel - as useful as a parrot"
86
87
87 def do_execute(self, code, silent, store_history=True, user_expressions=None,
88 def do_execute(self, code, silent, store_history=True, user_expressions=None,
88 allow_stdin=False):
89 allow_stdin=False):
89 if not silent:
90 if not silent:
90 stream_content = {'name': 'stdout', 'text': code}
91 stream_content = {'name': 'stdout', 'text': code}
91 self.send_response(self.iopub_socket, 'stream', stream_content)
92 self.send_response(self.iopub_socket, 'stream', stream_content)
92
93
93 return {'status': 'ok',
94 return {'status': 'ok',
94 # The base class increments the execution count
95 # The base class increments the execution count
95 'execution_count': self.execution_count,
96 'execution_count': self.execution_count,
96 'payload': [],
97 'payload': [],
97 'user_expressions': {},
98 'user_expressions': {},
98 }
99 }
99
100
100 if __name__ == '__main__':
101 if __name__ == '__main__':
101 from IPython.kernel.zmq.kernelapp import IPKernelApp
102 from IPython.kernel.zmq.kernelapp import IPKernelApp
102 IPKernelApp.launch_instance(kernel_class=EchoKernel)
103 IPKernelApp.launch_instance(kernel_class=EchoKernel)
103
104
104 Here's the Kernel spec ``kernel.json`` file for this::
105 Here's the Kernel spec ``kernel.json`` file for this::
105
106
106 {"argv":["python","-m","echokernel", "-f", "{connection_file}"],
107 {"argv":["python","-m","echokernel", "-f", "{connection_file}"],
107 "display_name":"Echo",
108 "display_name":"Echo",
108 }
109 }
109
110
110
111
111 Optional steps
112 Optional steps
112 --------------
113 --------------
113
114
114 You can override a number of other methods to improve the functionality of your
115 You can override a number of other methods to improve the functionality of your
115 kernel. All of these methods should return a dictionary as described in the
116 kernel. All of these methods should return a dictionary as described in the
116 relevant section of the :doc:`messaging spec <messaging>`.
117 relevant section of the :doc:`messaging spec <messaging>`.
117
118
118 .. class:: MyKernel
119 .. class:: MyKernel
119
120
120 .. method:: do_complete(code, cusor_pos)
121 .. method:: do_complete(code, cusor_pos)
121
122
122 Code completion
123 Code completion
123
124
124 :param str code: The code already present
125 :param str code: The code already present
125 :param int cursor_pos: The position in the code where completion is requested
126 :param int cursor_pos: The position in the code where completion is requested
126
127
127 .. seealso::
128 .. seealso::
128
129
129 :ref:`msging_completion` messages
130 :ref:`msging_completion` messages
130
131
131 .. method:: do_inspect(code, cusor_pos, detail_level=0)
132 .. method:: do_inspect(code, cusor_pos, detail_level=0)
132
133
133 Object introspection
134 Object introspection
134
135
135 :param str code: The code
136 :param str code: The code
136 :param int cursor_pos: The position in the code where introspection is requested
137 :param int cursor_pos: The position in the code where introspection is requested
137 :param int detail_level: 0 or 1 for more or less detail. In IPython, 1 gets
138 :param int detail_level: 0 or 1 for more or less detail. In IPython, 1 gets
138 the source code.
139 the source code.
139
140
140 .. seealso::
141 .. seealso::
141
142
142 :ref:`msging_inspection` messages
143 :ref:`msging_inspection` messages
143
144
144 .. method:: do_history(hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False)
145 .. method:: do_history(hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False)
145
146
146 History access. Only the relevant parameters for the type of history
147 History access. Only the relevant parameters for the type of history
147 request concerned will be passed, so your method definition must have defaults
148 request concerned will be passed, so your method definition must have defaults
148 for all the arguments shown with defaults here.
149 for all the arguments shown with defaults here.
149
150
150 .. seealso::
151 .. seealso::
151
152
152 :ref:`msging_history` messages
153 :ref:`msging_history` messages
153
154
154 .. method:: do_is_complete(code)
155 .. method:: do_is_complete(code)
155
156
156 Is code entered in a console-like interface complete and ready to execute,
157 Is code entered in a console-like interface complete and ready to execute,
157 or should a continuation prompt be shown?
158 or should a continuation prompt be shown?
158
159
159 :param str code: The code entered so far - possibly multiple lines
160 :param str code: The code entered so far - possibly multiple lines
160
161
161 .. seealso::
162 .. seealso::
162
163
163 :ref:`msging_is_complete` messages
164 :ref:`msging_is_complete` messages
164
165
165 .. method:: do_shutdown(restart)
166 .. method:: do_shutdown(restart)
166
167
167 Shutdown the kernel. You only need to handle your own clean up - the kernel
168 Shutdown the kernel. You only need to handle your own clean up - the kernel
168 machinery will take care of cleaning up its own things before stopping.
169 machinery will take care of cleaning up its own things before stopping.
169
170
170 :param bool restart: Whether the kernel will be started again afterwards
171 :param bool restart: Whether the kernel will be started again afterwards
171
172
172 .. seealso::
173 .. seealso::
173
174
174 :ref:`msging_shutdown` messages
175 :ref:`msging_shutdown` messages
General Comments 0
You need to be logged in to leave comments. Login now