Show More
@@ -0,0 +1,195 b'' | |||
|
1 | #----------------------------------------------------------------------------- | |
|
2 | # Copyright (C) 2011 The IPython Development Team | |
|
3 | # | |
|
4 | # Distributed under the terms of the BSD License. The full license is in | |
|
5 | # the file COPYING.txt, distributed as part of this software. | |
|
6 | #----------------------------------------------------------------------------- | |
|
7 | ||
|
8 | #----------------------------------------------------------------------------- | |
|
9 | # Imports | |
|
10 | #----------------------------------------------------------------------------- | |
|
11 | ||
|
12 | import datetime | |
|
13 | import os | |
|
14 | import uuid | |
|
15 | ||
|
16 | from tornado import web | |
|
17 | ||
|
18 | from IPython.config.configurable import Configurable | |
|
19 | from IPython.nbformat import current | |
|
20 | from IPython.utils.traitlets import Unicode, List, Dict | |
|
21 | ||
|
22 | ||
|
23 | #----------------------------------------------------------------------------- | |
|
24 | # Code | |
|
25 | #----------------------------------------------------------------------------- | |
|
26 | ||
|
27 | ||
|
28 | class NotebookManager(Configurable): | |
|
29 | ||
|
30 | notebook_dir = Unicode(os.getcwd()) | |
|
31 | filename_ext = Unicode(u'.ipynb') | |
|
32 | allowed_formats = List([u'json',u'xml',u'py']) | |
|
33 | ||
|
34 | # Map notebook_ids to notebook names | |
|
35 | mapping = Dict() | |
|
36 | # Map notebook names to notebook_ids | |
|
37 | rev_mapping = Dict() | |
|
38 | ||
|
39 | def list_notebooks(self): | |
|
40 | """List all notebooks in the notebook dir. | |
|
41 | ||
|
42 | This returns a list of dicts of the form:: | |
|
43 | ||
|
44 | dict(notebook_id=notebook,name=name) | |
|
45 | """ | |
|
46 | names = os.listdir(self.notebook_dir) | |
|
47 | names = [name.strip(self.filename_ext)\ | |
|
48 | for name in names if name.endswith(self.filename_ext)] | |
|
49 | data = [] | |
|
50 | for name in names: | |
|
51 | if name not in self.rev_mapping: | |
|
52 | notebook_id = self.new_notebook_id(name) | |
|
53 | else: | |
|
54 | notebook_id = self.rev_mapping[name] | |
|
55 | data.append(dict(notebook_id=notebook_id,name=name)) | |
|
56 | return data | |
|
57 | ||
|
58 | def new_notebook_id(self, name): | |
|
59 | """Generate a new notebook_id for a name and store its mappings.""" | |
|
60 | notebook_id = unicode(uuid.uuid4()) | |
|
61 | self.mapping[notebook_id] = name | |
|
62 | self.rev_mapping[name] = notebook_id | |
|
63 | return notebook_id | |
|
64 | ||
|
65 | def delete_notebook_id(self, notebook_id): | |
|
66 | """Delete a notebook's id only. This doesn't delete the actual notebook.""" | |
|
67 | name = self.mapping[notebook_id] | |
|
68 | del self.mapping[notebook_id] | |
|
69 | del self.rev_mapping[name] | |
|
70 | ||
|
71 | def notebook_exists(self, notebook_id): | |
|
72 | """Does a notebook exist?""" | |
|
73 | if notebook_id not in self.mapping: | |
|
74 | return False | |
|
75 | path = self.get_path_by_name(self.mapping[notebook_id]) | |
|
76 | if not os.path.isfile(path): | |
|
77 | return False | |
|
78 | return True | |
|
79 | ||
|
80 | def find_path(self, notebook_id): | |
|
81 | """Return a full path to a notebook given its notebook_id.""" | |
|
82 | try: | |
|
83 | name = self.mapping[notebook_id] | |
|
84 | except KeyError: | |
|
85 | raise web.HTTPError(404) | |
|
86 | return self.get_path_by_name(name) | |
|
87 | ||
|
88 | def get_path_by_name(self, name): | |
|
89 | """Return a full path to a notebook given its name.""" | |
|
90 | filename = name + self.filename_ext | |
|
91 | path = os.path.join(self.notebook_dir, filename) | |
|
92 | return path | |
|
93 | ||
|
94 | def get_notebook(self, notebook_id, format=u'json'): | |
|
95 | """Get the representation of a notebook in format by notebook_id.""" | |
|
96 | format = unicode(format) | |
|
97 | if format not in self.allowed_formats: | |
|
98 | raise web.HTTPError(415) | |
|
99 | last_modified, nb = self.get_notebook_object(notebook_id) | |
|
100 | data = current.writes(nb, format) | |
|
101 | name = nb.get('name','notebook') | |
|
102 | return last_modified, name, data | |
|
103 | ||
|
104 | def get_notebook_object(self, notebook_id): | |
|
105 | """Get the NotebookNode representation of a notebook by notebook_id.""" | |
|
106 | path = self.find_path(notebook_id) | |
|
107 | if not os.path.isfile(path): | |
|
108 | raise web.HTTPError(404) | |
|
109 | info = os.stat(path) | |
|
110 | last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime) | |
|
111 | try: | |
|
112 | with open(path,'r') as f: | |
|
113 | s = f.read() | |
|
114 | try: | |
|
115 | # v2 and later have xml in the .ipynb files | |
|
116 | nb = current.reads(s, 'xml') | |
|
117 | except: | |
|
118 | # v1 had json in the .ipynb files | |
|
119 | nb = current.reads(s, 'json') | |
|
120 | except: | |
|
121 | raise web.HTTPError(404) | |
|
122 | return last_modified, nb | |
|
123 | ||
|
124 | def save_new_notebook(self, data, format=u'json'): | |
|
125 | """Save a new notebook and return its notebook_id.""" | |
|
126 | if format not in self.allowed_formats: | |
|
127 | raise web.HTTPError(415) | |
|
128 | try: | |
|
129 | nb = current.reads(data, format) | |
|
130 | except: | |
|
131 | raise web.HTTPError(400) | |
|
132 | try: | |
|
133 | name = nb.name | |
|
134 | except AttributeError: | |
|
135 | raise web.HTTPError(400) | |
|
136 | notebook_id = self.new_notebook_id(name) | |
|
137 | self.save_notebook_object(notebook_id, nb) | |
|
138 | return notebook_id | |
|
139 | ||
|
140 | def save_notebook(self, notebook_id, data, format=u'json'): | |
|
141 | """Save an existing notebook by notebook_id.""" | |
|
142 | if format not in self.allowed_formats: | |
|
143 | raise web.HTTPError(415) | |
|
144 | try: | |
|
145 | nb = current.reads(data, format) | |
|
146 | except: | |
|
147 | raise web.HTTPError(400) | |
|
148 | self.save_notebook_object(notebook_id, nb) | |
|
149 | ||
|
150 | def save_notebook_object(self, notebook_id, nb): | |
|
151 | """Save an existing notebook object by notebook_id.""" | |
|
152 | if notebook_id not in self.mapping: | |
|
153 | raise web.HTTPError(404) | |
|
154 | old_name = self.mapping[notebook_id] | |
|
155 | try: | |
|
156 | new_name = nb.name | |
|
157 | except AttributeError: | |
|
158 | raise web.HTTPError(400) | |
|
159 | path = self.get_path_by_name(new_name) | |
|
160 | try: | |
|
161 | with open(path,'w') as f: | |
|
162 | current.write(nb, f, u'xml') | |
|
163 | except: | |
|
164 | raise web.HTTPError(400) | |
|
165 | if old_name != new_name: | |
|
166 | old_path = self.get_path_by_name(old_name) | |
|
167 | if os.path.isfile(old_path): | |
|
168 | os.unlink(old_path) | |
|
169 | self.mapping[notebook_id] = new_name | |
|
170 | self.rev_mapping[new_name] = notebook_id | |
|
171 | ||
|
172 | def delete_notebook(self, notebook_id): | |
|
173 | """Delete notebook by notebook_id.""" | |
|
174 | path = self.find_path(notebook_id) | |
|
175 | if not os.path.isfile(path): | |
|
176 | raise web.HTTPError(404) | |
|
177 | os.unlink(path) | |
|
178 | self.delete_notebook_id(notebook_id) | |
|
179 | ||
|
180 | def new_notebook(self): | |
|
181 | """Create a new notebook and returns its notebook_id.""" | |
|
182 | i = 0 | |
|
183 | while True: | |
|
184 | name = u'Untitled%i' % i | |
|
185 | path = self.get_path_by_name(name) | |
|
186 | if not os.path.isfile(path): | |
|
187 | break | |
|
188 | else: | |
|
189 | i = i+1 | |
|
190 | notebook_id = self.new_notebook_id(name) | |
|
191 | nb = current.new_notebook(name=name, id=notebook_id) | |
|
192 | with open(path,'w') as f: | |
|
193 | current.write(nb, f, u'xml') | |
|
194 | return notebook_id | |
|
195 |
@@ -4,15 +4,14 b'' | |||
|
4 | 4 | # Imports |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | |
|
7 | import datetime | |
|
8 | 7 | import json |
|
9 | 8 | import logging |
|
10 | import os | |
|
11 | 9 | import urllib |
|
12 | 10 | |
|
13 | 11 | from tornado import web |
|
14 | 12 | from tornado import websocket |
|
15 | 13 | |
|
14 | ||
|
16 | 15 | #----------------------------------------------------------------------------- |
|
17 | 16 | # Handlers |
|
18 | 17 | #----------------------------------------------------------------------------- |
@@ -20,7 +19,16 b' from tornado import websocket' | |||
|
20 | 19 | |
|
21 | 20 | class MainHandler(web.RequestHandler): |
|
22 | 21 | def get(self): |
|
23 | self.render('notebook.html') | |
|
22 | notebook_id = self.application.notebook_manager.new_notebook() | |
|
23 | self.render('notebook.html', notebook_id=notebook_id) | |
|
24 | ||
|
25 | ||
|
26 | class NamedNotebookHandler(web.RequestHandler): | |
|
27 | def get(self, notebook_id): | |
|
28 | nbm = self.application.notebook_manager | |
|
29 | if not nbm.notebook_exists(notebook_id): | |
|
30 | raise web.HTTPError(404) | |
|
31 | self.render('notebook.html', notebook_id=notebook_id) | |
|
24 | 32 | |
|
25 | 33 | |
|
26 | 34 | class KernelHandler(web.RequestHandler): |
@@ -30,6 +38,7 b' class KernelHandler(web.RequestHandler):' | |||
|
30 | 38 | |
|
31 | 39 | def post(self): |
|
32 | 40 | kernel_id = self.application.start_kernel() |
|
41 | self.set_header('Location', '/'+kernel_id) | |
|
33 | 42 | self.write(json.dumps(kernel_id)) |
|
34 | 43 | |
|
35 | 44 | |
@@ -65,52 +74,52 b' class ZMQStreamHandler(websocket.WebSocketHandler):' | |||
|
65 | 74 | class NotebookRootHandler(web.RequestHandler): |
|
66 | 75 | |
|
67 | 76 | def get(self): |
|
68 | files = os.listdir(os.getcwd()) | |
|
69 | files = [file for file in files if file.endswith(".ipynb")] | |
|
77 | nbm = self.application.notebook_manager | |
|
78 | files = nbm.list_notebooks() | |
|
70 | 79 | self.write(json.dumps(files)) |
|
71 | 80 | |
|
81 | def post(self): | |
|
82 | nbm = self.application.notebook_manager | |
|
83 | body = self.request.body.strip() | |
|
84 | format = self.get_argument('format', default='json') | |
|
85 | if body: | |
|
86 | notebook_id = nbm.save_new_notebook(body, format) | |
|
87 | else: | |
|
88 | notebook_id = nbm.new_notebook() | |
|
89 | self.set_header('Location', '/'+notebook_id) | |
|
90 | self.write(json.dumps(notebook_id)) | |
|
72 | 91 | |
|
73 | class NotebookHandler(web.RequestHandler): | |
|
74 | ||
|
75 | SUPPORTED_METHODS = ("GET", "DELETE", "PUT") | |
|
76 | 92 | |
|
77 | def find_path(self, filename): | |
|
78 | filename = urllib.unquote(filename) | |
|
79 | if not filename.endswith('.ipynb'): | |
|
80 | raise web.HTTPError(400) | |
|
81 | path = os.path.join(os.getcwd(), filename) | |
|
82 | return path | |
|
93 | class NotebookHandler(web.RequestHandler): | |
|
83 | 94 | |
|
84 | def get(self, filename): | |
|
85 | path = self.find_path(filename) | |
|
86 | if not os.path.isfile(path): | |
|
87 | raise web.HTTPError(404) | |
|
88 | info = os.stat(path) | |
|
89 | self.set_header("Content-Type", "application/unknown") | |
|
90 | self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp( | |
|
91 | info.st_mtime)) | |
|
92 | f = open(path, "r") | |
|
93 | try: | |
|
94 | self.finish(f.read()) | |
|
95 | finally: | |
|
96 | f.close() | |
|
97 | ||
|
98 | def put(self, filename): | |
|
99 | path = self.find_path(filename) | |
|
100 | f = open(path, "w") | |
|
101 | f.write(self.request.body) | |
|
102 | f.close() | |
|
95 | SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE') | |
|
96 | ||
|
97 | def get(self, notebook_id): | |
|
98 | nbm = self.application.notebook_manager | |
|
99 | format = self.get_argument('format', default='json') | |
|
100 | last_mod, name, data = nbm.get_notebook(notebook_id, format) | |
|
101 | if format == u'json': | |
|
102 | self.set_header('Content-Type', 'application/json') | |
|
103 | self.set_header('Content-Disposition','attachment; filename=%s.json' % name) | |
|
104 | elif format == u'xml': | |
|
105 | self.set_header('Content-Type', 'text/xml') | |
|
106 | self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name) | |
|
107 | elif format == u'py': | |
|
108 | self.set_header('Content-Type', 'text/plain') | |
|
109 | self.set_header('Content-Disposition','attachment; filename=%s.py' % name) | |
|
110 | self.set_header('Last-Modified', last_mod) | |
|
111 | self.finish(data) | |
|
112 | ||
|
113 | def put(self, notebook_id): | |
|
114 | nbm = self.application.notebook_manager | |
|
115 | format = self.get_argument('format', default='json') | |
|
116 | nbm.save_notebook(notebook_id, self.request.body, format) | |
|
117 | self.set_status(204) | |
|
103 | 118 | self.finish() |
|
104 | 119 | |
|
105 |
def delete(self, |
|
|
106 | path = self.find_path(filename) | |
|
107 | if not os.path.isfile(path): | |
|
108 | raise web.HTTPError(404) | |
|
109 | os.unlink(path) | |
|
120 | def delete(self, notebook_id): | |
|
121 | nbm = self.application.notebook_manager | |
|
122 | nbm.delete_notebook(notebook_id) | |
|
110 | 123 | self.set_status(204) |
|
111 | 124 | self.finish() |
|
112 | 125 | |
|
113 | ||
|
114 | ||
|
115 | ||
|
116 |
@@ -27,13 +27,15 b' tornado.ioloop = ioloop' | |||
|
27 | 27 | from tornado import httpserver |
|
28 | 28 | from tornado import web |
|
29 | 29 | |
|
30 | from kernelmanager import KernelManager | |
|
31 | from sessionmanager import SessionManager | |
|
32 | from handlers import ( | |
|
33 | MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler, | |
|
30 | from .kernelmanager import KernelManager | |
|
31 | from .sessionmanager import SessionManager | |
|
32 | from .handlers import ( | |
|
33 | MainHandler, NamedNotebookHandler, | |
|
34 | KernelHandler, KernelActionHandler, ZMQStreamHandler, | |
|
34 | 35 | NotebookRootHandler, NotebookHandler |
|
35 | 36 | ) |
|
36 | from routers import IOPubStreamRouter, ShellStreamRouter | |
|
37 | from .routers import IOPubStreamRouter, ShellStreamRouter | |
|
38 | from .notebookmanager import NotebookManager | |
|
37 | 39 | |
|
38 | 40 | from IPython.core.application import BaseIPythonApplication |
|
39 | 41 | from IPython.core.profiledir import ProfileDir |
@@ -53,6 +55,7 b' from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum' | |||
|
53 | 55 | |
|
54 | 56 | _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)" |
|
55 | 57 | _kernel_action_regex = r"(?P<action>restart|interrupt)" |
|
58 | _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)" | |
|
56 | 59 | |
|
57 | 60 | LOCALHOST = '127.0.0.1' |
|
58 | 61 | |
@@ -65,12 +68,13 b' class NotebookWebApplication(web.Application):' | |||
|
65 | 68 | def __init__(self, kernel_manager, log, kernel_argv, config): |
|
66 | 69 | handlers = [ |
|
67 | 70 | (r"/", MainHandler), |
|
71 | (r"/%s" % _notebook_id_regex, NamedNotebookHandler), | |
|
68 | 72 | (r"/kernels", KernelHandler), |
|
69 | 73 | (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler), |
|
70 | 74 | (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')), |
|
71 | 75 | (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')), |
|
72 | 76 | (r"/notebooks", NotebookRootHandler), |
|
73 |
(r"/notebooks/ |
|
|
77 | (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler) | |
|
74 | 78 | ] |
|
75 | 79 | settings = dict( |
|
76 | 80 | template_path=os.path.join(os.path.dirname(__file__), "templates"), |
@@ -84,6 +88,7 b' class NotebookWebApplication(web.Application):' | |||
|
84 | 88 | self.config = config |
|
85 | 89 | self._routers = {} |
|
86 | 90 | self._session_dict = {} |
|
91 | self.notebook_manager = NotebookManager(config=self.config) | |
|
87 | 92 | |
|
88 | 93 | #------------------------------------------------------------------------- |
|
89 | 94 | # Methods for managing kernels and sessions |
@@ -112,7 +112,7 b' class ShellStreamRouter(ZMQStreamRouter):' | |||
|
112 | 112 | def forward_msg(self, client_id, msg): |
|
113 | 113 | if len(msg) < self.max_msg_size: |
|
114 | 114 | msg = json.loads(msg) |
|
115 | to_send = self.session.serialize(msg) | |
|
115 | # to_send = self.session.serialize(msg) | |
|
116 | 116 | self._request_queue.put(client_id) |
|
117 | 117 | self.session.send(self.zmq_stream, msg) |
|
118 | 118 |
@@ -206,6 +206,13 b' span.button_label {' | |||
|
206 | 206 | font-size: 77%; |
|
207 | 207 | } |
|
208 | 208 | |
|
209 | #download_format { | |
|
210 | float: right; | |
|
211 | font-size: 85%; | |
|
212 | width: 60px; | |
|
213 | margin: 1px 5px; | |
|
214 | } | |
|
215 | ||
|
209 | 216 | div#left_panel_splitter { |
|
210 | 217 | width: 8px; |
|
211 | 218 | top: 0px; |
@@ -302,18 +302,28 b' var IPython = (function (IPython) {' | |||
|
302 | 302 | |
|
303 | 303 | CodeCell.prototype.fromJSON = function (data) { |
|
304 | 304 | if (data.cell_type === 'code') { |
|
305 | this.set_code(data.code); | |
|
305 | if (data.input !== undefined) { | |
|
306 | this.set_code(data.input); | |
|
307 | } | |
|
308 | if (data.prompt_number !== undefined) { | |
|
306 | 309 | this.set_input_prompt(data.prompt_number); |
|
310 | } else { | |
|
311 | this.set_input_prompt(); | |
|
312 | }; | |
|
307 | 313 | }; |
|
308 | 314 | }; |
|
309 | 315 | |
|
310 | 316 | |
|
311 | 317 | CodeCell.prototype.toJSON = function () { |
|
312 |
|
|
|
313 |
|
|
|
314 |
|
|
|
315 |
|
|
|
318 | var data = {} | |
|
319 | data.input = this.get_code(); | |
|
320 | data.cell_type = 'code'; | |
|
321 | if (this.input_prompt_number !== ' ') { | |
|
322 | data.prompt_number = this.input_prompt_number | |
|
316 | 323 | }; |
|
324 | data.outputs = []; | |
|
325 | data.language = 'python'; | |
|
326 | return data; | |
|
317 | 327 | }; |
|
318 | 328 | |
|
319 | 329 | IPython.CodeCell = CodeCell; |
@@ -14,14 +14,9 b' var IPython = (function (IPython) {' | |||
|
14 | 14 | this.next_prompt_number = 1; |
|
15 | 15 | this.kernel = null; |
|
16 | 16 | this.msg_cell_map = {}; |
|
17 | this.filename = null; | |
|
18 | this.notebook_load_re = /%notebook load/ | |
|
19 | this.notebook_save_re = /%notebook save/ | |
|
20 | this.notebook_filename_re = /(\w)+.ipynb/ | |
|
21 | 17 | this.style(); |
|
22 | 18 | this.create_elements(); |
|
23 | 19 | this.bind_events(); |
|
24 | this.start_kernel(); | |
|
25 | 20 | }; |
|
26 | 21 | |
|
27 | 22 | |
@@ -473,24 +468,8 b' var IPython = (function (IPython) {' | |||
|
473 | 468 | if (cell instanceof IPython.CodeCell) { |
|
474 | 469 | cell.clear_output(); |
|
475 | 470 | var code = cell.get_code(); |
|
476 | if (that.notebook_load_re.test(code)) { | |
|
477 | // %notebook load | |
|
478 | var code_parts = code.split(' '); | |
|
479 | if (code_parts.length === 3) { | |
|
480 | that.load_notebook(code_parts[2]); | |
|
481 | }; | |
|
482 | } else if (that.notebook_save_re.test(code)) { | |
|
483 | // %notebook save | |
|
484 | var code_parts = code.split(' '); | |
|
485 | if (code_parts.length === 3) { | |
|
486 | that.save_notebook(code_parts[2]); | |
|
487 | } else { | |
|
488 | that.save_notebook() | |
|
489 | }; | |
|
490 | } else { | |
|
491 | 471 |
|
|
492 | 472 |
|
|
493 | }; | |
|
494 | 473 | } else if (cell instanceof IPython.TextCell) { |
|
495 | 474 | cell.render(); |
|
496 | 475 | } |
@@ -532,7 +511,10 b' var IPython = (function (IPython) {' | |||
|
532 | 511 | // Always delete cell 0 as they get renumbered as they are deleted. |
|
533 | 512 | this.delete_cell(0); |
|
534 | 513 | }; |
|
535 | var new_cells = data.cells; | |
|
514 | // Only handle 1 worksheet for now. | |
|
515 | var worksheet = data.worksheets[0]; | |
|
516 | if (worksheet !== undefined) { | |
|
517 | var new_cells = worksheet.cells; | |
|
536 | 518 | ncells = new_cells.length; |
|
537 | 519 | var cell_data = null; |
|
538 | 520 | for (var i=0; i<ncells; i++) { |
@@ -546,6 +528,7 b' var IPython = (function (IPython) {' | |||
|
546 | 528 | }; |
|
547 | 529 | }; |
|
548 | 530 | }; |
|
531 | }; | |
|
549 | 532 | |
|
550 | 533 | |
|
551 | 534 | Notebook.prototype.toJSON = function () { |
@@ -555,67 +538,66 b' var IPython = (function (IPython) {' | |||
|
555 | 538 | for (var i=0; i<ncells; i++) { |
|
556 | 539 | cell_array[i] = cells[i].toJSON(); |
|
557 | 540 | }; |
|
558 |
|
|
|
559 | cells : cell_array | |
|
560 | }; | |
|
561 | return json | |
|
562 | }; | |
|
563 | ||
|
564 | ||
|
565 | Notebook.prototype.test_filename = function (filename) { | |
|
566 | if (this.notebook_filename_re.test(filename)) { | |
|
567 | return true; | |
|
568 | } else { | |
|
569 | var bad_filename = $('<div/>'); | |
|
570 | bad_filename.html( | |
|
571 | "The filename you entered (" + filename + ") is not valid. Notebook filenames must have the following form: foo.ipynb" | |
|
572 | ); | |
|
573 | bad_filename.dialog({title: 'Invalid filename', modal: true}); | |
|
574 | return false; | |
|
575 | }; | |
|
576 | }; | |
|
577 | ||
|
578 | Notebook.prototype.save_notebook = function (filename) { | |
|
579 | this.filename = filename || this.filename || ''; | |
|
580 | if (this.filename === '') { | |
|
581 | var no_filename = $('<div/>'); | |
|
582 | no_filename.html( | |
|
583 | "This notebook has no filename, please specify a filename of the form: foo.ipynb" | |
|
584 | ); | |
|
585 | no_filename.dialog({title: 'Missing filename', modal: true}); | |
|
586 | return; | |
|
541 | data = { | |
|
542 | // Only handle 1 worksheet for now. | |
|
543 | worksheets : [{cells:cell_array}] | |
|
587 | 544 | } |
|
588 | if (!this.test_filename(this.filename)) {return;} | |
|
589 | var thedata = this.toJSON(); | |
|
545 | return data | |
|
546 | }; | |
|
547 | ||
|
548 | Notebook.prototype.save_notebook = function () { | |
|
549 | if (IPython.save_widget.test_notebook_name()) { | |
|
550 | var notebook_id = IPython.save_widget.get_notebook_id(); | |
|
551 | var nbname = IPython.save_widget.get_notebook_name(); | |
|
552 | // We may want to move the name/id/nbformat logic inside toJSON? | |
|
553 | var data = this.toJSON(); | |
|
554 | data.name = nbname; | |
|
555 | data.nbformat = 2; | |
|
556 | data.id = notebook_id | |
|
557 | // We do the call with settings so we can set cache to false. | |
|
590 | 558 | var settings = { |
|
591 | 559 | processData : false, |
|
592 | 560 | cache : false, |
|
593 | 561 | type : "PUT", |
|
594 |
data : JSON.stringify( |
|
|
595 | success : function (data, status, xhr) {console.log(data);} | |
|
562 | data : JSON.stringify(data), | |
|
563 | success : $.proxy(this.notebook_saved,this) | |
|
564 | }; | |
|
565 | IPython.save_widget.status_saving(); | |
|
566 | $.ajax("/notebooks/" + notebook_id, settings); | |
|
596 | 567 | }; |
|
597 | $.ajax("/notebooks/" + this.filename, settings); | |
|
598 | 568 | }; |
|
599 | 569 | |
|
600 | 570 | |
|
601 |
Notebook.prototype. |
|
|
602 | if (!this.test_filename(filename)) {return;} | |
|
603 | var that = this; | |
|
571 | Notebook.prototype.notebook_saved = function (data, status, xhr) { | |
|
572 | IPython.save_widget.status_save(); | |
|
573 | } | |
|
574 | ||
|
575 | ||
|
576 | Notebook.prototype.load_notebook = function () { | |
|
577 | var notebook_id = IPython.save_widget.get_notebook_id(); | |
|
604 | 578 | // We do the call with settings so we can set cache to false. |
|
605 | 579 | var settings = { |
|
606 | 580 | processData : false, |
|
607 | 581 | cache : false, |
|
608 | 582 | type : "GET", |
|
609 | 583 | dataType : "json", |
|
610 | success : function (data, status, xhr) { | |
|
611 | that.fromJSON(data); | |
|
612 | that.filename = filename; | |
|
613 | that.kernel.restart(); | |
|
614 | } | |
|
584 | success : $.proxy(this.notebook_loaded,this) | |
|
615 | 585 | }; |
|
616 | $.ajax("/notebooks/" + filename, settings); | |
|
586 | IPython.save_widget.status_loading(); | |
|
587 | $.ajax("/notebooks/" + notebook_id, settings); | |
|
617 | 588 | } |
|
618 | 589 | |
|
590 | ||
|
591 | Notebook.prototype.notebook_loaded = function (data, status, xhr) { | |
|
592 | this.fromJSON(data); | |
|
593 | if (this.ncells() === 0) { | |
|
594 | this.insert_code_cell_after(); | |
|
595 | }; | |
|
596 | IPython.save_widget.status_save(); | |
|
597 | IPython.save_widget.set_notebook_name(data.name); | |
|
598 | this.start_kernel(); | |
|
599 | }; | |
|
600 | ||
|
619 | 601 | IPython.Notebook = Notebook; |
|
620 | 602 | |
|
621 | 603 | return IPython; |
@@ -30,14 +30,18 b' $(document).ready(function () {' | |||
|
30 | 30 | IPython.kernel_status_widget.status_idle(); |
|
31 | 31 | |
|
32 | 32 | IPython.layout_manager.do_resize(); |
|
33 | IPython.notebook.insert_code_cell_after(); | |
|
34 | IPython.layout_manager.do_resize(); | |
|
35 | 33 | |
|
36 | 34 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
37 | 35 | $('div#header').css('display','block'); |
|
38 | 36 | $('div#notebook_app').css('display','block'); |
|
37 | ||
|
38 | IPython.notebook.load_notebook(); | |
|
39 | ||
|
40 | // Perform these actions after the notebook has been loaded. | |
|
41 | setTimeout(function () { | |
|
42 | IPython.save_widget.update_url(); | |
|
39 | 43 | IPython.layout_manager.do_resize(); |
|
40 | 44 | IPython.pager.collapse(); |
|
41 | IPython.layout_manager.do_resize(); | |
|
45 | }, 100); | |
|
42 | 46 | }); |
|
43 | 47 |
@@ -82,20 +82,31 b' var IPython = (function (IPython) {' | |||
|
82 | 82 | this.content.addClass('ui-helper-clearfix'); |
|
83 | 83 | this.content.find('div.section_row').addClass('ui-helper-clearfix'); |
|
84 | 84 | this.content.find('#new_open').buttonset(); |
|
85 | this.content.find('#download_notebook').button(); | |
|
86 | this.content.find('#upload_notebook').button(); | |
|
87 | this.content.find('#download_format').addClass('ui-widget ui-widget-content'); | |
|
88 | this.content.find('#download_format option').addClass('ui-widget ui-widget-content'); | |
|
85 | 89 | }; |
|
86 | 90 | |
|
87 | 91 | |
|
88 | 92 | NotebookSection.prototype.bind_events = function () { |
|
89 | 93 | PanelSection.prototype.bind_events.apply(this); |
|
94 | var that = this; | |
|
90 | 95 | this.content.find('#new_notebook').click(function () { |
|
91 | alert('Not Implemented'); | |
|
96 | console.log('click!') | |
|
97 | window.open('/'); | |
|
92 | 98 | }); |
|
93 | 99 | this.content.find('#open_notebook').click(function () { |
|
94 | 100 | alert('Not Implemented'); |
|
95 | 101 | }); |
|
102 | this.content.find('#download_notebook').click(function () { | |
|
103 | var format = that.content.find('#download_format').val(); | |
|
104 | var notebook_id = IPython.save_widget.get_notebook_id(); | |
|
105 | var url = '/notebooks/' + notebook_id + '?format=' + format; | |
|
106 | window.open(url,'_newtab'); | |
|
107 | }); | |
|
96 | 108 | }; |
|
97 | 109 | |
|
98 | ||
|
99 | 110 | // CellSection |
|
100 | 111 | |
|
101 | 112 | var CellSection = function () { |
@@ -9,6 +9,7 b' var IPython = (function (IPython) {' | |||
|
9 | 9 | |
|
10 | 10 | var SaveWidget = function (selector) { |
|
11 | 11 | this.selector = selector; |
|
12 | this.notebook_name_re = /[^/\\]+/ | |
|
12 | 13 | if (this.selector !== undefined) { |
|
13 | 14 | this.element = $(selector); |
|
14 | 15 | this.style(); |
@@ -29,7 +30,7 b' var IPython = (function (IPython) {' | |||
|
29 | 30 | SaveWidget.prototype.bind_events = function () { |
|
30 | 31 | var that = this; |
|
31 | 32 | this.element.find('button#save_notebook').click(function () { |
|
32 |
IPython.notebook.save_notebook( |
|
|
33 | IPython.notebook.save_notebook(); | |
|
33 | 34 | }); |
|
34 | 35 | }; |
|
35 | 36 | |
@@ -39,11 +40,59 b' var IPython = (function (IPython) {' | |||
|
39 | 40 | } |
|
40 | 41 | |
|
41 | 42 | |
|
42 | SaveWidget.prototype.set_notebook_name = function (name) { | |
|
43 | this.element.find('input#notebook_name').attr('value',name); | |
|
43 | SaveWidget.prototype.set_notebook_name = function (nbname) { | |
|
44 | this.element.find('input#notebook_name').attr('value',nbname); | |
|
44 | 45 | } |
|
45 | 46 | |
|
46 | 47 | |
|
48 | SaveWidget.prototype.get_notebook_id = function () { | |
|
49 | return this.element.find('span#notebook_id').text() | |
|
50 | }; | |
|
51 | ||
|
52 | ||
|
53 | SaveWidget.prototype.update_url = function () { | |
|
54 | var notebook_id = this.get_notebook_id(); | |
|
55 | if (notebook_id !== '') { | |
|
56 | window.history.replaceState({}, '', notebook_id); | |
|
57 | }; | |
|
58 | }; | |
|
59 | ||
|
60 | ||
|
61 | SaveWidget.prototype.test_notebook_name = function () { | |
|
62 | var nbname = this.get_notebook_name(); | |
|
63 | if (this.notebook_name_re.test(nbname)) { | |
|
64 | return true; | |
|
65 | } else { | |
|
66 | var bad_name = $('<div/>'); | |
|
67 | bad_name.html( | |
|
68 | "The notebook name you entered (" + | |
|
69 | nbname + | |
|
70 | ") is not valid. Notebook names can contain any characters except / and \\" | |
|
71 | ); | |
|
72 | bad_name.dialog({title: 'Invalid name', modal: true}); | |
|
73 | return false; | |
|
74 | }; | |
|
75 | }; | |
|
76 | ||
|
77 | ||
|
78 | SaveWidget.prototype.status_save = function () { | |
|
79 | this.element.find('span.ui-button-text').text('Save'); | |
|
80 | this.element.find('button#save_notebook').button('enable'); | |
|
81 | }; | |
|
82 | ||
|
83 | ||
|
84 | SaveWidget.prototype.status_saving = function () { | |
|
85 | this.element.find('span.ui-button-text').text('Saving'); | |
|
86 | this.element.find('button#save_notebook').button('disable'); | |
|
87 | }; | |
|
88 | ||
|
89 | ||
|
90 | SaveWidget.prototype.status_loading = function () { | |
|
91 | this.element.find('span.ui-button-text').text('Loading'); | |
|
92 | this.element.find('button#save_notebook').button('disable'); | |
|
93 | }; | |
|
94 | ||
|
95 | ||
|
47 | 96 | IPython.SaveWidget = SaveWidget; |
|
48 | 97 | |
|
49 | 98 | return IPython; |
@@ -129,17 +129,19 b' var IPython = (function (IPython) {' | |||
|
129 | 129 | |
|
130 | 130 | TextCell.prototype.fromJSON = function (data) { |
|
131 | 131 | if (data.cell_type === 'text') { |
|
132 | if (data.text !== undefined) { | |
|
132 | 133 | this.set_text(data.text); |
|
133 | 134 | this.grow(this.element.find("textarea.text_cell_input")); |
|
134 | 135 | }; |
|
136 | }; | |
|
135 | 137 | } |
|
136 | 138 | |
|
137 | 139 | |
|
138 | 140 | TextCell.prototype.toJSON = function () { |
|
139 |
|
|
|
140 |
|
|
|
141 |
|
|
|
142 |
|
|
|
141 | var data = {} | |
|
142 | data.cell_type = 'text'; | |
|
143 | data.text = this.get_text(); | |
|
144 | return data; | |
|
143 | 145 | }; |
|
144 | 146 | |
|
145 | 147 | IPython.TextCell = TextCell; |
@@ -33,6 +33,7 b'' | |||
|
33 | 33 | <span id="ipython_notebook"><h1>IPython Notebook</h1></span> |
|
34 | 34 | <span id="save_widget"> |
|
35 | 35 | <input type="text" id="notebook_name" size="20"></textarea> |
|
36 | <span id="notebook_id" style="display:none">{{notebook_id}}</span> | |
|
36 | 37 | <button id="save_notebook">Save</button> |
|
37 | 38 | </span> |
|
38 | 39 | <span id="kernel_status">Idle</span> |
@@ -52,6 +53,18 b'' | |||
|
52 | 53 | </span> |
|
53 | 54 | <span class="section_row_header">Actions</span> |
|
54 | 55 | </div> |
|
56 | <div class="section_row"> | |
|
57 | <span class="section_row_buttons"> | |
|
58 | <button id="download_notebook">Export</button> | |
|
59 | </span> | |
|
60 | <span> | |
|
61 | <select id="download_format"> | |
|
62 | <option value="xml">xml</option> | |
|
63 | <option value="json">json</option> | |
|
64 | <option value="py">py</option> | |
|
65 | </select> | |
|
66 | </span> | |
|
67 | </div> | |
|
55 | 68 | </div> |
|
56 | 69 | </div> |
|
57 | 70 |
@@ -5,6 +5,11 b' import re' | |||
|
5 | 5 | from IPython.nbformat import v2 |
|
6 | 6 | from IPython.nbformat import v1 |
|
7 | 7 | |
|
8 | from IPython.nbformat.v2 import ( | |
|
9 | NotebookNode, | |
|
10 | new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet | |
|
11 | ) | |
|
12 | ||
|
8 | 13 | |
|
9 | 14 | current_nbformat = 2 |
|
10 | 15 |
@@ -36,7 +36,8 b' class PyWriter(NotebookWriter):' | |||
|
36 | 36 | for ws in nb.worksheets: |
|
37 | 37 | for cell in ws.cells: |
|
38 | 38 | if cell.cell_type == 'code': |
|
39 | input = cell.input | |
|
39 | input = cell.get('input') | |
|
40 | if input is not None: | |
|
40 | 41 | lines.extend([u'# <codecell>',u'']) |
|
41 | 42 | lines.extend(input.splitlines()) |
|
42 | 43 | lines.extend([u'',u'# </codecell>']) |
General Comments 0
You need to be logged in to leave comments.
Login now