##// END OF EJS Templates
better message when notebook format is not supported...
MinRK -
Show More
@@ -1,358 +1,359 b''
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import io
21 21 import os
22 22 import glob
23 23 import shutil
24 24 from unicodedata import normalize
25 25
26 26 from tornado import web
27 27
28 28 from .nbmanager import NotebookManager
29 29 from IPython.nbformat import current
30 30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
31 31 from IPython.utils import tz
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Classes
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class FileNotebookManager(NotebookManager):
38 38
39 39 save_script = Bool(False, config=True,
40 40 help="""Automatically create a Python script when saving the notebook.
41 41
42 42 For easier use of import, %run and %load across notebooks, a
43 43 <notebook-name>.py script will be created next to any
44 44 <notebook-name>.ipynb on each save. This can also be set with the
45 45 short `--script` flag.
46 46 """
47 47 )
48 48
49 49 checkpoint_dir = Unicode(config=True,
50 50 help="""The location in which to keep notebook checkpoints
51 51
52 52 By default, it is notebook-dir/.ipynb_checkpoints
53 53 """
54 54 )
55 55 def _checkpoint_dir_default(self):
56 56 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
57 57
58 58 def _checkpoint_dir_changed(self, name, old, new):
59 59 """do a bit of validation of the checkpoint dir"""
60 60 if not os.path.isabs(new):
61 61 # If we receive a non-absolute path, make it absolute.
62 62 abs_new = os.path.abspath(new)
63 63 self.checkpoint_dir = abs_new
64 64 return
65 65 if os.path.exists(new) and not os.path.isdir(new):
66 66 raise TraitError("checkpoint dir %r is not a directory" % new)
67 67 if not os.path.exists(new):
68 68 self.log.info("Creating checkpoint dir %s", new)
69 69 try:
70 70 os.mkdir(new)
71 71 except:
72 72 raise TraitError("Couldn't create checkpoint dir %r" % new)
73 73
74 74 filename_ext = Unicode(u'.ipynb')
75 75
76 76 # Map notebook names to notebook_ids
77 77 rev_mapping = Dict()
78 78
79 79 def get_notebook_names(self):
80 80 """List all notebook names in the notebook dir."""
81 81 names = glob.glob(os.path.join(self.notebook_dir,
82 82 '*' + self.filename_ext))
83 83 names = [normalize('NFC', os.path.splitext(os.path.basename(name))[0])
84 84 for name in names]
85 85 return names
86 86
87 87 def list_notebooks(self):
88 88 """List all notebooks in the notebook dir."""
89 89 names = self.get_notebook_names()
90 90
91 91 data = []
92 92 for name in names:
93 93 if name not in self.rev_mapping:
94 94 notebook_id = self.new_notebook_id(name)
95 95 else:
96 96 notebook_id = self.rev_mapping[name]
97 97 data.append(dict(notebook_id=notebook_id,name=name))
98 98 data = sorted(data, key=lambda item: item['name'])
99 99 return data
100 100
101 101 def new_notebook_id(self, name):
102 102 """Generate a new notebook_id for a name and store its mappings."""
103 103 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
104 104 self.rev_mapping[name] = notebook_id
105 105 return notebook_id
106 106
107 107 def delete_notebook_id(self, notebook_id):
108 108 """Delete a notebook's id in the mapping."""
109 109 name = self.mapping[notebook_id]
110 110 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
111 111 del self.rev_mapping[name]
112 112
113 113 def notebook_exists(self, notebook_id):
114 114 """Does a notebook exist?"""
115 115 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
116 116 if not exists:
117 117 return False
118 118 path = self.get_path_by_name(self.mapping[notebook_id])
119 119 return os.path.isfile(path)
120 120
121 121 def get_name(self, notebook_id):
122 122 """get a notebook name, raising 404 if not found"""
123 123 try:
124 124 name = self.mapping[notebook_id]
125 125 except KeyError:
126 126 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
127 127 return name
128 128
129 129 def get_path(self, notebook_id):
130 130 """Return a full path to a notebook given its notebook_id."""
131 131 name = self.get_name(notebook_id)
132 132 return self.get_path_by_name(name)
133 133
134 134 def get_path_by_name(self, name):
135 135 """Return a full path to a notebook given its name."""
136 136 filename = name + self.filename_ext
137 137 path = os.path.join(self.notebook_dir, filename)
138 138 return path
139 139
140 140 def read_notebook_object_from_path(self, path):
141 141 """read a notebook object from a path"""
142 142 info = os.stat(path)
143 143 last_modified = tz.utcfromtimestamp(info.st_mtime)
144 144 with open(path,'r') as f:
145 145 s = f.read()
146 146 try:
147 147 # v1 and v2 and json in the .ipynb files.
148 148 nb = current.reads(s, u'json')
149 except Exception as e:
150 raise web.HTTPError(500, u'Unreadable JSON notebook: %s' % e)
149 except ValueError as e:
150 msg = u"Unreadable Notebook: %s" % e
151 raise web.HTTPError(400, msg, reason=msg)
151 152 return last_modified, nb
152 153
153 154 def read_notebook_object(self, notebook_id):
154 155 """Get the Notebook representation of a notebook by notebook_id."""
155 156 path = self.get_path(notebook_id)
156 157 if not os.path.isfile(path):
157 158 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
158 159 last_modified, nb = self.read_notebook_object_from_path(path)
159 160 # Always use the filename as the notebook name.
160 161 # Eventually we will get rid of the notebook name in the metadata
161 162 # but for now, that name is just an empty string. Until the notebooks
162 163 # web service knows about names in URLs we still pass the name
163 164 # back to the web app using the metadata though.
164 165 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
165 166 return last_modified, nb
166 167
167 168 def write_notebook_object(self, nb, notebook_id=None):
168 169 """Save an existing notebook object by notebook_id."""
169 170 try:
170 171 new_name = normalize('NFC', nb.metadata.name)
171 172 except AttributeError:
172 173 raise web.HTTPError(400, u'Missing notebook name')
173 174
174 175 if notebook_id is None:
175 176 notebook_id = self.new_notebook_id(new_name)
176 177
177 178 if notebook_id not in self.mapping:
178 179 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
179 180
180 181 old_name = self.mapping[notebook_id]
181 182 old_checkpoints = self.list_checkpoints(notebook_id)
182 183 path = self.get_path_by_name(new_name)
183 184
184 185 # Right before we save the notebook, we write an empty string as the
185 186 # notebook name in the metadata. This is to prepare for removing
186 187 # this attribute entirely post 1.0. The web app still uses the metadata
187 188 # name for now.
188 189 nb.metadata.name = u''
189 190
190 191 try:
191 192 self.log.debug("Autosaving notebook %s", path)
192 193 with open(path,'w') as f:
193 194 current.write(nb, f, u'json')
194 195 except Exception as e:
195 196 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
196 197
197 198 # save .py script as well
198 199 if self.save_script:
199 200 pypath = os.path.splitext(path)[0] + '.py'
200 201 self.log.debug("Writing script %s", pypath)
201 202 try:
202 203 with io.open(pypath,'w', encoding='utf-8') as f:
203 204 current.write(nb, f, u'py')
204 205 except Exception as e:
205 206 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
206 207
207 208 # remove old files if the name changed
208 209 if old_name != new_name:
209 210 # update mapping
210 211 self.mapping[notebook_id] = new_name
211 212 self.rev_mapping[new_name] = notebook_id
212 213 del self.rev_mapping[old_name]
213 214
214 215 # remove renamed original, if it exists
215 216 old_path = self.get_path_by_name(old_name)
216 217 if os.path.isfile(old_path):
217 218 self.log.debug("unlinking notebook %s", old_path)
218 219 os.unlink(old_path)
219 220
220 221 # cleanup old script, if it exists
221 222 if self.save_script:
222 223 old_pypath = os.path.splitext(old_path)[0] + '.py'
223 224 if os.path.isfile(old_pypath):
224 225 self.log.debug("unlinking script %s", old_pypath)
225 226 os.unlink(old_pypath)
226 227
227 228 # rename checkpoints to follow file
228 229 for cp in old_checkpoints:
229 230 checkpoint_id = cp['checkpoint_id']
230 231 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
231 232 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
232 233 if os.path.isfile(old_cp_path):
233 234 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
234 235 os.rename(old_cp_path, new_cp_path)
235 236
236 237 return notebook_id
237 238
238 239 def delete_notebook(self, notebook_id):
239 240 """Delete notebook by notebook_id."""
240 241 nb_path = self.get_path(notebook_id)
241 242 if not os.path.isfile(nb_path):
242 243 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
243 244
244 245 # clear checkpoints
245 246 for checkpoint in self.list_checkpoints(notebook_id):
246 247 checkpoint_id = checkpoint['checkpoint_id']
247 248 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
248 249 self.log.debug(path)
249 250 if os.path.isfile(path):
250 251 self.log.debug("unlinking checkpoint %s", path)
251 252 os.unlink(path)
252 253
253 254 self.log.debug("unlinking notebook %s", nb_path)
254 255 os.unlink(nb_path)
255 256 self.delete_notebook_id(notebook_id)
256 257
257 258 def increment_filename(self, basename):
258 259 """Return a non-used filename of the form basename<int>.
259 260
260 261 This searches through the filenames (basename0, basename1, ...)
261 262 until is find one that is not already being used. It is used to
262 263 create Untitled and Copy names that are unique.
263 264 """
264 265 i = 0
265 266 while True:
266 267 name = u'%s%i' % (basename,i)
267 268 path = self.get_path_by_name(name)
268 269 if not os.path.isfile(path):
269 270 break
270 271 else:
271 272 i = i+1
272 273 return name
273 274
274 275 # Checkpoint-related utilities
275 276
276 277 def get_checkpoint_path_by_name(self, name, checkpoint_id):
277 278 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
278 279 filename = u"{name}-{checkpoint_id}{ext}".format(
279 280 name=name,
280 281 checkpoint_id=checkpoint_id,
281 282 ext=self.filename_ext,
282 283 )
283 284 path = os.path.join(self.checkpoint_dir, filename)
284 285 return path
285 286
286 287 def get_checkpoint_path(self, notebook_id, checkpoint_id):
287 288 """find the path to a checkpoint"""
288 289 name = self.get_name(notebook_id)
289 290 return self.get_checkpoint_path_by_name(name, checkpoint_id)
290 291
291 292 def get_checkpoint_info(self, notebook_id, checkpoint_id):
292 293 """construct the info dict for a given checkpoint"""
293 294 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
294 295 stats = os.stat(path)
295 296 last_modified = tz.utcfromtimestamp(stats.st_mtime)
296 297 info = dict(
297 298 checkpoint_id = checkpoint_id,
298 299 last_modified = last_modified,
299 300 )
300 301
301 302 return info
302 303
303 304 # public checkpoint API
304 305
305 306 def create_checkpoint(self, notebook_id):
306 307 """Create a checkpoint from the current state of a notebook"""
307 308 nb_path = self.get_path(notebook_id)
308 309 # only the one checkpoint ID:
309 310 checkpoint_id = u"checkpoint"
310 311 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
311 312 self.log.debug("creating checkpoint for notebook %s", notebook_id)
312 313 if not os.path.exists(self.checkpoint_dir):
313 314 os.mkdir(self.checkpoint_dir)
314 315 shutil.copy2(nb_path, cp_path)
315 316
316 317 # return the checkpoint info
317 318 return self.get_checkpoint_info(notebook_id, checkpoint_id)
318 319
319 320 def list_checkpoints(self, notebook_id):
320 321 """list the checkpoints for a given notebook
321 322
322 323 This notebook manager currently only supports one checkpoint per notebook.
323 324 """
324 325 checkpoint_id = u"checkpoint"
325 326 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
326 327 if not os.path.exists(path):
327 328 return []
328 329 else:
329 330 return [self.get_checkpoint_info(notebook_id, checkpoint_id)]
330 331
331 332
332 333 def restore_checkpoint(self, notebook_id, checkpoint_id):
333 334 """restore a notebook to a checkpointed state"""
334 335 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
335 336 nb_path = self.get_path(notebook_id)
336 337 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
337 338 if not os.path.isfile(cp_path):
338 339 self.log.debug("checkpoint file does not exist: %s", cp_path)
339 340 raise web.HTTPError(404,
340 341 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
341 342 )
342 343 # ensure notebook is readable (never restore from an unreadable notebook)
343 344 last_modified, nb = self.read_notebook_object_from_path(cp_path)
344 345 shutil.copy2(cp_path, nb_path)
345 346 self.log.debug("copying %s -> %s", cp_path, nb_path)
346 347
347 348 def delete_checkpoint(self, notebook_id, checkpoint_id):
348 349 """delete a notebook's checkpoint"""
349 350 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
350 351 if not os.path.isfile(path):
351 352 raise web.HTTPError(404,
352 353 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
353 354 )
354 355 self.log.debug("unlinking %s", path)
355 356 os.unlink(path)
356 357
357 358 def info_string(self):
358 359 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,2037 +1,2037 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15 var key = IPython.utils.keycodes;
16 16
17 17 /**
18 18 * A notebook contains and manages cells.
19 19 *
20 20 * @class Notebook
21 21 * @constructor
22 22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 23 * @param {Object} [options] A config object
24 24 */
25 25 var Notebook = function (selector, options) {
26 26 var options = options || {};
27 27 this._baseProjectUrl = options.baseProjectUrl;
28 28 this.read_only = options.read_only || IPython.read_only;
29 29
30 30 this.element = $(selector);
31 31 this.element.scroll();
32 32 this.element.data("notebook", this);
33 33 this.next_prompt_number = 1;
34 34 this.kernel = null;
35 35 this.clipboard = null;
36 36 this.undelete_backup = null;
37 37 this.undelete_index = null;
38 38 this.undelete_below = false;
39 39 this.paste_enabled = false;
40 40 this.set_dirty(false);
41 41 this.metadata = {};
42 42 this._checkpoint_after_save = false;
43 43 this.last_checkpoint = null;
44 44 this.autosave_interval = 0;
45 45 this.autosave_timer = null;
46 46 // autosave *at most* every two minutes
47 47 this.minimum_autosave_interval = 120000;
48 48 // single worksheet for now
49 49 this.worksheet_metadata = {};
50 50 this.control_key_active = false;
51 51 this.notebook_id = null;
52 52 this.notebook_name = null;
53 53 this.notebook_name_blacklist_re = /[\/\\:]/;
54 54 this.nbformat = 3 // Increment this when changing the nbformat
55 55 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 56 this.style();
57 57 this.create_elements();
58 58 this.bind_events();
59 59 };
60 60
61 61 /**
62 62 * Tweak the notebook's CSS style.
63 63 *
64 64 * @method style
65 65 */
66 66 Notebook.prototype.style = function () {
67 67 $('div#notebook').addClass('border-box-sizing');
68 68 };
69 69
70 70 /**
71 71 * Get the root URL of the notebook server.
72 72 *
73 73 * @method baseProjectUrl
74 74 * @return {String} The base project URL
75 75 */
76 76 Notebook.prototype.baseProjectUrl = function(){
77 77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 78 };
79 79
80 80 /**
81 81 * Create an HTML and CSS representation of the notebook.
82 82 *
83 83 * @method create_elements
84 84 */
85 85 Notebook.prototype.create_elements = function () {
86 86 // We add this end_space div to the end of the notebook div to:
87 87 // i) provide a margin between the last cell and the end of the notebook
88 88 // ii) to prevent the div from scrolling up when the last cell is being
89 89 // edited, but is too low on the page, which browsers will do automatically.
90 90 var that = this;
91 91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 92 var end_space = $('<div/>').addClass('end_space');
93 93 end_space.dblclick(function (e) {
94 94 if (that.read_only) return;
95 95 var ncells = that.ncells();
96 96 that.insert_cell_below('code',ncells-1);
97 97 });
98 98 this.element.append(this.container);
99 99 this.container.append(end_space);
100 100 $('div#notebook').addClass('border-box-sizing');
101 101 };
102 102
103 103 /**
104 104 * Bind JavaScript events: key presses and custom IPython events.
105 105 *
106 106 * @method bind_events
107 107 */
108 108 Notebook.prototype.bind_events = function () {
109 109 var that = this;
110 110
111 111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
112 112 var index = that.find_cell_index(data.cell);
113 113 var new_cell = that.insert_cell_below('code',index);
114 114 new_cell.set_text(data.text);
115 115 that.dirty = true;
116 116 });
117 117
118 118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
119 119 that.dirty = data.value;
120 120 });
121 121
122 122 $([IPython.events]).on('select.Cell', function (event, data) {
123 123 var index = that.find_cell_index(data.cell);
124 124 that.select(index);
125 125 });
126 126
127 127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 128 IPython.dialog.modal({
129 129 title: "Kernel Restarting",
130 130 body: "The kernel appears to have died. It will restart automatically.",
131 131 buttons: {
132 132 OK : {
133 133 class : "btn-primary"
134 134 }
135 135 }
136 136 });
137 137 });
138 138
139 139
140 140 $(document).keydown(function (event) {
141 141 // console.log(event);
142 142 if (that.read_only) return true;
143 143
144 144 // Save (CTRL+S) or (AppleKey+S)
145 145 //metaKey = applekey on mac
146 146 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
147 147 that.save_checkpoint();
148 148 event.preventDefault();
149 149 return false;
150 150 } else if (event.which === key.ESC) {
151 151 // Intercept escape at highest level to avoid closing
152 152 // websocket connection with firefox
153 153 IPython.pager.collapse();
154 154 event.preventDefault();
155 155 } else if (event.which === key.SHIFT) {
156 156 // ignore shift keydown
157 157 return true;
158 158 }
159 159 if (event.which === key.UPARROW && !event.shiftKey) {
160 160 var cell = that.get_selected_cell();
161 161 if (cell && cell.at_top()) {
162 162 event.preventDefault();
163 163 that.select_prev();
164 164 };
165 165 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
166 166 var cell = that.get_selected_cell();
167 167 if (cell && cell.at_bottom()) {
168 168 event.preventDefault();
169 169 that.select_next();
170 170 };
171 171 } else if (event.which === key.ENTER && event.shiftKey) {
172 172 that.execute_selected_cell();
173 173 return false;
174 174 } else if (event.which === key.ENTER && event.altKey) {
175 175 // Execute code cell, and insert new in place
176 176 that.execute_selected_cell();
177 177 // Only insert a new cell, if we ended up in an already populated cell
178 178 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
179 179 that.insert_cell_above('code');
180 180 }
181 181 return false;
182 182 } else if (event.which === key.ENTER && event.ctrlKey) {
183 183 that.execute_selected_cell({terminal:true});
184 184 return false;
185 185 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
186 186 that.control_key_active = true;
187 187 return false;
188 188 } else if (event.which === 88 && that.control_key_active) {
189 189 // Cut selected cell = x
190 190 that.cut_cell();
191 191 that.control_key_active = false;
192 192 return false;
193 193 } else if (event.which === 67 && that.control_key_active) {
194 194 // Copy selected cell = c
195 195 that.copy_cell();
196 196 that.control_key_active = false;
197 197 return false;
198 198 } else if (event.which === 86 && that.control_key_active) {
199 199 // Paste below selected cell = v
200 200 that.paste_cell_below();
201 201 that.control_key_active = false;
202 202 return false;
203 203 } else if (event.which === 68 && that.control_key_active) {
204 204 // Delete selected cell = d
205 205 that.delete_cell();
206 206 that.control_key_active = false;
207 207 return false;
208 208 } else if (event.which === 65 && that.control_key_active) {
209 209 // Insert code cell above selected = a
210 210 that.insert_cell_above('code');
211 211 that.control_key_active = false;
212 212 return false;
213 213 } else if (event.which === 66 && that.control_key_active) {
214 214 // Insert code cell below selected = b
215 215 that.insert_cell_below('code');
216 216 that.control_key_active = false;
217 217 return false;
218 218 } else if (event.which === 89 && that.control_key_active) {
219 219 // To code = y
220 220 that.to_code();
221 221 that.control_key_active = false;
222 222 return false;
223 223 } else if (event.which === 77 && that.control_key_active) {
224 224 // To markdown = m
225 225 that.to_markdown();
226 226 that.control_key_active = false;
227 227 return false;
228 228 } else if (event.which === 84 && that.control_key_active) {
229 229 // To Raw = t
230 230 that.to_raw();
231 231 that.control_key_active = false;
232 232 return false;
233 233 } else if (event.which === 49 && that.control_key_active) {
234 234 // To Heading 1 = 1
235 235 that.to_heading(undefined, 1);
236 236 that.control_key_active = false;
237 237 return false;
238 238 } else if (event.which === 50 && that.control_key_active) {
239 239 // To Heading 2 = 2
240 240 that.to_heading(undefined, 2);
241 241 that.control_key_active = false;
242 242 return false;
243 243 } else if (event.which === 51 && that.control_key_active) {
244 244 // To Heading 3 = 3
245 245 that.to_heading(undefined, 3);
246 246 that.control_key_active = false;
247 247 return false;
248 248 } else if (event.which === 52 && that.control_key_active) {
249 249 // To Heading 4 = 4
250 250 that.to_heading(undefined, 4);
251 251 that.control_key_active = false;
252 252 return false;
253 253 } else if (event.which === 53 && that.control_key_active) {
254 254 // To Heading 5 = 5
255 255 that.to_heading(undefined, 5);
256 256 that.control_key_active = false;
257 257 return false;
258 258 } else if (event.which === 54 && that.control_key_active) {
259 259 // To Heading 6 = 6
260 260 that.to_heading(undefined, 6);
261 261 that.control_key_active = false;
262 262 return false;
263 263 } else if (event.which === 79 && that.control_key_active) {
264 264 // Toggle output = o
265 265 if (event.shiftKey){
266 266 that.toggle_output_scroll();
267 267 } else {
268 268 that.toggle_output();
269 269 }
270 270 that.control_key_active = false;
271 271 return false;
272 272 } else if (event.which === 83 && that.control_key_active) {
273 273 // Save notebook = s
274 274 that.save_checkpoint();
275 275 that.control_key_active = false;
276 276 return false;
277 277 } else if (event.which === 74 && that.control_key_active) {
278 278 // Move cell down = j
279 279 that.move_cell_down();
280 280 that.control_key_active = false;
281 281 return false;
282 282 } else if (event.which === 75 && that.control_key_active) {
283 283 // Move cell up = k
284 284 that.move_cell_up();
285 285 that.control_key_active = false;
286 286 return false;
287 287 } else if (event.which === 80 && that.control_key_active) {
288 288 // Select previous = p
289 289 that.select_prev();
290 290 that.control_key_active = false;
291 291 return false;
292 292 } else if (event.which === 78 && that.control_key_active) {
293 293 // Select next = n
294 294 that.select_next();
295 295 that.control_key_active = false;
296 296 return false;
297 297 } else if (event.which === 76 && that.control_key_active) {
298 298 // Toggle line numbers = l
299 299 that.cell_toggle_line_numbers();
300 300 that.control_key_active = false;
301 301 return false;
302 302 } else if (event.which === 73 && that.control_key_active) {
303 303 // Interrupt kernel = i
304 304 that.kernel.interrupt();
305 305 that.control_key_active = false;
306 306 return false;
307 307 } else if (event.which === 190 && that.control_key_active) {
308 308 // Restart kernel = . # matches qt console
309 309 that.restart_kernel();
310 310 that.control_key_active = false;
311 311 return false;
312 312 } else if (event.which === 72 && that.control_key_active) {
313 313 // Show keyboard shortcuts = h
314 314 IPython.quick_help.show_keyboard_shortcuts();
315 315 that.control_key_active = false;
316 316 return false;
317 317 } else if (event.which === 90 && that.control_key_active) {
318 318 // Undo last cell delete = z
319 319 that.undelete();
320 320 that.control_key_active = false;
321 321 return false;
322 322 } else if (event.which === 189 && that.control_key_active) {
323 323 // Split cell = -
324 324 that.split_cell();
325 325 that.control_key_active = false;
326 326 return false;
327 327 } else if (that.control_key_active) {
328 328 that.control_key_active = false;
329 329 return true;
330 330 }
331 331 return true;
332 332 });
333 333
334 334 var collapse_time = function(time){
335 335 var app_height = $('#ipython-main-app').height(); // content height
336 336 var splitter_height = $('div#pager_splitter').outerHeight(true);
337 337 var new_height = app_height - splitter_height;
338 338 that.element.animate({height : new_height + 'px'}, time);
339 339 }
340 340
341 341 this.element.bind('collapse_pager', function (event,extrap) {
342 342 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
343 343 collapse_time(time);
344 344 });
345 345
346 346 var expand_time = function(time) {
347 347 var app_height = $('#ipython-main-app').height(); // content height
348 348 var splitter_height = $('div#pager_splitter').outerHeight(true);
349 349 var pager_height = $('div#pager').outerHeight(true);
350 350 var new_height = app_height - pager_height - splitter_height;
351 351 that.element.animate({height : new_height + 'px'}, time);
352 352 }
353 353
354 354 this.element.bind('expand_pager', function (event, extrap) {
355 355 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
356 356 expand_time(time);
357 357 });
358 358
359 359 // Firefox 22 broke $(window).on("beforeunload")
360 360 // I'm not sure why or how.
361 361 window.onbeforeunload = function (e) {
362 362 // TODO: Make killing the kernel configurable.
363 363 var kill_kernel = false;
364 364 if (kill_kernel) {
365 365 that.kernel.kill();
366 366 }
367 367 // if we are autosaving, trigger an autosave on nav-away.
368 368 // still warn, because if we don't the autosave may fail.
369 369 if (that.dirty && ! that.read_only) {
370 370 if ( that.autosave_interval ) {
371 371 that.save_notebook();
372 372 return "Autosave in progress, latest changes may be lost.";
373 373 } else {
374 374 return "Unsaved changes will be lost.";
375 375 }
376 376 };
377 377 // Null is the *only* return value that will make the browser not
378 378 // pop up the "don't leave" dialog.
379 379 return null;
380 380 };
381 381 };
382 382
383 383 /**
384 384 * Set the dirty flag, and trigger the set_dirty.Notebook event
385 385 *
386 386 * @method set_dirty
387 387 */
388 388 Notebook.prototype.set_dirty = function (value) {
389 389 if (value === undefined) {
390 390 value = true;
391 391 }
392 392 if (this.dirty == value) {
393 393 return;
394 394 }
395 395 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
396 396 };
397 397
398 398 /**
399 399 * Scroll the top of the page to a given cell.
400 400 *
401 401 * @method scroll_to_cell
402 402 * @param {Number} cell_number An index of the cell to view
403 403 * @param {Number} time Animation time in milliseconds
404 404 * @return {Number} Pixel offset from the top of the container
405 405 */
406 406 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
407 407 var cells = this.get_cells();
408 408 var time = time || 0;
409 409 cell_number = Math.min(cells.length-1,cell_number);
410 410 cell_number = Math.max(0 ,cell_number);
411 411 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
412 412 this.element.animate({scrollTop:scroll_value}, time);
413 413 return scroll_value;
414 414 };
415 415
416 416 /**
417 417 * Scroll to the bottom of the page.
418 418 *
419 419 * @method scroll_to_bottom
420 420 */
421 421 Notebook.prototype.scroll_to_bottom = function () {
422 422 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
423 423 };
424 424
425 425 /**
426 426 * Scroll to the top of the page.
427 427 *
428 428 * @method scroll_to_top
429 429 */
430 430 Notebook.prototype.scroll_to_top = function () {
431 431 this.element.animate({scrollTop:0}, 0);
432 432 };
433 433
434 434
435 435 // Cell indexing, retrieval, etc.
436 436
437 437 /**
438 438 * Get all cell elements in the notebook.
439 439 *
440 440 * @method get_cell_elements
441 441 * @return {jQuery} A selector of all cell elements
442 442 */
443 443 Notebook.prototype.get_cell_elements = function () {
444 444 return this.container.children("div.cell");
445 445 };
446 446
447 447 /**
448 448 * Get a particular cell element.
449 449 *
450 450 * @method get_cell_element
451 451 * @param {Number} index An index of a cell to select
452 452 * @return {jQuery} A selector of the given cell.
453 453 */
454 454 Notebook.prototype.get_cell_element = function (index) {
455 455 var result = null;
456 456 var e = this.get_cell_elements().eq(index);
457 457 if (e.length !== 0) {
458 458 result = e;
459 459 }
460 460 return result;
461 461 };
462 462
463 463 /**
464 464 * Count the cells in this notebook.
465 465 *
466 466 * @method ncells
467 467 * @return {Number} The number of cells in this notebook
468 468 */
469 469 Notebook.prototype.ncells = function () {
470 470 return this.get_cell_elements().length;
471 471 };
472 472
473 473 /**
474 474 * Get all Cell objects in this notebook.
475 475 *
476 476 * @method get_cells
477 477 * @return {Array} This notebook's Cell objects
478 478 */
479 479 // TODO: we are often calling cells as cells()[i], which we should optimize
480 480 // to cells(i) or a new method.
481 481 Notebook.prototype.get_cells = function () {
482 482 return this.get_cell_elements().toArray().map(function (e) {
483 483 return $(e).data("cell");
484 484 });
485 485 };
486 486
487 487 /**
488 488 * Get a Cell object from this notebook.
489 489 *
490 490 * @method get_cell
491 491 * @param {Number} index An index of a cell to retrieve
492 492 * @return {Cell} A particular cell
493 493 */
494 494 Notebook.prototype.get_cell = function (index) {
495 495 var result = null;
496 496 var ce = this.get_cell_element(index);
497 497 if (ce !== null) {
498 498 result = ce.data('cell');
499 499 }
500 500 return result;
501 501 }
502 502
503 503 /**
504 504 * Get the cell below a given cell.
505 505 *
506 506 * @method get_next_cell
507 507 * @param {Cell} cell The provided cell
508 508 * @return {Cell} The next cell
509 509 */
510 510 Notebook.prototype.get_next_cell = function (cell) {
511 511 var result = null;
512 512 var index = this.find_cell_index(cell);
513 513 if (this.is_valid_cell_index(index+1)) {
514 514 result = this.get_cell(index+1);
515 515 }
516 516 return result;
517 517 }
518 518
519 519 /**
520 520 * Get the cell above a given cell.
521 521 *
522 522 * @method get_prev_cell
523 523 * @param {Cell} cell The provided cell
524 524 * @return {Cell} The previous cell
525 525 */
526 526 Notebook.prototype.get_prev_cell = function (cell) {
527 527 // TODO: off-by-one
528 528 // nb.get_prev_cell(nb.get_cell(1)) is null
529 529 var result = null;
530 530 var index = this.find_cell_index(cell);
531 531 if (index !== null && index > 1) {
532 532 result = this.get_cell(index-1);
533 533 }
534 534 return result;
535 535 }
536 536
537 537 /**
538 538 * Get the numeric index of a given cell.
539 539 *
540 540 * @method find_cell_index
541 541 * @param {Cell} cell The provided cell
542 542 * @return {Number} The cell's numeric index
543 543 */
544 544 Notebook.prototype.find_cell_index = function (cell) {
545 545 var result = null;
546 546 this.get_cell_elements().filter(function (index) {
547 547 if ($(this).data("cell") === cell) {
548 548 result = index;
549 549 };
550 550 });
551 551 return result;
552 552 };
553 553
554 554 /**
555 555 * Get a given index , or the selected index if none is provided.
556 556 *
557 557 * @method index_or_selected
558 558 * @param {Number} index A cell's index
559 559 * @return {Number} The given index, or selected index if none is provided.
560 560 */
561 561 Notebook.prototype.index_or_selected = function (index) {
562 562 var i;
563 563 if (index === undefined || index === null) {
564 564 i = this.get_selected_index();
565 565 if (i === null) {
566 566 i = 0;
567 567 }
568 568 } else {
569 569 i = index;
570 570 }
571 571 return i;
572 572 };
573 573
574 574 /**
575 575 * Get the currently selected cell.
576 576 * @method get_selected_cell
577 577 * @return {Cell} The selected cell
578 578 */
579 579 Notebook.prototype.get_selected_cell = function () {
580 580 var index = this.get_selected_index();
581 581 return this.get_cell(index);
582 582 };
583 583
584 584 /**
585 585 * Check whether a cell index is valid.
586 586 *
587 587 * @method is_valid_cell_index
588 588 * @param {Number} index A cell index
589 589 * @return True if the index is valid, false otherwise
590 590 */
591 591 Notebook.prototype.is_valid_cell_index = function (index) {
592 592 if (index !== null && index >= 0 && index < this.ncells()) {
593 593 return true;
594 594 } else {
595 595 return false;
596 596 };
597 597 }
598 598
599 599 /**
600 600 * Get the index of the currently selected cell.
601 601
602 602 * @method get_selected_index
603 603 * @return {Number} The selected cell's numeric index
604 604 */
605 605 Notebook.prototype.get_selected_index = function () {
606 606 var result = null;
607 607 this.get_cell_elements().filter(function (index) {
608 608 if ($(this).data("cell").selected === true) {
609 609 result = index;
610 610 };
611 611 });
612 612 return result;
613 613 };
614 614
615 615
616 616 // Cell selection.
617 617
618 618 /**
619 619 * Programmatically select a cell.
620 620 *
621 621 * @method select
622 622 * @param {Number} index A cell's index
623 623 * @return {Notebook} This notebook
624 624 */
625 625 Notebook.prototype.select = function (index) {
626 626 if (this.is_valid_cell_index(index)) {
627 627 var sindex = this.get_selected_index()
628 628 if (sindex !== null && index !== sindex) {
629 629 this.get_cell(sindex).unselect();
630 630 };
631 631 var cell = this.get_cell(index);
632 632 cell.select();
633 633 if (cell.cell_type === 'heading') {
634 634 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
635 635 {'cell_type':cell.cell_type,level:cell.level}
636 636 );
637 637 } else {
638 638 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
639 639 {'cell_type':cell.cell_type}
640 640 );
641 641 };
642 642 };
643 643 return this;
644 644 };
645 645
646 646 /**
647 647 * Programmatically select the next cell.
648 648 *
649 649 * @method select_next
650 650 * @return {Notebook} This notebook
651 651 */
652 652 Notebook.prototype.select_next = function () {
653 653 var index = this.get_selected_index();
654 654 this.select(index+1);
655 655 return this;
656 656 };
657 657
658 658 /**
659 659 * Programmatically select the previous cell.
660 660 *
661 661 * @method select_prev
662 662 * @return {Notebook} This notebook
663 663 */
664 664 Notebook.prototype.select_prev = function () {
665 665 var index = this.get_selected_index();
666 666 this.select(index-1);
667 667 return this;
668 668 };
669 669
670 670
671 671 // Cell movement
672 672
673 673 /**
674 674 * Move given (or selected) cell up and select it.
675 675 *
676 676 * @method move_cell_up
677 677 * @param [index] {integer} cell index
678 678 * @return {Notebook} This notebook
679 679 **/
680 680 Notebook.prototype.move_cell_up = function (index) {
681 681 var i = this.index_or_selected(index);
682 682 if (this.is_valid_cell_index(i) && i > 0) {
683 683 var pivot = this.get_cell_element(i-1);
684 684 var tomove = this.get_cell_element(i);
685 685 if (pivot !== null && tomove !== null) {
686 686 tomove.detach();
687 687 pivot.before(tomove);
688 688 this.select(i-1);
689 689 };
690 690 this.set_dirty(true);
691 691 };
692 692 return this;
693 693 };
694 694
695 695
696 696 /**
697 697 * Move given (or selected) cell down and select it
698 698 *
699 699 * @method move_cell_down
700 700 * @param [index] {integer} cell index
701 701 * @return {Notebook} This notebook
702 702 **/
703 703 Notebook.prototype.move_cell_down = function (index) {
704 704 var i = this.index_or_selected(index);
705 705 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
706 706 var pivot = this.get_cell_element(i+1);
707 707 var tomove = this.get_cell_element(i);
708 708 if (pivot !== null && tomove !== null) {
709 709 tomove.detach();
710 710 pivot.after(tomove);
711 711 this.select(i+1);
712 712 };
713 713 };
714 714 this.set_dirty();
715 715 return this;
716 716 };
717 717
718 718
719 719 // Insertion, deletion.
720 720
721 721 /**
722 722 * Delete a cell from the notebook.
723 723 *
724 724 * @method delete_cell
725 725 * @param [index] A cell's numeric index
726 726 * @return {Notebook} This notebook
727 727 */
728 728 Notebook.prototype.delete_cell = function (index) {
729 729 var i = this.index_or_selected(index);
730 730 var cell = this.get_selected_cell();
731 731 this.undelete_backup = cell.toJSON();
732 732 $('#undelete_cell').removeClass('disabled');
733 733 if (this.is_valid_cell_index(i)) {
734 734 var ce = this.get_cell_element(i);
735 735 ce.remove();
736 736 if (i === (this.ncells())) {
737 737 this.select(i-1);
738 738 this.undelete_index = i - 1;
739 739 this.undelete_below = true;
740 740 } else {
741 741 this.select(i);
742 742 this.undelete_index = i;
743 743 this.undelete_below = false;
744 744 };
745 745 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
746 746 this.set_dirty(true);
747 747 };
748 748 return this;
749 749 };
750 750
751 751 /**
752 752 * Insert a cell so that after insertion the cell is at given index.
753 753 *
754 754 * Similar to insert_above, but index parameter is mandatory
755 755 *
756 756 * Index will be brought back into the accissible range [0,n]
757 757 *
758 758 * @method insert_cell_at_index
759 759 * @param type {string} in ['code','markdown','heading']
760 760 * @param [index] {int} a valid index where to inser cell
761 761 *
762 762 * @return cell {cell|null} created cell or null
763 763 **/
764 764 Notebook.prototype.insert_cell_at_index = function(type, index){
765 765
766 766 var ncells = this.ncells();
767 767 var index = Math.min(index,ncells);
768 768 index = Math.max(index,0);
769 769 var cell = null;
770 770
771 771 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
772 772 if (type === 'code') {
773 773 cell = new IPython.CodeCell(this.kernel);
774 774 cell.set_input_prompt();
775 775 } else if (type === 'markdown') {
776 776 cell = new IPython.MarkdownCell();
777 777 } else if (type === 'raw') {
778 778 cell = new IPython.RawCell();
779 779 } else if (type === 'heading') {
780 780 cell = new IPython.HeadingCell();
781 781 }
782 782
783 783 if(this._insert_element_at_index(cell.element,index)){
784 784 cell.render();
785 785 this.select(this.find_cell_index(cell));
786 786 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
787 787 this.set_dirty(true);
788 788 }
789 789 }
790 790 return cell;
791 791
792 792 };
793 793
794 794 /**
795 795 * Insert an element at given cell index.
796 796 *
797 797 * @method _insert_element_at_index
798 798 * @param element {dom element} a cell element
799 799 * @param [index] {int} a valid index where to inser cell
800 800 * @private
801 801 *
802 802 * return true if everything whent fine.
803 803 **/
804 804 Notebook.prototype._insert_element_at_index = function(element, index){
805 805 if (element === undefined){
806 806 return false;
807 807 }
808 808
809 809 var ncells = this.ncells();
810 810
811 811 if (ncells === 0) {
812 812 // special case append if empty
813 813 this.element.find('div.end_space').before(element);
814 814 } else if ( ncells === index ) {
815 815 // special case append it the end, but not empty
816 816 this.get_cell_element(index-1).after(element);
817 817 } else if (this.is_valid_cell_index(index)) {
818 818 // otherwise always somewhere to append to
819 819 this.get_cell_element(index).before(element);
820 820 } else {
821 821 return false;
822 822 }
823 823
824 824 if (this.undelete_index !== null && index <= this.undelete_index) {
825 825 this.undelete_index = this.undelete_index + 1;
826 826 this.set_dirty(true);
827 827 }
828 828 return true;
829 829 };
830 830
831 831 /**
832 832 * Insert a cell of given type above given index, or at top
833 833 * of notebook if index smaller than 0.
834 834 *
835 835 * default index value is the one of currently selected cell
836 836 *
837 837 * @method insert_cell_above
838 838 * @param type {string} cell type
839 839 * @param [index] {integer}
840 840 *
841 841 * @return handle to created cell or null
842 842 **/
843 843 Notebook.prototype.insert_cell_above = function (type, index) {
844 844 index = this.index_or_selected(index);
845 845 return this.insert_cell_at_index(type, index);
846 846 };
847 847
848 848 /**
849 849 * Insert a cell of given type below given index, or at bottom
850 850 * of notebook if index greater thatn number of cell
851 851 *
852 852 * default index value is the one of currently selected cell
853 853 *
854 854 * @method insert_cell_below
855 855 * @param type {string} cell type
856 856 * @param [index] {integer}
857 857 *
858 858 * @return handle to created cell or null
859 859 *
860 860 **/
861 861 Notebook.prototype.insert_cell_below = function (type, index) {
862 862 index = this.index_or_selected(index);
863 863 return this.insert_cell_at_index(type, index+1);
864 864 };
865 865
866 866
867 867 /**
868 868 * Insert cell at end of notebook
869 869 *
870 870 * @method insert_cell_at_bottom
871 871 * @param {String} type cell type
872 872 *
873 873 * @return the added cell; or null
874 874 **/
875 875 Notebook.prototype.insert_cell_at_bottom = function (type){
876 876 var len = this.ncells();
877 877 return this.insert_cell_below(type,len-1);
878 878 };
879 879
880 880 /**
881 881 * Turn a cell into a code cell.
882 882 *
883 883 * @method to_code
884 884 * @param {Number} [index] A cell's index
885 885 */
886 886 Notebook.prototype.to_code = function (index) {
887 887 var i = this.index_or_selected(index);
888 888 if (this.is_valid_cell_index(i)) {
889 889 var source_element = this.get_cell_element(i);
890 890 var source_cell = source_element.data("cell");
891 891 if (!(source_cell instanceof IPython.CodeCell)) {
892 892 var target_cell = this.insert_cell_below('code',i);
893 893 var text = source_cell.get_text();
894 894 if (text === source_cell.placeholder) {
895 895 text = '';
896 896 }
897 897 target_cell.set_text(text);
898 898 // make this value the starting point, so that we can only undo
899 899 // to this state, instead of a blank cell
900 900 target_cell.code_mirror.clearHistory();
901 901 source_element.remove();
902 902 this.set_dirty(true);
903 903 };
904 904 };
905 905 };
906 906
907 907 /**
908 908 * Turn a cell into a Markdown cell.
909 909 *
910 910 * @method to_markdown
911 911 * @param {Number} [index] A cell's index
912 912 */
913 913 Notebook.prototype.to_markdown = function (index) {
914 914 var i = this.index_or_selected(index);
915 915 if (this.is_valid_cell_index(i)) {
916 916 var source_element = this.get_cell_element(i);
917 917 var source_cell = source_element.data("cell");
918 918 if (!(source_cell instanceof IPython.MarkdownCell)) {
919 919 var target_cell = this.insert_cell_below('markdown',i);
920 920 var text = source_cell.get_text();
921 921 if (text === source_cell.placeholder) {
922 922 text = '';
923 923 };
924 924 // The edit must come before the set_text.
925 925 target_cell.edit();
926 926 target_cell.set_text(text);
927 927 // make this value the starting point, so that we can only undo
928 928 // to this state, instead of a blank cell
929 929 target_cell.code_mirror.clearHistory();
930 930 source_element.remove();
931 931 this.set_dirty(true);
932 932 };
933 933 };
934 934 };
935 935
936 936 /**
937 937 * Turn a cell into a raw text cell.
938 938 *
939 939 * @method to_raw
940 940 * @param {Number} [index] A cell's index
941 941 */
942 942 Notebook.prototype.to_raw = function (index) {
943 943 var i = this.index_or_selected(index);
944 944 if (this.is_valid_cell_index(i)) {
945 945 var source_element = this.get_cell_element(i);
946 946 var source_cell = source_element.data("cell");
947 947 var target_cell = null;
948 948 if (!(source_cell instanceof IPython.RawCell)) {
949 949 target_cell = this.insert_cell_below('raw',i);
950 950 var text = source_cell.get_text();
951 951 if (text === source_cell.placeholder) {
952 952 text = '';
953 953 };
954 954 // The edit must come before the set_text.
955 955 target_cell.edit();
956 956 target_cell.set_text(text);
957 957 // make this value the starting point, so that we can only undo
958 958 // to this state, instead of a blank cell
959 959 target_cell.code_mirror.clearHistory();
960 960 source_element.remove();
961 961 this.set_dirty(true);
962 962 };
963 963 };
964 964 };
965 965
966 966 /**
967 967 * Turn a cell into a heading cell.
968 968 *
969 969 * @method to_heading
970 970 * @param {Number} [index] A cell's index
971 971 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
972 972 */
973 973 Notebook.prototype.to_heading = function (index, level) {
974 974 level = level || 1;
975 975 var i = this.index_or_selected(index);
976 976 if (this.is_valid_cell_index(i)) {
977 977 var source_element = this.get_cell_element(i);
978 978 var source_cell = source_element.data("cell");
979 979 var target_cell = null;
980 980 if (source_cell instanceof IPython.HeadingCell) {
981 981 source_cell.set_level(level);
982 982 } else {
983 983 target_cell = this.insert_cell_below('heading',i);
984 984 var text = source_cell.get_text();
985 985 if (text === source_cell.placeholder) {
986 986 text = '';
987 987 };
988 988 // The edit must come before the set_text.
989 989 target_cell.set_level(level);
990 990 target_cell.edit();
991 991 target_cell.set_text(text);
992 992 // make this value the starting point, so that we can only undo
993 993 // to this state, instead of a blank cell
994 994 target_cell.code_mirror.clearHistory();
995 995 source_element.remove();
996 996 this.set_dirty(true);
997 997 };
998 998 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
999 999 {'cell_type':'heading',level:level}
1000 1000 );
1001 1001 };
1002 1002 };
1003 1003
1004 1004
1005 1005 // Cut/Copy/Paste
1006 1006
1007 1007 /**
1008 1008 * Enable UI elements for pasting cells.
1009 1009 *
1010 1010 * @method enable_paste
1011 1011 */
1012 1012 Notebook.prototype.enable_paste = function () {
1013 1013 var that = this;
1014 1014 if (!this.paste_enabled) {
1015 1015 $('#paste_cell_replace').removeClass('disabled')
1016 1016 .on('click', function () {that.paste_cell_replace();});
1017 1017 $('#paste_cell_above').removeClass('disabled')
1018 1018 .on('click', function () {that.paste_cell_above();});
1019 1019 $('#paste_cell_below').removeClass('disabled')
1020 1020 .on('click', function () {that.paste_cell_below();});
1021 1021 this.paste_enabled = true;
1022 1022 };
1023 1023 };
1024 1024
1025 1025 /**
1026 1026 * Disable UI elements for pasting cells.
1027 1027 *
1028 1028 * @method disable_paste
1029 1029 */
1030 1030 Notebook.prototype.disable_paste = function () {
1031 1031 if (this.paste_enabled) {
1032 1032 $('#paste_cell_replace').addClass('disabled').off('click');
1033 1033 $('#paste_cell_above').addClass('disabled').off('click');
1034 1034 $('#paste_cell_below').addClass('disabled').off('click');
1035 1035 this.paste_enabled = false;
1036 1036 };
1037 1037 };
1038 1038
1039 1039 /**
1040 1040 * Cut a cell.
1041 1041 *
1042 1042 * @method cut_cell
1043 1043 */
1044 1044 Notebook.prototype.cut_cell = function () {
1045 1045 this.copy_cell();
1046 1046 this.delete_cell();
1047 1047 }
1048 1048
1049 1049 /**
1050 1050 * Copy a cell.
1051 1051 *
1052 1052 * @method copy_cell
1053 1053 */
1054 1054 Notebook.prototype.copy_cell = function () {
1055 1055 var cell = this.get_selected_cell();
1056 1056 this.clipboard = cell.toJSON();
1057 1057 this.enable_paste();
1058 1058 };
1059 1059
1060 1060 /**
1061 1061 * Replace the selected cell with a cell in the clipboard.
1062 1062 *
1063 1063 * @method paste_cell_replace
1064 1064 */
1065 1065 Notebook.prototype.paste_cell_replace = function () {
1066 1066 if (this.clipboard !== null && this.paste_enabled) {
1067 1067 var cell_data = this.clipboard;
1068 1068 var new_cell = this.insert_cell_above(cell_data.cell_type);
1069 1069 new_cell.fromJSON(cell_data);
1070 1070 var old_cell = this.get_next_cell(new_cell);
1071 1071 this.delete_cell(this.find_cell_index(old_cell));
1072 1072 this.select(this.find_cell_index(new_cell));
1073 1073 };
1074 1074 };
1075 1075
1076 1076 /**
1077 1077 * Paste a cell from the clipboard above the selected cell.
1078 1078 *
1079 1079 * @method paste_cell_above
1080 1080 */
1081 1081 Notebook.prototype.paste_cell_above = function () {
1082 1082 if (this.clipboard !== null && this.paste_enabled) {
1083 1083 var cell_data = this.clipboard;
1084 1084 var new_cell = this.insert_cell_above(cell_data.cell_type);
1085 1085 new_cell.fromJSON(cell_data);
1086 1086 };
1087 1087 };
1088 1088
1089 1089 /**
1090 1090 * Paste a cell from the clipboard below the selected cell.
1091 1091 *
1092 1092 * @method paste_cell_below
1093 1093 */
1094 1094 Notebook.prototype.paste_cell_below = function () {
1095 1095 if (this.clipboard !== null && this.paste_enabled) {
1096 1096 var cell_data = this.clipboard;
1097 1097 var new_cell = this.insert_cell_below(cell_data.cell_type);
1098 1098 new_cell.fromJSON(cell_data);
1099 1099 };
1100 1100 };
1101 1101
1102 1102 // Cell undelete
1103 1103
1104 1104 /**
1105 1105 * Restore the most recently deleted cell.
1106 1106 *
1107 1107 * @method undelete
1108 1108 */
1109 1109 Notebook.prototype.undelete = function() {
1110 1110 if (this.undelete_backup !== null && this.undelete_index !== null) {
1111 1111 var current_index = this.get_selected_index();
1112 1112 if (this.undelete_index < current_index) {
1113 1113 current_index = current_index + 1;
1114 1114 }
1115 1115 if (this.undelete_index >= this.ncells()) {
1116 1116 this.select(this.ncells() - 1);
1117 1117 }
1118 1118 else {
1119 1119 this.select(this.undelete_index);
1120 1120 }
1121 1121 var cell_data = this.undelete_backup;
1122 1122 var new_cell = null;
1123 1123 if (this.undelete_below) {
1124 1124 new_cell = this.insert_cell_below(cell_data.cell_type);
1125 1125 } else {
1126 1126 new_cell = this.insert_cell_above(cell_data.cell_type);
1127 1127 }
1128 1128 new_cell.fromJSON(cell_data);
1129 1129 this.select(current_index);
1130 1130 this.undelete_backup = null;
1131 1131 this.undelete_index = null;
1132 1132 }
1133 1133 $('#undelete_cell').addClass('disabled');
1134 1134 }
1135 1135
1136 1136 // Split/merge
1137 1137
1138 1138 /**
1139 1139 * Split the selected cell into two, at the cursor.
1140 1140 *
1141 1141 * @method split_cell
1142 1142 */
1143 1143 Notebook.prototype.split_cell = function () {
1144 1144 // Todo: implement spliting for other cell types.
1145 1145 var cell = this.get_selected_cell();
1146 1146 if (cell.is_splittable()) {
1147 1147 var texta = cell.get_pre_cursor();
1148 1148 var textb = cell.get_post_cursor();
1149 1149 if (cell instanceof IPython.CodeCell) {
1150 1150 cell.set_text(texta);
1151 1151 var new_cell = this.insert_cell_below('code');
1152 1152 new_cell.set_text(textb);
1153 1153 } else if (cell instanceof IPython.MarkdownCell) {
1154 1154 cell.set_text(texta);
1155 1155 cell.render();
1156 1156 var new_cell = this.insert_cell_below('markdown');
1157 1157 new_cell.edit(); // editor must be visible to call set_text
1158 1158 new_cell.set_text(textb);
1159 1159 new_cell.render();
1160 1160 }
1161 1161 };
1162 1162 };
1163 1163
1164 1164 /**
1165 1165 * Combine the selected cell into the cell above it.
1166 1166 *
1167 1167 * @method merge_cell_above
1168 1168 */
1169 1169 Notebook.prototype.merge_cell_above = function () {
1170 1170 var index = this.get_selected_index();
1171 1171 var cell = this.get_cell(index);
1172 1172 if (index > 0) {
1173 1173 var upper_cell = this.get_cell(index-1);
1174 1174 var upper_text = upper_cell.get_text();
1175 1175 var text = cell.get_text();
1176 1176 if (cell instanceof IPython.CodeCell) {
1177 1177 cell.set_text(upper_text+'\n'+text);
1178 1178 } else if (cell instanceof IPython.MarkdownCell) {
1179 1179 cell.edit();
1180 1180 cell.set_text(upper_text+'\n'+text);
1181 1181 cell.render();
1182 1182 };
1183 1183 this.delete_cell(index-1);
1184 1184 this.select(this.find_cell_index(cell));
1185 1185 };
1186 1186 };
1187 1187
1188 1188 /**
1189 1189 * Combine the selected cell into the cell below it.
1190 1190 *
1191 1191 * @method merge_cell_below
1192 1192 */
1193 1193 Notebook.prototype.merge_cell_below = function () {
1194 1194 var index = this.get_selected_index();
1195 1195 var cell = this.get_cell(index);
1196 1196 if (index < this.ncells()-1) {
1197 1197 var lower_cell = this.get_cell(index+1);
1198 1198 var lower_text = lower_cell.get_text();
1199 1199 var text = cell.get_text();
1200 1200 if (cell instanceof IPython.CodeCell) {
1201 1201 cell.set_text(text+'\n'+lower_text);
1202 1202 } else if (cell instanceof IPython.MarkdownCell) {
1203 1203 cell.edit();
1204 1204 cell.set_text(text+'\n'+lower_text);
1205 1205 cell.render();
1206 1206 };
1207 1207 this.delete_cell(index+1);
1208 1208 this.select(this.find_cell_index(cell));
1209 1209 };
1210 1210 };
1211 1211
1212 1212
1213 1213 // Cell collapsing and output clearing
1214 1214
1215 1215 /**
1216 1216 * Hide a cell's output.
1217 1217 *
1218 1218 * @method collapse
1219 1219 * @param {Number} index A cell's numeric index
1220 1220 */
1221 1221 Notebook.prototype.collapse = function (index) {
1222 1222 var i = this.index_or_selected(index);
1223 1223 this.get_cell(i).collapse();
1224 1224 this.set_dirty(true);
1225 1225 };
1226 1226
1227 1227 /**
1228 1228 * Show a cell's output.
1229 1229 *
1230 1230 * @method expand
1231 1231 * @param {Number} index A cell's numeric index
1232 1232 */
1233 1233 Notebook.prototype.expand = function (index) {
1234 1234 var i = this.index_or_selected(index);
1235 1235 this.get_cell(i).expand();
1236 1236 this.set_dirty(true);
1237 1237 };
1238 1238
1239 1239 /** Toggle whether a cell's output is collapsed or expanded.
1240 1240 *
1241 1241 * @method toggle_output
1242 1242 * @param {Number} index A cell's numeric index
1243 1243 */
1244 1244 Notebook.prototype.toggle_output = function (index) {
1245 1245 var i = this.index_or_selected(index);
1246 1246 this.get_cell(i).toggle_output();
1247 1247 this.set_dirty(true);
1248 1248 };
1249 1249
1250 1250 /**
1251 1251 * Toggle a scrollbar for long cell outputs.
1252 1252 *
1253 1253 * @method toggle_output_scroll
1254 1254 * @param {Number} index A cell's numeric index
1255 1255 */
1256 1256 Notebook.prototype.toggle_output_scroll = function (index) {
1257 1257 var i = this.index_or_selected(index);
1258 1258 this.get_cell(i).toggle_output_scroll();
1259 1259 };
1260 1260
1261 1261 /**
1262 1262 * Hide each code cell's output area.
1263 1263 *
1264 1264 * @method collapse_all_output
1265 1265 */
1266 1266 Notebook.prototype.collapse_all_output = function () {
1267 1267 var ncells = this.ncells();
1268 1268 var cells = this.get_cells();
1269 1269 for (var i=0; i<ncells; i++) {
1270 1270 if (cells[i] instanceof IPython.CodeCell) {
1271 1271 cells[i].output_area.collapse();
1272 1272 }
1273 1273 };
1274 1274 // this should not be set if the `collapse` key is removed from nbformat
1275 1275 this.set_dirty(true);
1276 1276 };
1277 1277
1278 1278 /**
1279 1279 * Expand each code cell's output area, and add a scrollbar for long output.
1280 1280 *
1281 1281 * @method scroll_all_output
1282 1282 */
1283 1283 Notebook.prototype.scroll_all_output = function () {
1284 1284 var ncells = this.ncells();
1285 1285 var cells = this.get_cells();
1286 1286 for (var i=0; i<ncells; i++) {
1287 1287 if (cells[i] instanceof IPython.CodeCell) {
1288 1288 cells[i].output_area.expand();
1289 1289 cells[i].output_area.scroll_if_long();
1290 1290 }
1291 1291 };
1292 1292 // this should not be set if the `collapse` key is removed from nbformat
1293 1293 this.set_dirty(true);
1294 1294 };
1295 1295
1296 1296 /**
1297 1297 * Expand each code cell's output area, and remove scrollbars.
1298 1298 *
1299 1299 * @method expand_all_output
1300 1300 */
1301 1301 Notebook.prototype.expand_all_output = function () {
1302 1302 var ncells = this.ncells();
1303 1303 var cells = this.get_cells();
1304 1304 for (var i=0; i<ncells; i++) {
1305 1305 if (cells[i] instanceof IPython.CodeCell) {
1306 1306 cells[i].output_area.expand();
1307 1307 cells[i].output_area.unscroll_area();
1308 1308 }
1309 1309 };
1310 1310 // this should not be set if the `collapse` key is removed from nbformat
1311 1311 this.set_dirty(true);
1312 1312 };
1313 1313
1314 1314 /**
1315 1315 * Clear each code cell's output area.
1316 1316 *
1317 1317 * @method clear_all_output
1318 1318 */
1319 1319 Notebook.prototype.clear_all_output = function () {
1320 1320 var ncells = this.ncells();
1321 1321 var cells = this.get_cells();
1322 1322 for (var i=0; i<ncells; i++) {
1323 1323 if (cells[i] instanceof IPython.CodeCell) {
1324 1324 cells[i].clear_output(true,true,true);
1325 1325 // Make all In[] prompts blank, as well
1326 1326 // TODO: make this configurable (via checkbox?)
1327 1327 cells[i].set_input_prompt();
1328 1328 }
1329 1329 };
1330 1330 this.set_dirty(true);
1331 1331 };
1332 1332
1333 1333
1334 1334 // Other cell functions: line numbers, ...
1335 1335
1336 1336 /**
1337 1337 * Toggle line numbers in the selected cell's input area.
1338 1338 *
1339 1339 * @method cell_toggle_line_numbers
1340 1340 */
1341 1341 Notebook.prototype.cell_toggle_line_numbers = function() {
1342 1342 this.get_selected_cell().toggle_line_numbers();
1343 1343 };
1344 1344
1345 1345 // Kernel related things
1346 1346
1347 1347 /**
1348 1348 * Start a new kernel and set it on each code cell.
1349 1349 *
1350 1350 * @method start_kernel
1351 1351 */
1352 1352 Notebook.prototype.start_kernel = function () {
1353 1353 var base_url = $('body').data('baseKernelUrl') + "kernels";
1354 1354 this.kernel = new IPython.Kernel(base_url);
1355 1355 this.kernel.start(this.notebook_id);
1356 1356 // Now that the kernel has been created, tell the CodeCells about it.
1357 1357 var ncells = this.ncells();
1358 1358 for (var i=0; i<ncells; i++) {
1359 1359 var cell = this.get_cell(i);
1360 1360 if (cell instanceof IPython.CodeCell) {
1361 1361 cell.set_kernel(this.kernel)
1362 1362 };
1363 1363 };
1364 1364 };
1365 1365
1366 1366 /**
1367 1367 * Prompt the user to restart the IPython kernel.
1368 1368 *
1369 1369 * @method restart_kernel
1370 1370 */
1371 1371 Notebook.prototype.restart_kernel = function () {
1372 1372 var that = this;
1373 1373 IPython.dialog.modal({
1374 1374 title : "Restart kernel or continue running?",
1375 1375 body : $("<p/>").html(
1376 1376 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1377 1377 ),
1378 1378 buttons : {
1379 1379 "Continue running" : {},
1380 1380 "Restart" : {
1381 1381 "class" : "btn-danger",
1382 1382 "click" : function() {
1383 1383 that.kernel.restart();
1384 1384 }
1385 1385 }
1386 1386 }
1387 1387 });
1388 1388 };
1389 1389
1390 1390 /**
1391 1391 * Run the selected cell.
1392 1392 *
1393 1393 * Execute or render cell outputs.
1394 1394 *
1395 1395 * @method execute_selected_cell
1396 1396 * @param {Object} options Customize post-execution behavior
1397 1397 */
1398 1398 Notebook.prototype.execute_selected_cell = function (options) {
1399 1399 // add_new: should a new cell be added if we are at the end of the nb
1400 1400 // terminal: execute in terminal mode, which stays in the current cell
1401 1401 var default_options = {terminal: false, add_new: true};
1402 1402 $.extend(default_options, options);
1403 1403 var that = this;
1404 1404 var cell = that.get_selected_cell();
1405 1405 var cell_index = that.find_cell_index(cell);
1406 1406 if (cell instanceof IPython.CodeCell) {
1407 1407 cell.execute();
1408 1408 }
1409 1409 if (default_options.terminal) {
1410 1410 cell.select_all();
1411 1411 } else {
1412 1412 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1413 1413 that.insert_cell_below('code');
1414 1414 // If we are adding a new cell at the end, scroll down to show it.
1415 1415 that.scroll_to_bottom();
1416 1416 } else {
1417 1417 that.select(cell_index+1);
1418 1418 };
1419 1419 };
1420 1420 this.set_dirty(true);
1421 1421 };
1422 1422
1423 1423 /**
1424 1424 * Execute all cells below the selected cell.
1425 1425 *
1426 1426 * @method execute_cells_below
1427 1427 */
1428 1428 Notebook.prototype.execute_cells_below = function () {
1429 1429 this.execute_cell_range(this.get_selected_index(), this.ncells());
1430 1430 this.scroll_to_bottom();
1431 1431 };
1432 1432
1433 1433 /**
1434 1434 * Execute all cells above the selected cell.
1435 1435 *
1436 1436 * @method execute_cells_above
1437 1437 */
1438 1438 Notebook.prototype.execute_cells_above = function () {
1439 1439 this.execute_cell_range(0, this.get_selected_index());
1440 1440 };
1441 1441
1442 1442 /**
1443 1443 * Execute all cells.
1444 1444 *
1445 1445 * @method execute_all_cells
1446 1446 */
1447 1447 Notebook.prototype.execute_all_cells = function () {
1448 1448 this.execute_cell_range(0, this.ncells());
1449 1449 this.scroll_to_bottom();
1450 1450 };
1451 1451
1452 1452 /**
1453 1453 * Execute a contiguous range of cells.
1454 1454 *
1455 1455 * @method execute_cell_range
1456 1456 * @param {Number} start Index of the first cell to execute (inclusive)
1457 1457 * @param {Number} end Index of the last cell to execute (exclusive)
1458 1458 */
1459 1459 Notebook.prototype.execute_cell_range = function (start, end) {
1460 1460 for (var i=start; i<end; i++) {
1461 1461 this.select(i);
1462 1462 this.execute_selected_cell({add_new:false});
1463 1463 };
1464 1464 };
1465 1465
1466 1466 // Persistance and loading
1467 1467
1468 1468 /**
1469 1469 * Getter method for this notebook's ID.
1470 1470 *
1471 1471 * @method get_notebook_id
1472 1472 * @return {String} This notebook's ID
1473 1473 */
1474 1474 Notebook.prototype.get_notebook_id = function () {
1475 1475 return this.notebook_id;
1476 1476 };
1477 1477
1478 1478 /**
1479 1479 * Getter method for this notebook's name.
1480 1480 *
1481 1481 * @method get_notebook_name
1482 1482 * @return {String} This notebook's name
1483 1483 */
1484 1484 Notebook.prototype.get_notebook_name = function () {
1485 1485 return this.notebook_name;
1486 1486 };
1487 1487
1488 1488 /**
1489 1489 * Setter method for this notebook's name.
1490 1490 *
1491 1491 * @method set_notebook_name
1492 1492 * @param {String} name A new name for this notebook
1493 1493 */
1494 1494 Notebook.prototype.set_notebook_name = function (name) {
1495 1495 this.notebook_name = name;
1496 1496 };
1497 1497
1498 1498 /**
1499 1499 * Check that a notebook's name is valid.
1500 1500 *
1501 1501 * @method test_notebook_name
1502 1502 * @param {String} nbname A name for this notebook
1503 1503 * @return {Boolean} True if the name is valid, false if invalid
1504 1504 */
1505 1505 Notebook.prototype.test_notebook_name = function (nbname) {
1506 1506 nbname = nbname || '';
1507 1507 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1508 1508 return true;
1509 1509 } else {
1510 1510 return false;
1511 1511 };
1512 1512 };
1513 1513
1514 1514 /**
1515 1515 * Load a notebook from JSON (.ipynb).
1516 1516 *
1517 1517 * This currently handles one worksheet: others are deleted.
1518 1518 *
1519 1519 * @method fromJSON
1520 1520 * @param {Object} data JSON representation of a notebook
1521 1521 */
1522 1522 Notebook.prototype.fromJSON = function (data) {
1523 1523 var ncells = this.ncells();
1524 1524 var i;
1525 1525 for (i=0; i<ncells; i++) {
1526 1526 // Always delete cell 0 as they get renumbered as they are deleted.
1527 1527 this.delete_cell(0);
1528 1528 };
1529 1529 // Save the metadata and name.
1530 1530 this.metadata = data.metadata;
1531 1531 this.notebook_name = data.metadata.name;
1532 1532 // Only handle 1 worksheet for now.
1533 1533 var worksheet = data.worksheets[0];
1534 1534 if (worksheet !== undefined) {
1535 1535 if (worksheet.metadata) {
1536 1536 this.worksheet_metadata = worksheet.metadata;
1537 1537 }
1538 1538 var new_cells = worksheet.cells;
1539 1539 ncells = new_cells.length;
1540 1540 var cell_data = null;
1541 1541 var new_cell = null;
1542 1542 for (i=0; i<ncells; i++) {
1543 1543 cell_data = new_cells[i];
1544 1544 // VERSIONHACK: plaintext -> raw
1545 1545 // handle never-released plaintext name for raw cells
1546 1546 if (cell_data.cell_type === 'plaintext'){
1547 1547 cell_data.cell_type = 'raw';
1548 1548 }
1549 1549
1550 1550 new_cell = this.insert_cell_below(cell_data.cell_type);
1551 1551 new_cell.fromJSON(cell_data);
1552 1552 };
1553 1553 };
1554 1554 if (data.worksheets.length > 1) {
1555 1555 IPython.dialog.modal({
1556 1556 title : "Multiple worksheets",
1557 1557 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1558 1558 "but this version of IPython can only handle the first. " +
1559 1559 "If you save this notebook, worksheets after the first will be lost.",
1560 1560 buttons : {
1561 1561 OK : {
1562 1562 class : "btn-danger"
1563 1563 }
1564 1564 }
1565 1565 });
1566 1566 }
1567 1567 };
1568 1568
1569 1569 /**
1570 1570 * Dump this notebook into a JSON-friendly object.
1571 1571 *
1572 1572 * @method toJSON
1573 1573 * @return {Object} A JSON-friendly representation of this notebook.
1574 1574 */
1575 1575 Notebook.prototype.toJSON = function () {
1576 1576 var cells = this.get_cells();
1577 1577 var ncells = cells.length;
1578 1578 var cell_array = new Array(ncells);
1579 1579 for (var i=0; i<ncells; i++) {
1580 1580 cell_array[i] = cells[i].toJSON();
1581 1581 };
1582 1582 var data = {
1583 1583 // Only handle 1 worksheet for now.
1584 1584 worksheets : [{
1585 1585 cells: cell_array,
1586 1586 metadata: this.worksheet_metadata
1587 1587 }],
1588 1588 metadata : this.metadata
1589 1589 };
1590 1590 return data;
1591 1591 };
1592 1592
1593 1593 /**
1594 1594 * Start an autosave timer, for periodically saving the notebook.
1595 1595 *
1596 1596 * @method set_autosave_interval
1597 1597 * @param {Integer} interval the autosave interval in milliseconds
1598 1598 */
1599 1599 Notebook.prototype.set_autosave_interval = function (interval) {
1600 1600 var that = this;
1601 1601 // clear previous interval, so we don't get simultaneous timers
1602 1602 if (this.autosave_timer) {
1603 1603 clearInterval(this.autosave_timer);
1604 1604 }
1605 1605
1606 1606 this.autosave_interval = this.minimum_autosave_interval = interval;
1607 1607 if (interval) {
1608 1608 this.autosave_timer = setInterval(function() {
1609 1609 if (that.dirty) {
1610 1610 that.save_notebook();
1611 1611 }
1612 1612 }, interval);
1613 1613 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1614 1614 } else {
1615 1615 this.autosave_timer = null;
1616 1616 $([IPython.events]).trigger("autosave_disabled.Notebook");
1617 1617 };
1618 1618 };
1619 1619
1620 1620 /**
1621 1621 * Save this notebook on the server.
1622 1622 *
1623 1623 * @method save_notebook
1624 1624 */
1625 1625 Notebook.prototype.save_notebook = function () {
1626 1626 // We may want to move the name/id/nbformat logic inside toJSON?
1627 1627 var data = this.toJSON();
1628 1628 data.metadata.name = this.notebook_name;
1629 1629 data.nbformat = this.nbformat;
1630 1630 data.nbformat_minor = this.nbformat_minor;
1631 1631
1632 1632 // time the ajax call for autosave tuning purposes.
1633 1633 var start = new Date().getTime();
1634 1634
1635 1635 // We do the call with settings so we can set cache to false.
1636 1636 var settings = {
1637 1637 processData : false,
1638 1638 cache : false,
1639 1639 type : "PUT",
1640 1640 data : JSON.stringify(data),
1641 1641 headers : {'Content-Type': 'application/json'},
1642 1642 success : $.proxy(this.save_notebook_success, this, start),
1643 1643 error : $.proxy(this.save_notebook_error, this)
1644 1644 };
1645 1645 $([IPython.events]).trigger('notebook_saving.Notebook');
1646 1646 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1647 1647 $.ajax(url, settings);
1648 1648 };
1649 1649
1650 1650 /**
1651 1651 * Success callback for saving a notebook.
1652 1652 *
1653 1653 * @method save_notebook_success
1654 1654 * @param {Integer} start the time when the save request started
1655 1655 * @param {Object} data JSON representation of a notebook
1656 1656 * @param {String} status Description of response status
1657 1657 * @param {jqXHR} xhr jQuery Ajax object
1658 1658 */
1659 1659 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1660 1660 this.set_dirty(false);
1661 1661 $([IPython.events]).trigger('notebook_saved.Notebook');
1662 1662 this._update_autosave_interval(start);
1663 1663 if (this._checkpoint_after_save) {
1664 1664 this.create_checkpoint();
1665 1665 this._checkpoint_after_save = false;
1666 1666 };
1667 1667 };
1668 1668
1669 1669 /**
1670 1670 * update the autosave interval based on how long the last save took
1671 1671 *
1672 1672 * @method _update_autosave_interval
1673 1673 * @param {Integer} timestamp when the save request started
1674 1674 */
1675 1675 Notebook.prototype._update_autosave_interval = function (start) {
1676 1676 var duration = (new Date().getTime() - start);
1677 1677 if (this.autosave_interval) {
1678 1678 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1679 1679 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1680 1680 // round to 10 seconds, otherwise we will be setting a new interval too often
1681 1681 interval = 10000 * Math.round(interval / 10000);
1682 1682 // set new interval, if it's changed
1683 1683 if (interval != this.autosave_interval) {
1684 1684 this.set_autosave_interval(interval);
1685 1685 }
1686 1686 }
1687 1687 };
1688 1688
1689 1689 /**
1690 1690 * Failure callback for saving a notebook.
1691 1691 *
1692 1692 * @method save_notebook_error
1693 1693 * @param {jqXHR} xhr jQuery Ajax object
1694 1694 * @param {String} status Description of response status
1695 1695 * @param {String} error_msg HTTP error message
1696 1696 */
1697 1697 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1698 1698 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1699 1699 };
1700 1700
1701 1701 /**
1702 1702 * Request a notebook's data from the server.
1703 1703 *
1704 1704 * @method load_notebook
1705 1705 * @param {String} notebook_id A notebook to load
1706 1706 */
1707 1707 Notebook.prototype.load_notebook = function (notebook_id) {
1708 1708 var that = this;
1709 1709 this.notebook_id = notebook_id;
1710 1710 // We do the call with settings so we can set cache to false.
1711 1711 var settings = {
1712 1712 processData : false,
1713 1713 cache : false,
1714 1714 type : "GET",
1715 1715 dataType : "json",
1716 1716 success : $.proxy(this.load_notebook_success,this),
1717 1717 error : $.proxy(this.load_notebook_error,this),
1718 1718 };
1719 1719 $([IPython.events]).trigger('notebook_loading.Notebook');
1720 1720 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1721 1721 $.ajax(url, settings);
1722 1722 };
1723 1723
1724 1724 /**
1725 1725 * Success callback for loading a notebook from the server.
1726 1726 *
1727 1727 * Load notebook data from the JSON response.
1728 1728 *
1729 1729 * @method load_notebook_success
1730 1730 * @param {Object} data JSON representation of a notebook
1731 1731 * @param {String} status Description of response status
1732 1732 * @param {jqXHR} xhr jQuery Ajax object
1733 1733 */
1734 1734 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1735 1735 this.fromJSON(data);
1736 1736 if (this.ncells() === 0) {
1737 1737 this.insert_cell_below('code');
1738 1738 };
1739 1739 this.set_dirty(false);
1740 1740 this.select(0);
1741 1741 this.scroll_to_top();
1742 1742 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1743 1743 var msg = "This notebook has been converted from an older " +
1744 1744 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1745 1745 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1746 1746 "newer notebook format will be used and older versions of IPython " +
1747 1747 "may not be able to read it. To keep the older version, close the " +
1748 1748 "notebook without saving it.";
1749 1749 IPython.dialog.modal({
1750 1750 title : "Notebook converted",
1751 1751 body : msg,
1752 1752 buttons : {
1753 1753 OK : {
1754 1754 class : "btn-primary"
1755 1755 }
1756 1756 }
1757 1757 });
1758 1758 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1759 1759 var that = this;
1760 1760 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1761 1761 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1762 1762 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1763 1763 this_vs + ". You can still work with this notebook, but some features " +
1764 1764 "introduced in later notebook versions may not be available."
1765 1765
1766 1766 IPython.dialog.modal({
1767 1767 title : "Newer Notebook",
1768 1768 body : msg,
1769 1769 buttons : {
1770 1770 OK : {
1771 1771 class : "btn-danger"
1772 1772 }
1773 1773 }
1774 1774 });
1775 1775
1776 1776 }
1777 1777
1778 1778 // Create the kernel after the notebook is completely loaded to prevent
1779 1779 // code execution upon loading, which is a security risk.
1780 1780 if (! this.read_only) {
1781 1781 this.start_kernel();
1782 1782 // load our checkpoint list
1783 1783 IPython.notebook.list_checkpoints();
1784 1784 }
1785 1785 $([IPython.events]).trigger('notebook_loaded.Notebook');
1786 1786 };
1787 1787
1788 1788 /**
1789 1789 * Failure callback for loading a notebook from the server.
1790 1790 *
1791 1791 * @method load_notebook_error
1792 1792 * @param {jqXHR} xhr jQuery Ajax object
1793 1793 * @param {String} textStatus Description of response status
1794 1794 * @param {String} errorThrow HTTP error message
1795 1795 */
1796 1796 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1797 if (xhr.status === 500) {
1798 var msg = "An error occurred while loading this notebook. Most likely " +
1799 "this notebook is in a newer format than is supported by this " +
1800 "version of IPython. This version can load notebook formats " +
1801 "v"+this.nbformat+" or earlier.";
1802
1803 IPython.dialog.modal({
1804 title: "Error loading notebook",
1805 body : msg,
1806 buttons : {
1807 "OK": {}
1808 }
1809 });
1797 if (xhr.status === 400) {
1798 var msg = errorThrow;
1799 } else if (xhr.status === 500) {
1800 var msg = "An unknown error occurred while loading this notebook. " +
1801 "This version can load notebook formats " +
1802 "v" + this.nbformat + " or earlier.";
1810 1803 }
1804 IPython.dialog.modal({
1805 title: "Error loading notebook",
1806 body : msg,
1807 buttons : {
1808 "OK": {}
1809 }
1810 });
1811 1811 }
1812 1812
1813 1813 /********************* checkpoint-related *********************/
1814 1814
1815 1815 /**
1816 1816 * Save the notebook then immediately create a checkpoint.
1817 1817 *
1818 1818 * @method save_checkpoint
1819 1819 */
1820 1820 Notebook.prototype.save_checkpoint = function () {
1821 1821 this._checkpoint_after_save = true;
1822 1822 this.save_notebook();
1823 1823 };
1824 1824
1825 1825 /**
1826 1826 * List checkpoints for this notebook.
1827 1827 *
1828 1828 * @method list_checkpoint
1829 1829 */
1830 1830 Notebook.prototype.list_checkpoints = function () {
1831 1831 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1832 1832 $.get(url).done(
1833 1833 $.proxy(this.list_checkpoints_success, this)
1834 1834 ).fail(
1835 1835 $.proxy(this.list_checkpoints_error, this)
1836 1836 );
1837 1837 };
1838 1838
1839 1839 /**
1840 1840 * Success callback for listing checkpoints.
1841 1841 *
1842 1842 * @method list_checkpoint_success
1843 1843 * @param {Object} data JSON representation of a checkpoint
1844 1844 * @param {String} status Description of response status
1845 1845 * @param {jqXHR} xhr jQuery Ajax object
1846 1846 */
1847 1847 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1848 1848 var data = $.parseJSON(data);
1849 1849 if (data.length) {
1850 1850 this.last_checkpoint = data[0];
1851 1851 } else {
1852 1852 this.last_checkpoint = null;
1853 1853 }
1854 1854 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1855 1855 };
1856 1856
1857 1857 /**
1858 1858 * Failure callback for listing a checkpoint.
1859 1859 *
1860 1860 * @method list_checkpoint_error
1861 1861 * @param {jqXHR} xhr jQuery Ajax object
1862 1862 * @param {String} status Description of response status
1863 1863 * @param {String} error_msg HTTP error message
1864 1864 */
1865 1865 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1866 1866 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1867 1867 };
1868 1868
1869 1869 /**
1870 1870 * Create a checkpoint of this notebook on the server from the most recent save.
1871 1871 *
1872 1872 * @method create_checkpoint
1873 1873 */
1874 1874 Notebook.prototype.create_checkpoint = function () {
1875 1875 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1876 1876 $.post(url).done(
1877 1877 $.proxy(this.create_checkpoint_success, this)
1878 1878 ).fail(
1879 1879 $.proxy(this.create_checkpoint_error, this)
1880 1880 );
1881 1881 };
1882 1882
1883 1883 /**
1884 1884 * Success callback for creating a checkpoint.
1885 1885 *
1886 1886 * @method create_checkpoint_success
1887 1887 * @param {Object} data JSON representation of a checkpoint
1888 1888 * @param {String} status Description of response status
1889 1889 * @param {jqXHR} xhr jQuery Ajax object
1890 1890 */
1891 1891 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1892 1892 var data = $.parseJSON(data);
1893 1893 this.last_checkpoint = data;
1894 1894 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1895 1895 };
1896 1896
1897 1897 /**
1898 1898 * Failure callback for creating a checkpoint.
1899 1899 *
1900 1900 * @method create_checkpoint_error
1901 1901 * @param {jqXHR} xhr jQuery Ajax object
1902 1902 * @param {String} status Description of response status
1903 1903 * @param {String} error_msg HTTP error message
1904 1904 */
1905 1905 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1906 1906 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1907 1907 };
1908 1908
1909 1909 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1910 1910 var that = this;
1911 1911 var checkpoint = checkpoint || this.last_checkpoint;
1912 1912 if ( ! checkpoint ) {
1913 1913 console.log("restore dialog, but no checkpoint to restore to!");
1914 1914 return;
1915 1915 }
1916 1916 var body = $('<div/>').append(
1917 1917 $('<p/>').addClass("p-space").text(
1918 1918 "Are you sure you want to revert the notebook to " +
1919 1919 "the latest checkpoint?"
1920 1920 ).append(
1921 1921 $("<strong/>").text(
1922 1922 " This cannot be undone."
1923 1923 )
1924 1924 )
1925 1925 ).append(
1926 1926 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1927 1927 ).append(
1928 1928 $('<p/>').addClass("p-space").text(
1929 1929 Date(checkpoint.last_modified)
1930 1930 ).css("text-align", "center")
1931 1931 );
1932 1932
1933 1933 IPython.dialog.modal({
1934 1934 title : "Revert notebook to checkpoint",
1935 1935 body : body,
1936 1936 buttons : {
1937 1937 Revert : {
1938 1938 class : "btn-danger",
1939 1939 click : function () {
1940 1940 that.restore_checkpoint(checkpoint.checkpoint_id);
1941 1941 }
1942 1942 },
1943 1943 Cancel : {}
1944 1944 }
1945 1945 });
1946 1946 }
1947 1947
1948 1948 /**
1949 1949 * Restore the notebook to a checkpoint state.
1950 1950 *
1951 1951 * @method restore_checkpoint
1952 1952 * @param {String} checkpoint ID
1953 1953 */
1954 1954 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1955 1955 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1956 1956 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1957 1957 $.post(url).done(
1958 1958 $.proxy(this.restore_checkpoint_success, this)
1959 1959 ).fail(
1960 1960 $.proxy(this.restore_checkpoint_error, this)
1961 1961 );
1962 1962 };
1963 1963
1964 1964 /**
1965 1965 * Success callback for restoring a notebook to a checkpoint.
1966 1966 *
1967 1967 * @method restore_checkpoint_success
1968 1968 * @param {Object} data (ignored, should be empty)
1969 1969 * @param {String} status Description of response status
1970 1970 * @param {jqXHR} xhr jQuery Ajax object
1971 1971 */
1972 1972 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1973 1973 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1974 1974 this.load_notebook(this.notebook_id);
1975 1975 };
1976 1976
1977 1977 /**
1978 1978 * Failure callback for restoring a notebook to a checkpoint.
1979 1979 *
1980 1980 * @method restore_checkpoint_error
1981 1981 * @param {jqXHR} xhr jQuery Ajax object
1982 1982 * @param {String} status Description of response status
1983 1983 * @param {String} error_msg HTTP error message
1984 1984 */
1985 1985 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1986 1986 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1987 1987 };
1988 1988
1989 1989 /**
1990 1990 * Delete a notebook checkpoint.
1991 1991 *
1992 1992 * @method delete_checkpoint
1993 1993 * @param {String} checkpoint ID
1994 1994 */
1995 1995 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1996 1996 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
1997 1997 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1998 1998 $.ajax(url, {
1999 1999 type: 'DELETE',
2000 2000 success: $.proxy(this.delete_checkpoint_success, this),
2001 2001 error: $.proxy(this.delete_notebook_error,this)
2002 2002 });
2003 2003 };
2004 2004
2005 2005 /**
2006 2006 * Success callback for deleting a notebook checkpoint
2007 2007 *
2008 2008 * @method delete_checkpoint_success
2009 2009 * @param {Object} data (ignored, should be empty)
2010 2010 * @param {String} status Description of response status
2011 2011 * @param {jqXHR} xhr jQuery Ajax object
2012 2012 */
2013 2013 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2014 2014 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2015 2015 this.load_notebook(this.notebook_id);
2016 2016 };
2017 2017
2018 2018 /**
2019 2019 * Failure callback for deleting a notebook checkpoint.
2020 2020 *
2021 2021 * @method delete_checkpoint_error
2022 2022 * @param {jqXHR} xhr jQuery Ajax object
2023 2023 * @param {String} status Description of response status
2024 2024 * @param {String} error_msg HTTP error message
2025 2025 */
2026 2026 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2027 2027 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2028 2028 };
2029 2029
2030 2030
2031 2031 IPython.Notebook = Notebook;
2032 2032
2033 2033
2034 2034 return IPython;
2035 2035
2036 2036 }(IPython));
2037 2037
@@ -1,221 +1,226 b''
1 1 """The official API for working with notebooks in the current format version.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from __future__ import print_function
20 20 import json
21 21 from xml.etree import ElementTree as ET
22 22 import re
23 23
24 24 from IPython.nbformat import v3
25 25 from IPython.nbformat import v2
26 26 from IPython.nbformat import v1
27 27
28 28 from IPython.nbformat.v3 import (
29 29 NotebookNode,
30 30 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
31 31 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
32 32 nbformat_minor,
33 33 )
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Code
37 37 #-----------------------------------------------------------------------------
38 38
39 39 current_nbformat = nbformat
40 40 current_nbformat_minor = nbformat_minor
41 41
42 42
43 class NBFormatError(ValueError):
44 pass
43 45
44 class NBFormatError(Exception):
46 class NotJSONError(ValueError):
45 47 pass
46 48
47 49
48 50 def parse_json(s, **kwargs):
49 51 """Parse a string into a (nbformat, dict) tuple."""
50 d = json.loads(s, **kwargs)
52 try:
53 d = json.loads(s, **kwargs)
54 except ValueError:
55 raise NotJSONError("Notebook does not appear to be JSON: %r" % s[:16])
51 56 nbf = d.get('nbformat', 1)
52 57 nbm = d.get('nbformat_minor', 0)
53 58 return nbf, nbm, d
54 59
55 60
56 61 def parse_py(s, **kwargs):
57 62 """Parse a string into a (nbformat, string) tuple."""
58 63 nbf = current_nbformat
59 64 nbm = current_nbformat_minor
60 65
61 66 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
62 67 m = re.search(pattern,s)
63 68 if m is not None:
64 69 digits = m.group('nbformat').split('.')
65 70 nbf = int(digits[0])
66 71 if len(digits) > 1:
67 72 nbm = int(digits[1])
68 73
69 74 return nbf, nbm, s
70 75
71 76
72 77 def reads_json(s, **kwargs):
73 78 """Read a JSON notebook from a string and return the NotebookNode object."""
74 79 nbf, minor, d = parse_json(s, **kwargs)
75 80 if nbf == 1:
76 81 nb = v1.to_notebook_json(d, **kwargs)
77 82 nb = v3.convert_to_this_nbformat(nb, orig_version=1)
78 83 elif nbf == 2:
79 84 nb = v2.to_notebook_json(d, **kwargs)
80 85 nb = v3.convert_to_this_nbformat(nb, orig_version=2)
81 86 elif nbf == 3:
82 87 nb = v3.to_notebook_json(d, **kwargs)
83 88 nb = v3.convert_to_this_nbformat(nb, orig_version=3, orig_minor=minor)
84 89 else:
85 raise NBFormatError('Unsupported JSON nbformat version: %i' % nbf)
90 raise NBFormatError('Unsupported JSON nbformat version %s (supported version: %i)' % (nbf, 3))
86 91 return nb
87 92
88 93
89 94 def writes_json(nb, **kwargs):
90 95 return v3.writes_json(nb, **kwargs)
91 96
92 97
93 98 def reads_py(s, **kwargs):
94 99 """Read a .py notebook from a string and return the NotebookNode object."""
95 100 nbf, nbm, s = parse_py(s, **kwargs)
96 101 if nbf == 2:
97 102 nb = v2.to_notebook_py(s, **kwargs)
98 103 elif nbf == 3:
99 104 nb = v3.to_notebook_py(s, **kwargs)
100 105 else:
101 106 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
102 107 return nb
103 108
104 109
105 110 def writes_py(nb, **kwargs):
106 111 return v3.writes_py(nb, **kwargs)
107 112
108 113
109 114 # High level API
110 115
111 116
112 117 def reads(s, format, **kwargs):
113 118 """Read a notebook from a string and return the NotebookNode object.
114 119
115 120 This function properly handles notebooks of any version. The notebook
116 121 returned will always be in the current version's format.
117 122
118 123 Parameters
119 124 ----------
120 125 s : unicode
121 126 The raw unicode string to read the notebook from.
122 127 format : (u'json', u'ipynb', u'py')
123 128 The format that the string is in.
124 129
125 130 Returns
126 131 -------
127 132 nb : NotebookNode
128 133 The notebook that was read.
129 134 """
130 135 format = unicode(format)
131 136 if format == u'json' or format == u'ipynb':
132 137 return reads_json(s, **kwargs)
133 138 elif format == u'py':
134 139 return reads_py(s, **kwargs)
135 140 else:
136 141 raise NBFormatError('Unsupported format: %s' % format)
137 142
138 143
139 144 def writes(nb, format, **kwargs):
140 145 """Write a notebook to a string in a given format in the current nbformat version.
141 146
142 147 This function always writes the notebook in the current nbformat version.
143 148
144 149 Parameters
145 150 ----------
146 151 nb : NotebookNode
147 152 The notebook to write.
148 153 format : (u'json', u'ipynb', u'py')
149 154 The format to write the notebook in.
150 155
151 156 Returns
152 157 -------
153 158 s : unicode
154 159 The notebook string.
155 160 """
156 161 format = unicode(format)
157 162 if format == u'json' or format == u'ipynb':
158 163 return writes_json(nb, **kwargs)
159 164 elif format == u'py':
160 165 return writes_py(nb, **kwargs)
161 166 else:
162 167 raise NBFormatError('Unsupported format: %s' % format)
163 168
164 169
165 170 def read(fp, format, **kwargs):
166 171 """Read a notebook from a file and return the NotebookNode object.
167 172
168 173 This function properly handles notebooks of any version. The notebook
169 174 returned will always be in the current version's format.
170 175
171 176 Parameters
172 177 ----------
173 178 fp : file
174 179 Any file-like object with a read method.
175 180 format : (u'json', u'ipynb', u'py')
176 181 The format that the string is in.
177 182
178 183 Returns
179 184 -------
180 185 nb : NotebookNode
181 186 The notebook that was read.
182 187 """
183 188 return reads(fp.read(), format, **kwargs)
184 189
185 190
186 191 def write(nb, fp, format, **kwargs):
187 192 """Write a notebook to a file in a given format in the current nbformat version.
188 193
189 194 This function always writes the notebook in the current nbformat version.
190 195
191 196 Parameters
192 197 ----------
193 198 nb : NotebookNode
194 199 The notebook to write.
195 200 fp : file
196 201 Any file-like object with a write method.
197 202 format : (u'json', u'ipynb', u'py')
198 203 The format to write the notebook in.
199 204
200 205 Returns
201 206 -------
202 207 s : unicode
203 208 The notebook string.
204 209 """
205 210 return fp.write(writes(nb, format, **kwargs))
206 211
207 212 def _convert_to_metadata():
208 213 """Convert to a notebook having notebook metadata."""
209 214 import glob
210 215 for fname in glob.glob('*.ipynb'):
211 216 print('Converting file:',fname)
212 217 with open(fname,'r') as f:
213 218 nb = read(f,u'json')
214 219 md = new_metadata()
215 220 if u'name' in nb:
216 221 md.name = nb.name
217 222 del nb[u'name']
218 223 nb.metadata = md
219 224 with open(fname,'w') as f:
220 225 write(nb, f, u'json')
221 226
General Comments 0
You need to be logged in to leave comments. Login now