Basic notebook saving and loading.

* The logic in the server and javascript frontend is there for
  a basic JSON notebook format with a .ipynb extension.
* To save a new notebook: "%notebook save filename.ipynb"
* To save a notebook that is already saved: "%notebook save"
* To load a notebook from the cwd: "notebook load filename.ipynb"
This commit is contained in:
Brian Granger 2011-04-30 22:50:57 -07:00 committed by Brian E. Granger
parent b584914bfb
commit 005820c005
3 changed files with 165 additions and 34 deletions

View File

@ -126,7 +126,7 @@ class NotebookRootHandler(web.RequestHandler):
def get(self): def get(self):
files = os.listdir(os.getcwd()) files = os.listdir(os.getcwd())
files = [file for file in files if file.endswith(".nb")] files = [file for file in files if file.endswith(".ipynb")]
self.write(json.dumps(files)) self.write(json.dumps(files))
@ -135,7 +135,9 @@ class NotebookHandler(web.RequestHandler):
SUPPORTED_METHODS = ("GET", "DELETE", "PUT") SUPPORTED_METHODS = ("GET", "DELETE", "PUT")
def find_path(self, filename): def find_path(self, filename):
filename = urllib.unquote(filename) + ".nb" filename = urllib.unquote(filename)
if not filename.endswith('.ipynb'):
raise web.HTTPError(400)
path = os.path.join(os.getcwd(), filename) path = os.path.join(os.getcwd(), filename)
return path return path

View File

@ -1,26 +1,6 @@
var IPYTHON = {}; var IPYTHON = {};
// $.get("/notebooks/number2.nb",function (data, status, xhr) {console.log(data);});
//
// settings = {
// processData : false,
// cache : false,
// type : "DELETE",
// success : function (data, status, xhr) {console.log(data);}
// }
// $.ajax("/notebooks/number2.nb",settings)
//
// settings = {
// processData : false,
// cache : false,
// type : "PUT",
// success : function (data, status, xhr) {console.log(data);}
// }
// $.ajax("/notebooks/number2.nb",settings)
//============================================================================ //============================================================================
// Utilities // Utilities
//============================================================================ //============================================================================
@ -82,6 +62,7 @@ function fixConsole(txt) {
return txt.trim() return txt.trim()
} }
//============================================================================ //============================================================================
// Notebook // Notebook
//============================================================================ //============================================================================
@ -94,6 +75,10 @@ var Notebook = function (selector) {
this.next_prompt_number = 1; this.next_prompt_number = 1;
this.kernel = null; this.kernel = null;
this.msg_cell_map = {}; this.msg_cell_map = {};
this.filename = null;
this.notebook_load_re = /%notebook load/
this.notebook_save_re = /%notebook save/
this.notebook_filename_re = /(\w)+.ipynb/
this.bind_events(); this.bind_events();
this.start_kernel(); this.start_kernel();
}; };
@ -113,12 +98,29 @@ Notebook.prototype.bind_events = function () {
// The focus is not quite working here. // The focus is not quite working here.
var cell = that.selected_cell(); var cell = that.selected_cell();
var cell_index = that.find_cell_index(cell); var cell_index = that.find_cell_index(cell);
// TODO: the logic here needs to be moved into appropriate
// methods of Notebook.
if (cell instanceof CodeCell) { if (cell instanceof CodeCell) {
event.preventDefault(); event.preventDefault();
cell.clear_output(); cell.clear_output();
cell.hide_output_prompt(); cell.hide_output_prompt();
var msg_id = that.kernel.execute(cell.get_code()); var code = cell.get_code();
that.msg_cell_map[msg_id] = cell.cell_id; if (that.notebook_load_re.test(code)) {
var code_parts = code.split(' ');
if (code_parts.length === 3) {
that.load_notebook(code_parts[2]);
};
} else if (that.notebook_save_re.test(code)) {
var code_parts = code.split(' ');
if (code_parts.length === 3) {
that.save_notebook(code_parts[2]);
} else {
that.save_notebook()
};
} else {
var msg_id = that.kernel.execute(cell.get_code());
that.msg_cell_map[msg_id] = cell.cell_id;
};
if (cell_index === (that.ncells()-1)) { if (cell_index === (that.ncells()-1)) {
that.insert_code_cell_after(); that.insert_code_cell_after();
} else { } else {
@ -129,7 +131,7 @@ Notebook.prototype.bind_events = function () {
event.preventDefault(); event.preventDefault();
var cell = that.selected_cell(); var cell = that.selected_cell();
if (cell instanceof CodeCell) { if (cell instanceof CodeCell) {
var ta = cell.element.find("textarea.input_area"); var ta = cell.element.find("textarea.input_textarea");
ta.val(ta.val() + " "); ta.val(ta.val() + " ");
}; };
}; };
@ -386,8 +388,7 @@ Notebook.prototype.text_to_code = function (index) {
if (source_cell instanceof TextCell) { if (source_cell instanceof TextCell) {
this.insert_code_cell_after(i); this.insert_code_cell_after(i);
var target_cell = this.cells()[i+1]; var target_cell = this.cells()[i+1];
var text = source_element.find("textarea.text_cell_input").val(); target_cell.set_code(source_cell.get_text());
target_cell.element.find("textarea.input_area").val(text);
source_element.remove(); source_element.remove();
}; };
}; };
@ -401,12 +402,9 @@ Notebook.prototype.code_to_text = function (index) {
if (source_cell instanceof CodeCell) { if (source_cell instanceof CodeCell) {
this.insert_text_cell_after(i); this.insert_text_cell_after(i);
var target_cell = this.cells()[i+1]; var target_cell = this.cells()[i+1];
var text = source_element.find("textarea.input_area").val(); var text = source_cell.get_code();
if (text === "") {text = target_cell.placeholder;}; if (text === "") {text = target_cell.placeholder;};
target_cell.element.find("textarea.text_cell_input").val(text); target_cell.set_text(text);
target_cell.element.find("textarea.text_cell_input").html(text);
target_cell.element.find("div.text_cell_render").html(text);
source_element.remove(); source_element.remove();
}; };
}; };
@ -476,6 +474,94 @@ Notebook.prototype._handle_execute_reply = function (reply, cell) {
}; };
// Persistance and loading
Notebook.prototype.fromJSON = function (data) {
var ncells = this.ncells();
for (var i=0; i<ncells; i++) {
// Always delete cell 0 as they get renumbered as they are deleted.
this.delete_cell(0);
};
var new_cells = data.cells;
ncells = new_cells.length;
var cell_data = null;
for (var i=0; i<ncells; i++) {
cell_data = new_cells[i];
if (cell_data.cell_type == 'code') {
this.insert_code_cell_after();
this.selected_cell().fromJSON(cell_data);
} else if (cell_data.cell_type === 'text') {
this.insert_text_cell_after();
this.selected_cell().fromJSON(cell_data);
};
};
};
Notebook.prototype.toJSON = function () {
var cells = this.cells();
var ncells = cells.length;
cell_array = new Array(ncells);
for (var i=0; i<ncells; i++) {
cell_array[i] = cells[i].toJSON();
};
json = {
cells : cell_array
};
return json
};
Notebook.prototype.test_filename = function (filename) {
if (this.notebook_filename_re.test(filename)) {
return true;
} else {
var bad_filename = $('<div/>');
bad_filename.html(
"The filename you entered (" + filename + ") is not valid. Notebook filenames must have the following form: foo.ipynb"
);
bad_filename.dialog({title: 'Invalid filename', modal: true});
return false;
};
};
Notebook.prototype.save_notebook = function (filename) {
this.filename = filename || this.filename || '';
if (this.filename === '') {
var no_filename = $('<div/>');
no_filename.html(
"This notebook has no filename, please specify a filename of the form: foo.ipynb"
);
no_filename.dialog({title: 'Missing filename', modal: true});
return;
}
if (!this.test_filename(this.filename)) {return;}
var thedata = this.toJSON();
var settings = {
processData : false,
cache : false,
type : "PUT",
data : JSON.stringify(thedata),
success : function (data, status, xhr) {console.log(data);}
};
$.ajax("/notebooks/" + this.filename, settings);
};
Notebook.prototype.load_notebook = function (filename) {
if (!this.test_filename(filename)) {return;}
var that = this;
$.getJSON("/notebooks/" + filename,
function (data, status, xhr) {
that.fromJSON(data);
that.filename = filename;
that.kernel.restart();
}
);
}
//============================================================================ //============================================================================
// Cell // Cell
//============================================================================ //============================================================================
@ -620,16 +706,32 @@ CodeCell.prototype.set_output_prompt = function (number) {
this.element.find('div.output_prompt').html('Out[' + n + ']:'); this.element.find('div.output_prompt').html('Out[' + n + ']:');
}; };
CodeCell.prototype.hide_output_prompt = function () { CodeCell.prototype.hide_output_prompt = function () {
this.element.find('div.output_prompt').hide(); this.element.find('div.output_prompt').hide();
}; };
CodeCell.prototype.show_output_prompt = function () { CodeCell.prototype.show_output_prompt = function () {
this.element.find('div.output_prompt').show(); this.element.find('div.output_prompt').show();
}; };
CodeCell.prototype.get_code = function () { CodeCell.prototype.get_code = function () {
return this.element.find("textarea.input_area").val(); return this.element.find("textarea.input_textarea").val();
};
CodeCell.prototype.set_code = function (code) {
return this.element.find("textarea.input_textarea").val(code);
};
CodeCell.prototype.fromJSON = function (data) {
if (data.cell_type === 'code') {
this.set_code(data.code);
this.set_input_prompt(data.prompt_number);
};
}; };
@ -640,6 +742,7 @@ CodeCell.prototype.toJSON = function () {
prompt_number : this.input_prompt_number prompt_number : this.input_prompt_number
}; };
}; };
//============================================================================ //============================================================================
// TextCell // TextCell
//============================================================================ //============================================================================
@ -714,6 +817,32 @@ TextCell.prototype.config_mathjax = function () {
}; };
TextCell.prototype.get_text = function() {
return this.element.find("textarea.text_cell_input").val();
};
TextCell.prototype.set_text = function(text) {
this.element.find("textarea.text_cell_input").val(text);
this.element.find("textarea.text_cell_input").html(text);
this.element.find("div.text_cell_render").html(text);
};
TextCell.prototype.fromJSON = function (data) {
if (data.cell_type === 'text') {
this.set_text(data.text);
};
}
TextCell.prototype.toJSON = function () {
return {
cell_type : 'text',
text : this.get_text(),
};
};
//============================================================================ //============================================================================
// On document ready // On document ready
//============================================================================ //============================================================================

View File

@ -21,7 +21,7 @@
<div id="wrapper"> <div id="wrapper">
<div id="header"> <div id="header">
<span id="ipython_notebook"><h1>|I|Python Notebook</h1></span> <span id="ipython_notebook"><h1>[I]:Python Notebook</h1></span>
</div> </div>