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 | # Imports |
|
4 | # Imports | |
5 | #----------------------------------------------------------------------------- |
|
5 | #----------------------------------------------------------------------------- | |
6 |
|
6 | |||
7 | import datetime |
|
|||
8 | import json |
|
7 | import json | |
9 | import logging |
|
8 | import logging | |
10 | import os |
|
|||
11 | import urllib |
|
9 | import urllib | |
12 |
|
10 | |||
13 | from tornado import web |
|
11 | from tornado import web | |
14 | from tornado import websocket |
|
12 | from tornado import websocket | |
15 |
|
13 | |||
|
14 | ||||
16 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
17 | # Handlers |
|
16 | # Handlers | |
18 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
@@ -20,7 +19,16 b' from tornado import websocket' | |||||
20 |
|
19 | |||
21 | class MainHandler(web.RequestHandler): |
|
20 | class MainHandler(web.RequestHandler): | |
22 | def get(self): |
|
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 | class KernelHandler(web.RequestHandler): |
|
34 | class KernelHandler(web.RequestHandler): | |
@@ -30,6 +38,7 b' class KernelHandler(web.RequestHandler):' | |||||
30 |
|
38 | |||
31 | def post(self): |
|
39 | def post(self): | |
32 | kernel_id = self.application.start_kernel() |
|
40 | kernel_id = self.application.start_kernel() | |
|
41 | self.set_header('Location', '/'+kernel_id) | |||
33 | self.write(json.dumps(kernel_id)) |
|
42 | self.write(json.dumps(kernel_id)) | |
34 |
|
43 | |||
35 |
|
44 | |||
@@ -65,52 +74,52 b' class ZMQStreamHandler(websocket.WebSocketHandler):' | |||||
65 | class NotebookRootHandler(web.RequestHandler): |
|
74 | class NotebookRootHandler(web.RequestHandler): | |
66 |
|
75 | |||
67 | def get(self): |
|
76 | def get(self): | |
68 | files = os.listdir(os.getcwd()) |
|
77 | nbm = self.application.notebook_manager | |
69 | files = [file for file in files if file.endswith(".ipynb")] |
|
78 | files = nbm.list_notebooks() | |
70 | self.write(json.dumps(files)) |
|
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): |
|
93 | class NotebookHandler(web.RequestHandler): | |
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 |
|
|||
83 |
|
94 | |||
84 | def get(self, filename): |
|
95 | SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE') | |
85 | path = self.find_path(filename) |
|
96 | ||
86 | if not os.path.isfile(path): |
|
97 | def get(self, notebook_id): | |
87 | raise web.HTTPError(404) |
|
98 | nbm = self.application.notebook_manager | |
88 | info = os.stat(path) |
|
99 | format = self.get_argument('format', default='json') | |
89 | self.set_header("Content-Type", "application/unknown") |
|
100 | last_mod, name, data = nbm.get_notebook(notebook_id, format) | |
90 | self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp( |
|
101 | if format == u'json': | |
91 | info.st_mtime)) |
|
102 | self.set_header('Content-Type', 'application/json') | |
92 | f = open(path, "r") |
|
103 | self.set_header('Content-Disposition','attachment; filename=%s.json' % name) | |
93 | try: |
|
104 | elif format == u'xml': | |
94 | self.finish(f.read()) |
|
105 | self.set_header('Content-Type', 'text/xml') | |
95 | finally: |
|
106 | self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name) | |
96 | f.close() |
|
107 | elif format == u'py': | |
97 |
|
108 | self.set_header('Content-Type', 'text/plain') | ||
98 | def put(self, filename): |
|
109 | self.set_header('Content-Disposition','attachment; filename=%s.py' % name) | |
99 | path = self.find_path(filename) |
|
110 | self.set_header('Last-Modified', last_mod) | |
100 | f = open(path, "w") |
|
111 | self.finish(data) | |
101 | f.write(self.request.body) |
|
112 | ||
102 | f.close() |
|
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 | self.finish() |
|
118 | self.finish() | |
104 |
|
119 | |||
105 |
def delete(self, |
|
120 | def delete(self, notebook_id): | |
106 | path = self.find_path(filename) |
|
121 | nbm = self.application.notebook_manager | |
107 | if not os.path.isfile(path): |
|
122 | nbm.delete_notebook(notebook_id) | |
108 | raise web.HTTPError(404) |
|
|||
109 | os.unlink(path) |
|
|||
110 | self.set_status(204) |
|
123 | self.set_status(204) | |
111 | self.finish() |
|
124 | self.finish() | |
112 |
|
125 | |||
113 |
|
||||
114 |
|
||||
115 |
|
||||
116 |
|
@@ -27,13 +27,15 b' tornado.ioloop = ioloop' | |||||
27 | from tornado import httpserver |
|
27 | from tornado import httpserver | |
28 | from tornado import web |
|
28 | from tornado import web | |
29 |
|
29 | |||
30 | from kernelmanager import KernelManager |
|
30 | from .kernelmanager import KernelManager | |
31 | from sessionmanager import SessionManager |
|
31 | from .sessionmanager import SessionManager | |
32 | from handlers import ( |
|
32 | from .handlers import ( | |
33 | MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler, |
|
33 | MainHandler, NamedNotebookHandler, | |
|
34 | KernelHandler, KernelActionHandler, ZMQStreamHandler, | |||
34 | NotebookRootHandler, NotebookHandler |
|
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 | from IPython.core.application import BaseIPythonApplication |
|
40 | from IPython.core.application import BaseIPythonApplication | |
39 | from IPython.core.profiledir import ProfileDir |
|
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 | _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)" |
|
56 | _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)" | |
55 | _kernel_action_regex = r"(?P<action>restart|interrupt)" |
|
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 | LOCALHOST = '127.0.0.1' |
|
60 | LOCALHOST = '127.0.0.1' | |
58 |
|
61 | |||
@@ -65,12 +68,13 b' class NotebookWebApplication(web.Application):' | |||||
65 | def __init__(self, kernel_manager, log, kernel_argv, config): |
|
68 | def __init__(self, kernel_manager, log, kernel_argv, config): | |
66 | handlers = [ |
|
69 | handlers = [ | |
67 | (r"/", MainHandler), |
|
70 | (r"/", MainHandler), | |
|
71 | (r"/%s" % _notebook_id_regex, NamedNotebookHandler), | |||
68 | (r"/kernels", KernelHandler), |
|
72 | (r"/kernels", KernelHandler), | |
69 | (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler), |
|
73 | (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler), | |
70 | (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')), |
|
74 | (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')), | |
71 | (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')), |
|
75 | (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')), | |
72 | (r"/notebooks", NotebookRootHandler), |
|
76 | (r"/notebooks", NotebookRootHandler), | |
73 |
(r"/notebooks/ |
|
77 | (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler) | |
74 | ] |
|
78 | ] | |
75 | settings = dict( |
|
79 | settings = dict( | |
76 | template_path=os.path.join(os.path.dirname(__file__), "templates"), |
|
80 | template_path=os.path.join(os.path.dirname(__file__), "templates"), | |
@@ -84,6 +88,7 b' class NotebookWebApplication(web.Application):' | |||||
84 | self.config = config |
|
88 | self.config = config | |
85 | self._routers = {} |
|
89 | self._routers = {} | |
86 | self._session_dict = {} |
|
90 | self._session_dict = {} | |
|
91 | self.notebook_manager = NotebookManager(config=self.config) | |||
87 |
|
92 | |||
88 | #------------------------------------------------------------------------- |
|
93 | #------------------------------------------------------------------------- | |
89 | # Methods for managing kernels and sessions |
|
94 | # Methods for managing kernels and sessions |
@@ -112,7 +112,7 b' class ShellStreamRouter(ZMQStreamRouter):' | |||||
112 | def forward_msg(self, client_id, msg): |
|
112 | def forward_msg(self, client_id, msg): | |
113 | if len(msg) < self.max_msg_size: |
|
113 | if len(msg) < self.max_msg_size: | |
114 | msg = json.loads(msg) |
|
114 | msg = json.loads(msg) | |
115 | to_send = self.session.serialize(msg) |
|
115 | # to_send = self.session.serialize(msg) | |
116 | self._request_queue.put(client_id) |
|
116 | self._request_queue.put(client_id) | |
117 | self.session.send(self.zmq_stream, msg) |
|
117 | self.session.send(self.zmq_stream, msg) | |
118 |
|
118 |
@@ -206,6 +206,13 b' span.button_label {' | |||||
206 | font-size: 77%; |
|
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 | div#left_panel_splitter { |
|
216 | div#left_panel_splitter { | |
210 | width: 8px; |
|
217 | width: 8px; | |
211 | top: 0px; |
|
218 | top: 0px; |
@@ -302,18 +302,28 b' var IPython = (function (IPython) {' | |||||
302 |
|
302 | |||
303 | CodeCell.prototype.fromJSON = function (data) { |
|
303 | CodeCell.prototype.fromJSON = function (data) { | |
304 | if (data.cell_type === 'code') { |
|
304 | if (data.cell_type === 'code') { | |
305 | this.set_code(data.code); |
|
305 | if (data.input !== undefined) { | |
306 |
this.set_ |
|
306 | this.set_code(data.input); | |
|
307 | } | |||
|
308 | if (data.prompt_number !== undefined) { | |||
|
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 | CodeCell.prototype.toJSON = function () { |
|
317 | CodeCell.prototype.toJSON = function () { | |
312 |
|
|
318 | var data = {} | |
313 |
|
|
319 | data.input = this.get_code(); | |
314 |
|
|
320 | data.cell_type = 'code'; | |
315 |
|
|
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 | IPython.CodeCell = CodeCell; |
|
329 | IPython.CodeCell = CodeCell; |
@@ -14,14 +14,9 b' var IPython = (function (IPython) {' | |||||
14 | this.next_prompt_number = 1; |
|
14 | this.next_prompt_number = 1; | |
15 | this.kernel = null; |
|
15 | this.kernel = null; | |
16 | this.msg_cell_map = {}; |
|
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 | this.style(); |
|
17 | this.style(); | |
22 | this.create_elements(); |
|
18 | this.create_elements(); | |
23 | this.bind_events(); |
|
19 | this.bind_events(); | |
24 | this.start_kernel(); |
|
|||
25 | }; |
|
20 | }; | |
26 |
|
21 | |||
27 |
|
22 | |||
@@ -473,24 +468,8 b' var IPython = (function (IPython) {' | |||||
473 | if (cell instanceof IPython.CodeCell) { |
|
468 | if (cell instanceof IPython.CodeCell) { | |
474 | cell.clear_output(); |
|
469 | cell.clear_output(); | |
475 | var code = cell.get_code(); |
|
470 | var code = cell.get_code(); | |
476 | if (that.notebook_load_re.test(code)) { |
|
471 | var msg_id = that.kernel.execute(cell.get_code()); | |
477 | // %notebook load |
|
472 | that.msg_cell_map[msg_id] = cell.cell_id; | |
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 | var msg_id = that.kernel.execute(cell.get_code()); |
|
|||
492 | that.msg_cell_map[msg_id] = cell.cell_id; |
|
|||
493 | }; |
|
|||
494 | } else if (cell instanceof IPython.TextCell) { |
|
473 | } else if (cell instanceof IPython.TextCell) { | |
495 | cell.render(); |
|
474 | cell.render(); | |
496 | } |
|
475 | } | |
@@ -532,18 +511,22 b' var IPython = (function (IPython) {' | |||||
532 | // Always delete cell 0 as they get renumbered as they are deleted. |
|
511 | // Always delete cell 0 as they get renumbered as they are deleted. | |
533 | this.delete_cell(0); |
|
512 | this.delete_cell(0); | |
534 | }; |
|
513 | }; | |
535 | var new_cells = data.cells; |
|
514 | // Only handle 1 worksheet for now. | |
536 | ncells = new_cells.length; |
|
515 | var worksheet = data.worksheets[0]; | |
537 | var cell_data = null; |
|
516 | if (worksheet !== undefined) { | |
538 | for (var i=0; i<ncells; i++) { |
|
517 | var new_cells = worksheet.cells; | |
539 |
|
|
518 | ncells = new_cells.length; | |
540 | if (cell_data.cell_type == 'code') { |
|
519 | var cell_data = null; | |
541 | this.insert_code_cell_after(); |
|
520 | for (var i=0; i<ncells; i++) { | |
542 | this.selected_cell().fromJSON(cell_data); |
|
521 | cell_data = new_cells[i]; | |
543 |
|
|
522 | if (cell_data.cell_type == 'code') { | |
544 |
this.insert_ |
|
523 | this.insert_code_cell_after(); | |
545 | this.selected_cell().fromJSON(cell_data); |
|
524 | this.selected_cell().fromJSON(cell_data); | |
546 | }; |
|
525 | } else if (cell_data.cell_type === 'text') { | |
|
526 | this.insert_text_cell_after(); | |||
|
527 | this.selected_cell().fromJSON(cell_data); | |||
|
528 | }; | |||
|
529 | }; | |||
547 | }; |
|
530 | }; | |
548 | }; |
|
531 | }; | |
549 |
|
532 | |||
@@ -555,67 +538,66 b' var IPython = (function (IPython) {' | |||||
555 | for (var i=0; i<ncells; i++) { |
|
538 | for (var i=0; i<ncells; i++) { | |
556 | cell_array[i] = cells[i].toJSON(); |
|
539 | cell_array[i] = cells[i].toJSON(); | |
557 | }; |
|
540 | }; | |
558 |
|
|
541 | data = { | |
559 | cells : cell_array |
|
542 | // Only handle 1 worksheet for now. | |
|
543 | worksheets : [{cells:cell_array}] | |||
|
544 | } | |||
|
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. | |||
|
558 | var settings = { | |||
|
559 | processData : false, | |||
|
560 | cache : false, | |||
|
561 | type : "PUT", | |||
|
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); | |||
560 | }; |
|
567 | }; | |
561 | return json |
|
|||
562 | }; |
|
568 | }; | |
563 |
|
569 | |||
564 |
|
570 | |||
565 |
Notebook.prototype. |
|
571 | Notebook.prototype.notebook_saved = function (data, status, xhr) { | |
566 | if (this.notebook_filename_re.test(filename)) { |
|
572 | IPython.save_widget.status_save(); | |
567 | return true; |
|
573 | } | |
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; |
|
|||
587 | } |
|
|||
588 | if (!this.test_filename(this.filename)) {return;} |
|
|||
589 | var thedata = this.toJSON(); |
|
|||
590 | var settings = { |
|
|||
591 | processData : false, |
|
|||
592 | cache : false, |
|
|||
593 | type : "PUT", |
|
|||
594 | data : JSON.stringify(thedata), |
|
|||
595 | success : function (data, status, xhr) {console.log(data);} |
|
|||
596 | }; |
|
|||
597 | $.ajax("/notebooks/" + this.filename, settings); |
|
|||
598 | }; |
|
|||
599 |
|
574 | |||
600 |
|
575 | |||
601 |
Notebook.prototype.load_notebook = function ( |
|
576 | Notebook.prototype.load_notebook = function () { | |
602 | if (!this.test_filename(filename)) {return;} |
|
577 | var notebook_id = IPython.save_widget.get_notebook_id(); | |
603 | var that = this; |
|
|||
604 | // We do the call with settings so we can set cache to false. |
|
578 | // We do the call with settings so we can set cache to false. | |
605 | var settings = { |
|
579 | var settings = { | |
606 | processData : false, |
|
580 | processData : false, | |
607 | cache : false, |
|
581 | cache : false, | |
608 | type : "GET", |
|
582 | type : "GET", | |
609 | dataType : "json", |
|
583 | dataType : "json", | |
610 | success : function (data, status, xhr) { |
|
584 | success : $.proxy(this.notebook_loaded,this) | |
611 | that.fromJSON(data); |
|
|||
612 | that.filename = filename; |
|
|||
613 | that.kernel.restart(); |
|
|||
614 | } |
|
|||
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 | IPython.Notebook = Notebook; |
|
601 | IPython.Notebook = Notebook; | |
620 |
|
602 | |||
621 | return IPython; |
|
603 | return IPython; |
@@ -30,14 +30,18 b' $(document).ready(function () {' | |||||
30 | IPython.kernel_status_widget.status_idle(); |
|
30 | IPython.kernel_status_widget.status_idle(); | |
31 |
|
31 | |||
32 | IPython.layout_manager.do_resize(); |
|
32 | IPython.layout_manager.do_resize(); | |
33 | IPython.notebook.insert_code_cell_after(); |
|
|||
34 | IPython.layout_manager.do_resize(); |
|
|||
35 |
|
33 | |||
36 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
34 | // These have display: none in the css file and are made visible here to prevent FLOUC. | |
37 | $('div#header').css('display','block'); |
|
35 | $('div#header').css('display','block'); | |
38 | $('div#notebook_app').css('display','block'); |
|
36 | $('div#notebook_app').css('display','block'); | |
39 | IPython.layout_manager.do_resize(); |
|
37 | ||
40 | IPython.pager.collapse(); |
|
38 | IPython.notebook.load_notebook(); | |
41 | IPython.layout_manager.do_resize(); |
|
39 | ||
|
40 | // Perform these actions after the notebook has been loaded. | |||
|
41 | setTimeout(function () { | |||
|
42 | IPython.save_widget.update_url(); | |||
|
43 | IPython.layout_manager.do_resize(); | |||
|
44 | IPython.pager.collapse(); | |||
|
45 | }, 100); | |||
42 | }); |
|
46 | }); | |
43 |
|
47 |
@@ -82,20 +82,31 b' var IPython = (function (IPython) {' | |||||
82 | this.content.addClass('ui-helper-clearfix'); |
|
82 | this.content.addClass('ui-helper-clearfix'); | |
83 | this.content.find('div.section_row').addClass('ui-helper-clearfix'); |
|
83 | this.content.find('div.section_row').addClass('ui-helper-clearfix'); | |
84 | this.content.find('#new_open').buttonset(); |
|
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 | NotebookSection.prototype.bind_events = function () { |
|
92 | NotebookSection.prototype.bind_events = function () { | |
89 | PanelSection.prototype.bind_events.apply(this); |
|
93 | PanelSection.prototype.bind_events.apply(this); | |
|
94 | var that = this; | |||
90 | this.content.find('#new_notebook').click(function () { |
|
95 | this.content.find('#new_notebook').click(function () { | |
91 | alert('Not Implemented'); |
|
96 | console.log('click!') | |
|
97 | window.open('/'); | |||
92 | }); |
|
98 | }); | |
93 | this.content.find('#open_notebook').click(function () { |
|
99 | this.content.find('#open_notebook').click(function () { | |
94 | alert('Not Implemented'); |
|
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 | // CellSection |
|
110 | // CellSection | |
100 |
|
111 | |||
101 | var CellSection = function () { |
|
112 | var CellSection = function () { |
@@ -9,6 +9,7 b' var IPython = (function (IPython) {' | |||||
9 |
|
9 | |||
10 | var SaveWidget = function (selector) { |
|
10 | var SaveWidget = function (selector) { | |
11 | this.selector = selector; |
|
11 | this.selector = selector; | |
|
12 | this.notebook_name_re = /[^/\\]+/ | |||
12 | if (this.selector !== undefined) { |
|
13 | if (this.selector !== undefined) { | |
13 | this.element = $(selector); |
|
14 | this.element = $(selector); | |
14 | this.style(); |
|
15 | this.style(); | |
@@ -29,7 +30,7 b' var IPython = (function (IPython) {' | |||||
29 | SaveWidget.prototype.bind_events = function () { |
|
30 | SaveWidget.prototype.bind_events = function () { | |
30 | var that = this; |
|
31 | var that = this; | |
31 | this.element.find('button#save_notebook').click(function () { |
|
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 | SaveWidget.prototype.set_notebook_name = function (nbname) { | |
43 | this.element.find('input#notebook_name').attr('value',name); |
|
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 | IPython.SaveWidget = SaveWidget; |
|
96 | IPython.SaveWidget = SaveWidget; | |
48 |
|
97 | |||
49 | return IPython; |
|
98 | return IPython; |
@@ -129,17 +129,19 b' var IPython = (function (IPython) {' | |||||
129 |
|
129 | |||
130 | TextCell.prototype.fromJSON = function (data) { |
|
130 | TextCell.prototype.fromJSON = function (data) { | |
131 | if (data.cell_type === 'text') { |
|
131 | if (data.cell_type === 'text') { | |
132 |
|
|
132 | if (data.text !== undefined) { | |
133 | this.grow(this.element.find("textarea.text_cell_input")); |
|
133 | this.set_text(data.text); | |
|
134 | this.grow(this.element.find("textarea.text_cell_input")); | |||
|
135 | }; | |||
134 | }; |
|
136 | }; | |
135 | } |
|
137 | } | |
136 |
|
138 | |||
137 |
|
139 | |||
138 | TextCell.prototype.toJSON = function () { |
|
140 | TextCell.prototype.toJSON = function () { | |
139 |
|
|
141 | var data = {} | |
140 |
|
|
142 | data.cell_type = 'text'; | |
141 |
|
|
143 | data.text = this.get_text(); | |
142 |
|
|
144 | return data; | |
143 | }; |
|
145 | }; | |
144 |
|
146 | |||
145 | IPython.TextCell = TextCell; |
|
147 | IPython.TextCell = TextCell; |
@@ -33,6 +33,7 b'' | |||||
33 | <span id="ipython_notebook"><h1>IPython Notebook</h1></span> |
|
33 | <span id="ipython_notebook"><h1>IPython Notebook</h1></span> | |
34 | <span id="save_widget"> |
|
34 | <span id="save_widget"> | |
35 | <input type="text" id="notebook_name" size="20"></textarea> |
|
35 | <input type="text" id="notebook_name" size="20"></textarea> | |
|
36 | <span id="notebook_id" style="display:none">{{notebook_id}}</span> | |||
36 | <button id="save_notebook">Save</button> |
|
37 | <button id="save_notebook">Save</button> | |
37 | </span> |
|
38 | </span> | |
38 | <span id="kernel_status">Idle</span> |
|
39 | <span id="kernel_status">Idle</span> | |
@@ -52,6 +53,18 b'' | |||||
52 | </span> |
|
53 | </span> | |
53 | <span class="section_row_header">Actions</span> |
|
54 | <span class="section_row_header">Actions</span> | |
54 | </div> |
|
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 | </div> |
|
68 | </div> | |
56 | </div> |
|
69 | </div> | |
57 |
|
70 |
@@ -5,6 +5,11 b' import re' | |||||
5 | from IPython.nbformat import v2 |
|
5 | from IPython.nbformat import v2 | |
6 | from IPython.nbformat import v1 |
|
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 | current_nbformat = 2 |
|
14 | current_nbformat = 2 | |
10 |
|
15 |
@@ -36,10 +36,11 b' class PyWriter(NotebookWriter):' | |||||
36 | for ws in nb.worksheets: |
|
36 | for ws in nb.worksheets: | |
37 | for cell in ws.cells: |
|
37 | for cell in ws.cells: | |
38 | if cell.cell_type == 'code': |
|
38 | if cell.cell_type == 'code': | |
39 | input = cell.input |
|
39 | input = cell.get('input') | |
40 | lines.extend([u'# <codecell>',u'']) |
|
40 | if input is not None: | |
41 |
lines.extend( |
|
41 | lines.extend([u'# <codecell>',u'']) | |
42 |
lines.extend( |
|
42 | lines.extend(input.splitlines()) | |
|
43 | lines.extend([u'',u'# </codecell>']) | |||
43 | lines.append('') |
|
44 | lines.append('') | |
44 | return unicode('\n'.join(lines)) |
|
45 | return unicode('\n'.join(lines)) | |
45 |
|
46 |
General Comments 0
You need to be logged in to leave comments.
Login now