##// END OF EJS Templates
Merge pull request #4656 from takluyver/nbconvert-service...
Min RK -
r13924:0d046b44 merge
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,117 b''
1 import io
2 import os
3 import zipfile
4
5 from tornado import web
6
7 from ..base.handlers import IPythonHandler, notebook_path_regex
8 from IPython.nbformat.current import to_notebook_json
9 from IPython.nbconvert.exporters.export import exporter_map
10 from IPython.utils import tz
11 from IPython.utils.py3compat import cast_bytes
12
13 import sys
14
15 def find_resource_files(output_files_dir):
16 files = []
17 for dirpath, dirnames, filenames in os.walk(output_files_dir):
18 files.extend([os.path.join(dirpath, f) for f in filenames])
19 return files
20
21 def respond_zip(handler, name, output, resources):
22 """Zip up the output and resource files and respond with the zip file.
23
24 Returns True if it has served a zip file, False if there are no resource
25 files, in which case we serve the plain output file.
26 """
27 # Check if we have resource files we need to zip
28 output_files = resources.get('outputs', None)
29 if not output_files:
30 return False
31
32 # Headers
33 zip_filename = os.path.splitext(name)[0] + '.zip'
34 handler.set_header('Content-Disposition',
35 'attachment; filename="%s"' % zip_filename)
36 handler.set_header('Content-Type', 'application/zip')
37
38 # Prepare the zip file
39 buffer = io.BytesIO()
40 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
41 output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
42 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
43 for filename, data in output_files.items():
44 zipf.writestr(os.path.basename(filename), data)
45 zipf.close()
46
47 handler.finish(buffer.getvalue())
48 return True
49
50 class NbconvertFileHandler(IPythonHandler):
51
52 SUPPORTED_METHODS = ('GET',)
53
54 @web.authenticated
55 def get(self, format, path='', name=None):
56 exporter = exporter_map[format](config=self.config)
57
58 path = path.strip('/')
59 os_path = self.notebook_manager.get_os_path(name, path)
60 if not os.path.isfile(os_path):
61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62
63 info = os.stat(os_path)
64 self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime))
65
66 output, resources = exporter.from_filename(os_path)
67
68 if respond_zip(self, name, output, resources):
69 return
70
71 # Force download if requested
72 if self.get_argument('download', 'false').lower() == 'true':
73 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
74 self.set_header('Content-Disposition',
75 'attachment; filename="%s"' % filename)
76
77 # MIME type
78 if exporter.output_mimetype:
79 self.set_header('Content-Type',
80 '%s; charset=utf-8' % exporter.output_mimetype)
81
82 self.finish(output)
83
84 class NbconvertPostHandler(IPythonHandler):
85 SUPPORTED_METHODS = ('POST',)
86
87 @web.authenticated
88 def post(self, format):
89 exporter = exporter_map[format](config=self.config)
90
91 model = self.get_json_body()
92 nbnode = to_notebook_json(model['content'])
93
94 output, resources = exporter.from_notebook_node(nbnode)
95
96 if respond_zip(self, nbnode.metadata.name, output, resources):
97 return
98
99 # MIME type
100 if exporter.output_mimetype:
101 self.set_header('Content-Type',
102 '%s; charset=utf-8' % exporter.output_mimetype)
103
104 self.finish(output)
105
106 #-----------------------------------------------------------------------------
107 # URL to handler mappings
108 #-----------------------------------------------------------------------------
109
110 _format_regex = r"(?P<format>\w+)"
111
112
113 default_handlers = [
114 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
115 NbconvertFileHandler),
116 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
117 ] No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,120 b''
1 # coding: utf-8
2 import base64
3 import io
4 import json
5 import os
6 from os.path import join as pjoin
7 import shutil
8
9 import requests
10
11 from IPython.html.utils import url_path_join
12 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
13 from IPython.nbformat.current import (new_notebook, write, new_worksheet,
14 new_heading_cell, new_code_cell,
15 new_output)
16
17 class NbconvertAPI(object):
18 """Wrapper for nbconvert API calls."""
19 def __init__(self, base_url):
20 self.base_url = base_url
21
22 def _req(self, verb, path, body=None, params=None):
23 response = requests.request(verb,
24 url_path_join(self.base_url, 'nbconvert', path),
25 data=body, params=params,
26 )
27 response.raise_for_status()
28 return response
29
30 def from_file(self, format, path, name, download=False):
31 return self._req('GET', url_path_join(format, path, name),
32 params={'download':download})
33
34 def from_post(self, format, nbmodel):
35 body = json.dumps(nbmodel)
36 return self._req('POST', format, body)
37
38 def list_formats(self):
39 return self._req('GET', '')
40
41 png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00'
42 b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT'
43 b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82')
44
45 class APITest(NotebookTestBase):
46 def setUp(self):
47 nbdir = self.notebook_dir.name
48
49 if not os.path.isdir(pjoin(nbdir, 'foo')):
50 os.mkdir(pjoin(nbdir, 'foo'))
51
52 nb = new_notebook(name='testnb')
53
54 ws = new_worksheet()
55 nb.worksheets = [ws]
56 ws.cells.append(new_heading_cell(u'Created by test ³'))
57 cc1 = new_code_cell(input=u'print(2*6)')
58 cc1.outputs.append(new_output(output_text=u'12'))
59 cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout'))
60 ws.cells.append(cc1)
61
62 with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w',
63 encoding='utf-8') as f:
64 write(nb, f, format='ipynb')
65
66 self.nbconvert_api = NbconvertAPI(self.base_url())
67
68 def tearDown(self):
69 nbdir = self.notebook_dir.name
70
71 for dname in ['foo']:
72 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
73
74 def test_from_file(self):
75 r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb')
76 self.assertEqual(r.status_code, 200)
77 self.assertIn(u'text/html', r.headers['Content-Type'])
78 self.assertIn(u'Created by test', r.text)
79 self.assertIn(u'print', r.text)
80
81 r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb')
82 self.assertIn(u'text/x-python', r.headers['Content-Type'])
83 self.assertIn(u'print(2*6)', r.text)
84
85 def test_from_file_404(self):
86 with assert_http_error(404):
87 self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb')
88
89 def test_from_file_download(self):
90 r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True)
91 content_disposition = r.headers['Content-Disposition']
92 self.assertIn('attachment', content_disposition)
93 self.assertIn('testnb.py', content_disposition)
94
95 def test_from_file_zip(self):
96 r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True)
97 self.assertIn(u'application/zip', r.headers['Content-Type'])
98 self.assertIn(u'.zip', r.headers['Content-Disposition'])
99
100 def test_from_post(self):
101 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
102 nbmodel = requests.get(nbmodel_url).json()
103
104 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
105 self.assertEqual(r.status_code, 200)
106 self.assertIn(u'text/html', r.headers['Content-Type'])
107 self.assertIn(u'Created by test', r.text)
108 self.assertIn(u'print', r.text)
109
110 r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel)
111 self.assertIn(u'text/x-python', r.headers['Content-Type'])
112 self.assertIn(u'print(2*6)', r.text)
113
114 def test_from_post_zip(self):
115 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
116 nbmodel = requests.get(nbmodel_url).json()
117
118 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
119 self.assertIn(u'application/zip', r.headers['Content-Type'])
120 self.assertIn(u'.zip', r.headers['Content-Disposition'])
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,23 b''
1 import json
2
3 from tornado import web
4
5 from ...base.handlers import IPythonHandler, json_errors
6 from IPython.nbconvert.exporters.export import exporter_map
7
8 class NbconvertRootHandler(IPythonHandler):
9 SUPPORTED_METHODS = ('GET',)
10
11 @web.authenticated
12 @json_errors
13 def get(self):
14 res = {}
15 for format, exporter in exporter_map.items():
16 res[format] = info = {}
17 info['output_mimetype'] = exporter.output_mimetype
18
19 self.finish(json.dumps(res))
20
21 default_handlers = [
22 (r"/api/nbconvert", NbconvertRootHandler),
23 ] No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,31 b''
1 import requests
2
3 from IPython.html.utils import url_path_join
4 from IPython.html.tests.launchnotebook import NotebookTestBase
5
6 class NbconvertAPI(object):
7 """Wrapper for nbconvert API calls."""
8 def __init__(self, base_url):
9 self.base_url = base_url
10
11 def _req(self, verb, path, body=None, params=None):
12 response = requests.request(verb,
13 url_path_join(self.base_url, 'api/nbconvert', path),
14 data=body, params=params,
15 )
16 response.raise_for_status()
17 return response
18
19 def list_formats(self):
20 return self._req('GET', '')
21
22 class APITest(NotebookTestBase):
23 def setUp(self):
24 self.nbconvert_api = NbconvertAPI(self.base_url())
25
26 def test_list_formats(self):
27 formats = self.nbconvert_api.list_formats().json()
28 self.assertIsInstance(formats, dict)
29 self.assertIn('python', formats)
30 self.assertIn('html', formats)
31 self.assertEqual(formats['python']['output_mimetype'], 'text/x-python') No newline at end of file
@@ -0,0 +1,3 b''
1 * Print preview is back in the notebook menus, along with options to
2 download the open notebook in various formats. This is powered by
3 nbconvert.
@@ -1,364 +1,372 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import functools
20 import functools
21 import json
21 import json
22 import logging
22 import logging
23 import os
23 import os
24 import stat
24 import stat
25 import sys
25 import sys
26 import traceback
26 import traceback
27
27
28 from tornado import web
28 from tornado import web
29
29
30 try:
30 try:
31 from tornado.log import app_log
31 from tornado.log import app_log
32 except ImportError:
32 except ImportError:
33 app_log = logging.getLogger()
33 app_log = logging.getLogger()
34
34
35 from IPython.config import Application
35 from IPython.config import Application
36 from IPython.utils.path import filefind
36 from IPython.utils.path import filefind
37 from IPython.utils.py3compat import string_types
37 from IPython.utils.py3compat import string_types
38
38
39 # UF_HIDDEN is a stat flag not defined in the stat module.
39 # UF_HIDDEN is a stat flag not defined in the stat module.
40 # It is used by BSD to indicate hidden files.
40 # It is used by BSD to indicate hidden files.
41 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
41 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Top-level handlers
44 # Top-level handlers
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 class RequestHandler(web.RequestHandler):
47 class RequestHandler(web.RequestHandler):
48 """RequestHandler with default variable setting."""
48 """RequestHandler with default variable setting."""
49
49
50 def render(*args, **kwargs):
50 def render(*args, **kwargs):
51 kwargs.setdefault('message', '')
51 kwargs.setdefault('message', '')
52 return web.RequestHandler.render(*args, **kwargs)
52 return web.RequestHandler.render(*args, **kwargs)
53
53
54 class AuthenticatedHandler(RequestHandler):
54 class AuthenticatedHandler(RequestHandler):
55 """A RequestHandler with an authenticated user."""
55 """A RequestHandler with an authenticated user."""
56
56
57 def clear_login_cookie(self):
57 def clear_login_cookie(self):
58 self.clear_cookie(self.cookie_name)
58 self.clear_cookie(self.cookie_name)
59
59
60 def get_current_user(self):
60 def get_current_user(self):
61 user_id = self.get_secure_cookie(self.cookie_name)
61 user_id = self.get_secure_cookie(self.cookie_name)
62 # For now the user_id should not return empty, but it could eventually
62 # For now the user_id should not return empty, but it could eventually
63 if user_id == '':
63 if user_id == '':
64 user_id = 'anonymous'
64 user_id = 'anonymous'
65 if user_id is None:
65 if user_id is None:
66 # prevent extra Invalid cookie sig warnings:
66 # prevent extra Invalid cookie sig warnings:
67 self.clear_login_cookie()
67 self.clear_login_cookie()
68 if not self.login_available:
68 if not self.login_available:
69 user_id = 'anonymous'
69 user_id = 'anonymous'
70 return user_id
70 return user_id
71
71
72 @property
72 @property
73 def cookie_name(self):
73 def cookie_name(self):
74 default_cookie_name = 'username-{host}'.format(
74 default_cookie_name = 'username-{host}'.format(
75 host=self.request.host,
75 host=self.request.host,
76 ).replace(':', '-')
76 ).replace(':', '-')
77 return self.settings.get('cookie_name', default_cookie_name)
77 return self.settings.get('cookie_name', default_cookie_name)
78
78
79 @property
79 @property
80 def password(self):
80 def password(self):
81 """our password"""
81 """our password"""
82 return self.settings.get('password', '')
82 return self.settings.get('password', '')
83
83
84 @property
84 @property
85 def logged_in(self):
85 def logged_in(self):
86 """Is a user currently logged in?
86 """Is a user currently logged in?
87
87
88 """
88 """
89 user = self.get_current_user()
89 user = self.get_current_user()
90 return (user and not user == 'anonymous')
90 return (user and not user == 'anonymous')
91
91
92 @property
92 @property
93 def login_available(self):
93 def login_available(self):
94 """May a user proceed to log in?
94 """May a user proceed to log in?
95
95
96 This returns True if login capability is available, irrespective of
96 This returns True if login capability is available, irrespective of
97 whether the user is already logged in or not.
97 whether the user is already logged in or not.
98
98
99 """
99 """
100 return bool(self.settings.get('password', ''))
100 return bool(self.settings.get('password', ''))
101
101
102
102
103 class IPythonHandler(AuthenticatedHandler):
103 class IPythonHandler(AuthenticatedHandler):
104 """IPython-specific extensions to authenticated handling
104 """IPython-specific extensions to authenticated handling
105
105
106 Mostly property shortcuts to IPython-specific settings.
106 Mostly property shortcuts to IPython-specific settings.
107 """
107 """
108
108
109 @property
109 @property
110 def config(self):
110 def config(self):
111 return self.settings.get('config', None)
111 return self.settings.get('config', None)
112
112
113 @property
113 @property
114 def log(self):
114 def log(self):
115 """use the IPython log by default, falling back on tornado's logger"""
115 """use the IPython log by default, falling back on tornado's logger"""
116 if Application.initialized():
116 if Application.initialized():
117 return Application.instance().log
117 return Application.instance().log
118 else:
118 else:
119 return app_log
119 return app_log
120
120
121 @property
121 @property
122 def use_less(self):
122 def use_less(self):
123 """Use less instead of css in templates"""
123 """Use less instead of css in templates"""
124 return self.settings.get('use_less', False)
124 return self.settings.get('use_less', False)
125
125
126 #---------------------------------------------------------------
126 #---------------------------------------------------------------
127 # URLs
127 # URLs
128 #---------------------------------------------------------------
128 #---------------------------------------------------------------
129
129
130 @property
130 @property
131 def ws_url(self):
131 def ws_url(self):
132 """websocket url matching the current request
132 """websocket url matching the current request
133
133
134 By default, this is just `''`, indicating that it should match
134 By default, this is just `''`, indicating that it should match
135 the same host, protocol, port, etc.
135 the same host, protocol, port, etc.
136 """
136 """
137 return self.settings.get('websocket_url', '')
137 return self.settings.get('websocket_url', '')
138
138
139 @property
139 @property
140 def mathjax_url(self):
140 def mathjax_url(self):
141 return self.settings.get('mathjax_url', '')
141 return self.settings.get('mathjax_url', '')
142
142
143 @property
143 @property
144 def base_project_url(self):
144 def base_project_url(self):
145 return self.settings.get('base_project_url', '/')
145 return self.settings.get('base_project_url', '/')
146
146
147 @property
147 @property
148 def base_kernel_url(self):
148 def base_kernel_url(self):
149 return self.settings.get('base_kernel_url', '/')
149 return self.settings.get('base_kernel_url', '/')
150
150
151 #---------------------------------------------------------------
151 #---------------------------------------------------------------
152 # Manager objects
152 # Manager objects
153 #---------------------------------------------------------------
153 #---------------------------------------------------------------
154
154
155 @property
155 @property
156 def kernel_manager(self):
156 def kernel_manager(self):
157 return self.settings['kernel_manager']
157 return self.settings['kernel_manager']
158
158
159 @property
159 @property
160 def notebook_manager(self):
160 def notebook_manager(self):
161 return self.settings['notebook_manager']
161 return self.settings['notebook_manager']
162
162
163 @property
163 @property
164 def cluster_manager(self):
164 def cluster_manager(self):
165 return self.settings['cluster_manager']
165 return self.settings['cluster_manager']
166
166
167 @property
167 @property
168 def session_manager(self):
168 def session_manager(self):
169 return self.settings['session_manager']
169 return self.settings['session_manager']
170
170
171 @property
171 @property
172 def project_dir(self):
172 def project_dir(self):
173 return self.notebook_manager.notebook_dir
173 return self.notebook_manager.notebook_dir
174
174
175 #---------------------------------------------------------------
175 #---------------------------------------------------------------
176 # template rendering
176 # template rendering
177 #---------------------------------------------------------------
177 #---------------------------------------------------------------
178
178
179 def get_template(self, name):
179 def get_template(self, name):
180 """Return the jinja template object for a given name"""
180 """Return the jinja template object for a given name"""
181 return self.settings['jinja2_env'].get_template(name)
181 return self.settings['jinja2_env'].get_template(name)
182
182
183 def render_template(self, name, **ns):
183 def render_template(self, name, **ns):
184 ns.update(self.template_namespace)
184 ns.update(self.template_namespace)
185 template = self.get_template(name)
185 template = self.get_template(name)
186 return template.render(**ns)
186 return template.render(**ns)
187
187
188 @property
188 @property
189 def template_namespace(self):
189 def template_namespace(self):
190 return dict(
190 return dict(
191 base_project_url=self.base_project_url,
191 base_project_url=self.base_project_url,
192 base_kernel_url=self.base_kernel_url,
192 base_kernel_url=self.base_kernel_url,
193 logged_in=self.logged_in,
193 logged_in=self.logged_in,
194 login_available=self.login_available,
194 login_available=self.login_available,
195 use_less=self.use_less,
195 use_less=self.use_less,
196 )
196 )
197
197
198 def get_json_body(self):
198 def get_json_body(self):
199 """Return the body of the request as JSON data."""
199 """Return the body of the request as JSON data."""
200 if not self.request.body:
200 if not self.request.body:
201 return None
201 return None
202 # Do we need to call body.decode('utf-8') here?
202 # Do we need to call body.decode('utf-8') here?
203 body = self.request.body.strip().decode(u'utf-8')
203 body = self.request.body.strip().decode(u'utf-8')
204 try:
204 try:
205 model = json.loads(body)
205 model = json.loads(body)
206 except Exception:
206 except Exception:
207 self.log.debug("Bad JSON: %r", body)
207 self.log.debug("Bad JSON: %r", body)
208 self.log.error("Couldn't parse JSON", exc_info=True)
208 self.log.error("Couldn't parse JSON", exc_info=True)
209 raise web.HTTPError(400, u'Invalid JSON in body of request')
209 raise web.HTTPError(400, u'Invalid JSON in body of request')
210 return model
210 return model
211
211
212
212
213 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
213 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
214 """static files should only be accessible when logged in"""
214 """static files should only be accessible when logged in"""
215
215
216 @web.authenticated
216 @web.authenticated
217 def get(self, path):
217 def get(self, path):
218 if os.path.splitext(path)[1] == '.ipynb':
218 if os.path.splitext(path)[1] == '.ipynb':
219 name = os.path.basename(path)
219 name = os.path.basename(path)
220 self.set_header('Content-Type', 'application/json')
220 self.set_header('Content-Type', 'application/json')
221 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
221 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
222
222
223 return web.StaticFileHandler.get(self, path)
223 return web.StaticFileHandler.get(self, path)
224
224
225 def compute_etag(self):
225 def compute_etag(self):
226 return None
226 return None
227
227
228 def validate_absolute_path(self, root, absolute_path):
228 def validate_absolute_path(self, root, absolute_path):
229 """Validate and return the absolute path.
229 """Validate and return the absolute path.
230
230
231 Requires tornado 3.1
231 Requires tornado 3.1
232
232
233 Adding to tornado's own handling, forbids the serving of hidden files.
233 Adding to tornado's own handling, forbids the serving of hidden files.
234 """
234 """
235 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
235 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
236 abs_root = os.path.abspath(root)
236 abs_root = os.path.abspath(root)
237 self.forbid_hidden(abs_root, abs_path)
237 self.forbid_hidden(abs_root, abs_path)
238 return abs_path
238 return abs_path
239
239
240 def forbid_hidden(self, absolute_root, absolute_path):
240 def forbid_hidden(self, absolute_root, absolute_path):
241 """Raise 403 if a file is hidden or contained in a hidden directory.
241 """Raise 403 if a file is hidden or contained in a hidden directory.
242
242
243 Hidden is determined by either name starting with '.'
243 Hidden is determined by either name starting with '.'
244 or the UF_HIDDEN flag as reported by stat
244 or the UF_HIDDEN flag as reported by stat
245 """
245 """
246 inside_root = absolute_path[len(absolute_root):]
246 inside_root = absolute_path[len(absolute_root):]
247 if any(part.startswith('.') for part in inside_root.split(os.sep)):
247 if any(part.startswith('.') for part in inside_root.split(os.sep)):
248 raise web.HTTPError(403)
248 raise web.HTTPError(403)
249
249
250 # check UF_HIDDEN on any location up to root
250 # check UF_HIDDEN on any location up to root
251 path = absolute_path
251 path = absolute_path
252 while path and path.startswith(absolute_root) and path != absolute_root:
252 while path and path.startswith(absolute_root) and path != absolute_root:
253 st = os.stat(path)
253 st = os.stat(path)
254 if getattr(st, 'st_flags', 0) & UF_HIDDEN:
254 if getattr(st, 'st_flags', 0) & UF_HIDDEN:
255 raise web.HTTPError(403)
255 raise web.HTTPError(403)
256 path = os.path.dirname(path)
256 path = os.path.dirname(path)
257
257
258 return absolute_path
258 return absolute_path
259
259
260
260
261 def json_errors(method):
261 def json_errors(method):
262 """Decorate methods with this to return GitHub style JSON errors.
262 """Decorate methods with this to return GitHub style JSON errors.
263
263
264 This should be used on any JSON API on any handler method that can raise HTTPErrors.
264 This should be used on any JSON API on any handler method that can raise HTTPErrors.
265
265
266 This will grab the latest HTTPError exception using sys.exc_info
266 This will grab the latest HTTPError exception using sys.exc_info
267 and then:
267 and then:
268
268
269 1. Set the HTTP status code based on the HTTPError
269 1. Set the HTTP status code based on the HTTPError
270 2. Create and return a JSON body with a message field describing
270 2. Create and return a JSON body with a message field describing
271 the error in a human readable form.
271 the error in a human readable form.
272 """
272 """
273 @functools.wraps(method)
273 @functools.wraps(method)
274 def wrapper(self, *args, **kwargs):
274 def wrapper(self, *args, **kwargs):
275 try:
275 try:
276 result = method(self, *args, **kwargs)
276 result = method(self, *args, **kwargs)
277 except web.HTTPError as e:
277 except web.HTTPError as e:
278 status = e.status_code
278 status = e.status_code
279 message = e.log_message
279 message = e.log_message
280 self.set_status(e.status_code)
280 self.set_status(e.status_code)
281 self.finish(json.dumps(dict(message=message)))
281 self.finish(json.dumps(dict(message=message)))
282 except Exception:
282 except Exception:
283 self.log.error("Unhandled error in API request", exc_info=True)
283 self.log.error("Unhandled error in API request", exc_info=True)
284 status = 500
284 status = 500
285 message = "Unknown server error"
285 message = "Unknown server error"
286 t, value, tb = sys.exc_info()
286 t, value, tb = sys.exc_info()
287 self.set_status(status)
287 self.set_status(status)
288 tb_text = ''.join(traceback.format_exception(t, value, tb))
288 tb_text = ''.join(traceback.format_exception(t, value, tb))
289 reply = dict(message=message, traceback=tb_text)
289 reply = dict(message=message, traceback=tb_text)
290 self.finish(json.dumps(reply))
290 self.finish(json.dumps(reply))
291 else:
291 else:
292 return result
292 return result
293 return wrapper
293 return wrapper
294
294
295
295
296
296
297 #-----------------------------------------------------------------------------
297 #-----------------------------------------------------------------------------
298 # File handler
298 # File handler
299 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
300
300
301 # to minimize subclass changes:
301 # to minimize subclass changes:
302 HTTPError = web.HTTPError
302 HTTPError = web.HTTPError
303
303
304 class FileFindHandler(web.StaticFileHandler):
304 class FileFindHandler(web.StaticFileHandler):
305 """subclass of StaticFileHandler for serving files from a search path"""
305 """subclass of StaticFileHandler for serving files from a search path"""
306
306
307 # cache search results, don't search for files more than once
307 # cache search results, don't search for files more than once
308 _static_paths = {}
308 _static_paths = {}
309
309
310 def initialize(self, path, default_filename=None):
310 def initialize(self, path, default_filename=None):
311 if isinstance(path, string_types):
311 if isinstance(path, string_types):
312 path = [path]
312 path = [path]
313
313
314 self.root = tuple(
314 self.root = tuple(
315 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
315 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
316 )
316 )
317 self.default_filename = default_filename
317 self.default_filename = default_filename
318
318
319 def compute_etag(self):
319 def compute_etag(self):
320 return None
320 return None
321
321
322 @classmethod
322 @classmethod
323 def get_absolute_path(cls, roots, path):
323 def get_absolute_path(cls, roots, path):
324 """locate a file to serve on our static file search path"""
324 """locate a file to serve on our static file search path"""
325 with cls._lock:
325 with cls._lock:
326 if path in cls._static_paths:
326 if path in cls._static_paths:
327 return cls._static_paths[path]
327 return cls._static_paths[path]
328 try:
328 try:
329 abspath = os.path.abspath(filefind(path, roots))
329 abspath = os.path.abspath(filefind(path, roots))
330 except IOError:
330 except IOError:
331 # IOError means not found
331 # IOError means not found
332 raise web.HTTPError(404)
332 raise web.HTTPError(404)
333
333
334 cls._static_paths[path] = abspath
334 cls._static_paths[path] = abspath
335 return abspath
335 return abspath
336
336
337 def validate_absolute_path(self, root, absolute_path):
337 def validate_absolute_path(self, root, absolute_path):
338 """check if the file should be served (raises 404, 403, etc.)"""
338 """check if the file should be served (raises 404, 403, etc.)"""
339 for root in self.root:
339 for root in self.root:
340 if (absolute_path + os.sep).startswith(root):
340 if (absolute_path + os.sep).startswith(root):
341 break
341 break
342
342
343 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
343 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
344
344
345
345
346 class TrailingSlashHandler(web.RequestHandler):
346 class TrailingSlashHandler(web.RequestHandler):
347 """Simple redirect handler that strips trailing slashes
347 """Simple redirect handler that strips trailing slashes
348
348
349 This should be the first, highest priority handler.
349 This should be the first, highest priority handler.
350 """
350 """
351
351
352 SUPPORTED_METHODS = ['GET']
352 SUPPORTED_METHODS = ['GET']
353
353
354 def get(self):
354 def get(self):
355 self.redirect(self.request.uri.rstrip('/'))
355 self.redirect(self.request.uri.rstrip('/'))
356
356
357 #-----------------------------------------------------------------------------
357 #-----------------------------------------------------------------------------
358 # URL pattern fragments for re-use
359 #-----------------------------------------------------------------------------
360
361 path_regex = r"(?P<path>(?:/.*)*)"
362 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
363 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
364
365 #-----------------------------------------------------------------------------
358 # URL to handler mappings
366 # URL to handler mappings
359 #-----------------------------------------------------------------------------
367 #-----------------------------------------------------------------------------
360
368
361
369
362 default_handlers = [
370 default_handlers = [
363 (r".*/", TrailingSlashHandler)
371 (r".*/", TrailingSlashHandler)
364 ]
372 ]
@@ -1,91 +1,90 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
24 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
25 from ..utils import url_path_join, url_escape
24 from ..utils import url_path_join, url_escape
26
25
27 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
28 # Handlers
27 # Handlers
29 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
30
29
31
30
32 class NotebookHandler(IPythonHandler):
31 class NotebookHandler(IPythonHandler):
33
32
34 @web.authenticated
33 @web.authenticated
35 def get(self, path='', name=None):
34 def get(self, path='', name=None):
36 """get renders the notebook template if a name is given, or
35 """get renders the notebook template if a name is given, or
37 redirects to the '/files/' handler if the name is not given."""
36 redirects to the '/files/' handler if the name is not given."""
38 path = path.strip('/')
37 path = path.strip('/')
39 nbm = self.notebook_manager
38 nbm = self.notebook_manager
40 if name is None:
39 if name is None:
41 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
42
41
43 # a .ipynb filename was given
42 # a .ipynb filename was given
44 if not nbm.notebook_exists(name, path):
43 if not nbm.notebook_exists(name, path):
45 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
46 name = url_escape(name)
45 name = url_escape(name)
47 path = url_escape(path)
46 path = url_escape(path)
48 self.write(self.render_template('notebook.html',
47 self.write(self.render_template('notebook.html',
49 project=self.project_dir,
48 project=self.project_dir,
50 notebook_path=path,
49 notebook_path=path,
51 notebook_name=name,
50 notebook_name=name,
52 kill_kernel=False,
51 kill_kernel=False,
53 mathjax_url=self.mathjax_url,
52 mathjax_url=self.mathjax_url,
54 )
53 )
55 )
54 )
56
55
57 class NotebookRedirectHandler(IPythonHandler):
56 class NotebookRedirectHandler(IPythonHandler):
58 def get(self, path=''):
57 def get(self, path=''):
59 nbm = self.notebook_manager
58 nbm = self.notebook_manager
60 if nbm.path_exists(path):
59 if nbm.path_exists(path):
61 # it's a *directory*, redirect to /tree
60 # it's a *directory*, redirect to /tree
62 url = url_path_join(self.base_project_url, 'tree', path)
61 url = url_path_join(self.base_project_url, 'tree', path)
63 else:
62 else:
64 # otherwise, redirect to /files
63 # otherwise, redirect to /files
65 if '/files/' in path:
64 if '/files/' in path:
66 # redirect without files/ iff it would 404
65 # redirect without files/ iff it would 404
67 # this preserves pre-2.0-style 'files/' links
66 # this preserves pre-2.0-style 'files/' links
68 # FIXME: this is hardcoded based on notebook_path,
67 # FIXME: this is hardcoded based on notebook_path,
69 # but so is the files handler itself,
68 # but so is the files handler itself,
70 # so it should work until both are cleaned up.
69 # so it should work until both are cleaned up.
71 parts = path.split('/')
70 parts = path.split('/')
72 files_path = os.path.join(nbm.notebook_dir, *parts)
71 files_path = os.path.join(nbm.notebook_dir, *parts)
73 self.log.warn("filespath: %s", files_path)
72 self.log.warn("filespath: %s", files_path)
74 if not os.path.exists(files_path):
73 if not os.path.exists(files_path):
75 path = path.replace('/files/', '/', 1)
74 path = path.replace('/files/', '/', 1)
76
75
77 url = url_path_join(self.base_project_url, 'files', path)
76 url = url_path_join(self.base_project_url, 'files', path)
78 url = url_escape(url)
77 url = url_escape(url)
79 self.log.debug("Redirecting %s to %s", self.request.path, url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
80 self.redirect(url)
79 self.redirect(url)
81
80
82 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
83 # URL to handler mappings
82 # URL to handler mappings
84 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
85
84
86
85
87 default_handlers = [
86 default_handlers = [
88 (r"/notebooks%s" % _notebook_path_regex, NotebookHandler),
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
89 (r"/notebooks%s" % _path_regex, NotebookRedirectHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
90 ]
89 ]
91
90
@@ -1,749 +1,751 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import logging
22 import logging
23 import os
23 import os
24 import random
24 import random
25 import select
25 import select
26 import signal
26 import signal
27 import socket
27 import socket
28 import sys
28 import sys
29 import threading
29 import threading
30 import time
30 import time
31 import webbrowser
31 import webbrowser
32
32
33
33
34 # Third party
34 # Third party
35 # check for pyzmq 2.1.11
35 # check for pyzmq 2.1.11
36 from IPython.utils.zmqrelated import check_for_zmq
36 from IPython.utils.zmqrelated import check_for_zmq
37 check_for_zmq('2.1.11', 'IPython.html')
37 check_for_zmq('2.1.11', 'IPython.html')
38
38
39 from jinja2 import Environment, FileSystemLoader
39 from jinja2 import Environment, FileSystemLoader
40
40
41 # Install the pyzmq ioloop. This has to be done before anything else from
41 # Install the pyzmq ioloop. This has to be done before anything else from
42 # tornado is imported.
42 # tornado is imported.
43 from zmq.eventloop import ioloop
43 from zmq.eventloop import ioloop
44 ioloop.install()
44 ioloop.install()
45
45
46 # check for tornado 3.1.0
46 # check for tornado 3.1.0
47 msg = "The IPython Notebook requires tornado >= 3.1.0"
47 msg = "The IPython Notebook requires tornado >= 3.1.0"
48 try:
48 try:
49 import tornado
49 import tornado
50 except ImportError:
50 except ImportError:
51 raise ImportError(msg)
51 raise ImportError(msg)
52 try:
52 try:
53 version_info = tornado.version_info
53 version_info = tornado.version_info
54 except AttributeError:
54 except AttributeError:
55 raise ImportError(msg + ", but you have < 1.1.0")
55 raise ImportError(msg + ", but you have < 1.1.0")
56 if version_info < (3,1,0):
56 if version_info < (3,1,0):
57 raise ImportError(msg + ", but you have %s" % tornado.version)
57 raise ImportError(msg + ", but you have %s" % tornado.version)
58
58
59 from tornado import httpserver
59 from tornado import httpserver
60 from tornado import web
60 from tornado import web
61
61
62 # Our own libraries
62 # Our own libraries
63 from IPython.html import DEFAULT_STATIC_FILES_PATH
63 from IPython.html import DEFAULT_STATIC_FILES_PATH
64
64
65 from .services.kernels.kernelmanager import MappingKernelManager
65 from .services.kernels.kernelmanager import MappingKernelManager
66 from .services.notebooks.nbmanager import NotebookManager
66 from .services.notebooks.nbmanager import NotebookManager
67 from .services.notebooks.filenbmanager import FileNotebookManager
67 from .services.notebooks.filenbmanager import FileNotebookManager
68 from .services.clusters.clustermanager import ClusterManager
68 from .services.clusters.clustermanager import ClusterManager
69 from .services.sessions.sessionmanager import SessionManager
69 from .services.sessions.sessionmanager import SessionManager
70
70
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72
72
73 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import BaseIPythonApplication
74 from IPython.core.application import BaseIPythonApplication
75 from IPython.consoleapp import IPythonConsoleApp
75 from IPython.consoleapp import IPythonConsoleApp
76 from IPython.kernel import swallow_argv
76 from IPython.kernel import swallow_argv
77 from IPython.kernel.zmq.session import default_secure
77 from IPython.kernel.zmq.session import default_secure
78 from IPython.kernel.zmq.kernelapp import (
78 from IPython.kernel.zmq.kernelapp import (
79 kernel_flags,
79 kernel_flags,
80 kernel_aliases,
80 kernel_aliases,
81 )
81 )
82 from IPython.utils.importstring import import_item
82 from IPython.utils.importstring import import_item
83 from IPython.utils.localinterfaces import localhost
83 from IPython.utils.localinterfaces import localhost
84 from IPython.utils import submodule
84 from IPython.utils import submodule
85 from IPython.utils.traitlets import (
85 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes,
86 Dict, Unicode, Integer, List, Bool, Bytes,
87 DottedObjectName
87 DottedObjectName
88 )
88 )
89 from IPython.utils import py3compat
89 from IPython.utils import py3compat
90 from IPython.utils.path import filefind, get_ipython_dir
90 from IPython.utils.path import filefind, get_ipython_dir
91
91
92 from .utils import url_path_join
92 from .utils import url_path_join
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Module globals
95 # Module globals
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 _examples = """
98 _examples = """
99 ipython notebook # start the notebook
99 ipython notebook # start the notebook
100 ipython notebook --profile=sympy # use the sympy profile
100 ipython notebook --profile=sympy # use the sympy profile
101 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
101 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
102 """
102 """
103
103
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105 # Helper functions
105 # Helper functions
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107
107
108 def random_ports(port, n):
108 def random_ports(port, n):
109 """Generate a list of n random ports near the given port.
109 """Generate a list of n random ports near the given port.
110
110
111 The first 5 ports will be sequential, and the remaining n-5 will be
111 The first 5 ports will be sequential, and the remaining n-5 will be
112 randomly selected in the range [port-2*n, port+2*n].
112 randomly selected in the range [port-2*n, port+2*n].
113 """
113 """
114 for i in range(min(5, n)):
114 for i in range(min(5, n)):
115 yield port + i
115 yield port + i
116 for i in range(n-5):
116 for i in range(n-5):
117 yield max(1, port + random.randint(-2*n, 2*n))
117 yield max(1, port + random.randint(-2*n, 2*n))
118
118
119 def load_handlers(name):
119 def load_handlers(name):
120 """Load the (URL pattern, handler) tuples for each component."""
120 """Load the (URL pattern, handler) tuples for each component."""
121 name = 'IPython.html.' + name
121 name = 'IPython.html.' + name
122 mod = __import__(name, fromlist=['default_handlers'])
122 mod = __import__(name, fromlist=['default_handlers'])
123 return mod.default_handlers
123 return mod.default_handlers
124
124
125 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
126 # The Tornado web application
126 # The Tornado web application
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128
128
129 class NotebookWebApplication(web.Application):
129 class NotebookWebApplication(web.Application):
130
130
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
132 cluster_manager, session_manager, log, base_project_url,
132 cluster_manager, session_manager, log, base_project_url,
133 settings_overrides):
133 settings_overrides):
134
134
135 settings = self.init_settings(
135 settings = self.init_settings(
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
137 session_manager, log, base_project_url, settings_overrides)
137 session_manager, log, base_project_url, settings_overrides)
138 handlers = self.init_handlers(settings)
138 handlers = self.init_handlers(settings)
139
139
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
141
141
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
143 cluster_manager, session_manager, log, base_project_url,
143 cluster_manager, session_manager, log, base_project_url,
144 settings_overrides):
144 settings_overrides):
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
146 # base_project_url will always be unicode, which will in turn
146 # base_project_url will always be unicode, which will in turn
147 # make the patterns unicode, and ultimately result in unicode
147 # make the patterns unicode, and ultimately result in unicode
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
149 # This enforces that base_project_url be ascii in that situation.
149 # This enforces that base_project_url be ascii in that situation.
150 #
150 #
151 # Note that the URLs these patterns check against are escaped,
151 # Note that the URLs these patterns check against are escaped,
152 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
152 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
154 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
154 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
155 settings = dict(
155 settings = dict(
156 # basics
156 # basics
157 base_project_url=base_project_url,
157 base_project_url=base_project_url,
158 base_kernel_url=ipython_app.base_kernel_url,
158 base_kernel_url=ipython_app.base_kernel_url,
159 template_path=template_path,
159 template_path=template_path,
160 static_path=ipython_app.static_file_path,
160 static_path=ipython_app.static_file_path,
161 static_handler_class = FileFindHandler,
161 static_handler_class = FileFindHandler,
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
163
163
164 # authentication
164 # authentication
165 cookie_secret=ipython_app.cookie_secret,
165 cookie_secret=ipython_app.cookie_secret,
166 login_url=url_path_join(base_project_url,'/login'),
166 login_url=url_path_join(base_project_url,'/login'),
167 password=ipython_app.password,
167 password=ipython_app.password,
168
168
169 # managers
169 # managers
170 kernel_manager=kernel_manager,
170 kernel_manager=kernel_manager,
171 notebook_manager=notebook_manager,
171 notebook_manager=notebook_manager,
172 cluster_manager=cluster_manager,
172 cluster_manager=cluster_manager,
173 session_manager=session_manager,
173 session_manager=session_manager,
174
174
175 # IPython stuff
175 # IPython stuff
176 nbextensions_path = ipython_app.nbextensions_path,
176 nbextensions_path = ipython_app.nbextensions_path,
177 mathjax_url=ipython_app.mathjax_url,
177 mathjax_url=ipython_app.mathjax_url,
178 config=ipython_app.config,
178 config=ipython_app.config,
179 use_less=ipython_app.use_less,
179 use_less=ipython_app.use_less,
180 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
180 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
181 )
181 )
182
182
183 # allow custom overrides for the tornado web app.
183 # allow custom overrides for the tornado web app.
184 settings.update(settings_overrides)
184 settings.update(settings_overrides)
185 return settings
185 return settings
186
186
187 def init_handlers(self, settings):
187 def init_handlers(self, settings):
188 # Load the (URL pattern, handler) tuples for each component.
188 # Load the (URL pattern, handler) tuples for each component.
189 handlers = []
189 handlers = []
190 handlers.extend(load_handlers('base.handlers'))
190 handlers.extend(load_handlers('base.handlers'))
191 handlers.extend(load_handlers('tree.handlers'))
191 handlers.extend(load_handlers('tree.handlers'))
192 handlers.extend(load_handlers('auth.login'))
192 handlers.extend(load_handlers('auth.login'))
193 handlers.extend(load_handlers('auth.logout'))
193 handlers.extend(load_handlers('auth.logout'))
194 handlers.extend(load_handlers('notebook.handlers'))
194 handlers.extend(load_handlers('notebook.handlers'))
195 handlers.extend(load_handlers('nbconvert.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.notebooks.handlers'))
197 handlers.extend(load_handlers('services.notebooks.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
198 handlers.extend(load_handlers('services.clusters.handlers'))
198 handlers.extend(load_handlers('services.sessions.handlers'))
199 handlers.extend(load_handlers('services.sessions.handlers'))
200 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend([
201 handlers.extend([
200 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
201 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
202 ])
204 ])
203 # prepend base_project_url onto the patterns that we match
205 # prepend base_project_url onto the patterns that we match
204 new_handlers = []
206 new_handlers = []
205 for handler in handlers:
207 for handler in handlers:
206 pattern = url_path_join(settings['base_project_url'], handler[0])
208 pattern = url_path_join(settings['base_project_url'], handler[0])
207 new_handler = tuple([pattern] + list(handler[1:]))
209 new_handler = tuple([pattern] + list(handler[1:]))
208 new_handlers.append(new_handler)
210 new_handlers.append(new_handler)
209 return new_handlers
211 return new_handlers
210
212
211
213
212
214
213 #-----------------------------------------------------------------------------
215 #-----------------------------------------------------------------------------
214 # Aliases and Flags
216 # Aliases and Flags
215 #-----------------------------------------------------------------------------
217 #-----------------------------------------------------------------------------
216
218
217 flags = dict(kernel_flags)
219 flags = dict(kernel_flags)
218 flags['no-browser']=(
220 flags['no-browser']=(
219 {'NotebookApp' : {'open_browser' : False}},
221 {'NotebookApp' : {'open_browser' : False}},
220 "Don't open the notebook in a browser after startup."
222 "Don't open the notebook in a browser after startup."
221 )
223 )
222 flags['no-mathjax']=(
224 flags['no-mathjax']=(
223 {'NotebookApp' : {'enable_mathjax' : False}},
225 {'NotebookApp' : {'enable_mathjax' : False}},
224 """Disable MathJax
226 """Disable MathJax
225
227
226 MathJax is the javascript library IPython uses to render math/LaTeX. It is
228 MathJax is the javascript library IPython uses to render math/LaTeX. It is
227 very large, so you may want to disable it if you have a slow internet
229 very large, so you may want to disable it if you have a slow internet
228 connection, or for offline use of the notebook.
230 connection, or for offline use of the notebook.
229
231
230 When disabled, equations etc. will appear as their untransformed TeX source.
232 When disabled, equations etc. will appear as their untransformed TeX source.
231 """
233 """
232 )
234 )
233
235
234 # Add notebook manager flags
236 # Add notebook manager flags
235 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
237 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
236 'Auto-save a .py script everytime the .ipynb notebook is saved',
238 'Auto-save a .py script everytime the .ipynb notebook is saved',
237 'Do not auto-save .py scripts for every notebook'))
239 'Do not auto-save .py scripts for every notebook'))
238
240
239 # the flags that are specific to the frontend
241 # the flags that are specific to the frontend
240 # these must be scrubbed before being passed to the kernel,
242 # these must be scrubbed before being passed to the kernel,
241 # or it will raise an error on unrecognized flags
243 # or it will raise an error on unrecognized flags
242 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
244 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
243
245
244 aliases = dict(kernel_aliases)
246 aliases = dict(kernel_aliases)
245
247
246 aliases.update({
248 aliases.update({
247 'ip': 'NotebookApp.ip',
249 'ip': 'NotebookApp.ip',
248 'port': 'NotebookApp.port',
250 'port': 'NotebookApp.port',
249 'port-retries': 'NotebookApp.port_retries',
251 'port-retries': 'NotebookApp.port_retries',
250 'transport': 'KernelManager.transport',
252 'transport': 'KernelManager.transport',
251 'keyfile': 'NotebookApp.keyfile',
253 'keyfile': 'NotebookApp.keyfile',
252 'certfile': 'NotebookApp.certfile',
254 'certfile': 'NotebookApp.certfile',
253 'notebook-dir': 'NotebookManager.notebook_dir',
255 'notebook-dir': 'NotebookManager.notebook_dir',
254 'browser': 'NotebookApp.browser',
256 'browser': 'NotebookApp.browser',
255 })
257 })
256
258
257 # remove ipkernel flags that are singletons, and don't make sense in
259 # remove ipkernel flags that are singletons, and don't make sense in
258 # multi-kernel evironment:
260 # multi-kernel evironment:
259 aliases.pop('f', None)
261 aliases.pop('f', None)
260
262
261 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
263 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
262 u'notebook-dir', u'profile', u'profile-dir']
264 u'notebook-dir', u'profile', u'profile-dir']
263
265
264 #-----------------------------------------------------------------------------
266 #-----------------------------------------------------------------------------
265 # NotebookApp
267 # NotebookApp
266 #-----------------------------------------------------------------------------
268 #-----------------------------------------------------------------------------
267
269
268 class NotebookApp(BaseIPythonApplication):
270 class NotebookApp(BaseIPythonApplication):
269
271
270 name = 'ipython-notebook'
272 name = 'ipython-notebook'
271
273
272 description = """
274 description = """
273 The IPython HTML Notebook.
275 The IPython HTML Notebook.
274
276
275 This launches a Tornado based HTML Notebook Server that serves up an
277 This launches a Tornado based HTML Notebook Server that serves up an
276 HTML5/Javascript Notebook client.
278 HTML5/Javascript Notebook client.
277 """
279 """
278 examples = _examples
280 examples = _examples
279
281
280 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
282 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
281 FileNotebookManager]
283 FileNotebookManager]
282 flags = Dict(flags)
284 flags = Dict(flags)
283 aliases = Dict(aliases)
285 aliases = Dict(aliases)
284
286
285 kernel_argv = List(Unicode)
287 kernel_argv = List(Unicode)
286
288
287 def _log_level_default(self):
289 def _log_level_default(self):
288 return logging.INFO
290 return logging.INFO
289
291
290 def _log_format_default(self):
292 def _log_format_default(self):
291 """override default log format to include time"""
293 """override default log format to include time"""
292 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
294 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
293
295
294 # create requested profiles by default, if they don't exist:
296 # create requested profiles by default, if they don't exist:
295 auto_create = Bool(True)
297 auto_create = Bool(True)
296
298
297 # file to be opened in the notebook server
299 # file to be opened in the notebook server
298 file_to_run = Unicode('')
300 file_to_run = Unicode('')
299
301
300 # Network related information.
302 # Network related information.
301
303
302 ip = Unicode(config=True,
304 ip = Unicode(config=True,
303 help="The IP address the notebook server will listen on."
305 help="The IP address the notebook server will listen on."
304 )
306 )
305 def _ip_default(self):
307 def _ip_default(self):
306 return localhost()
308 return localhost()
307
309
308 def _ip_changed(self, name, old, new):
310 def _ip_changed(self, name, old, new):
309 if new == u'*': self.ip = u''
311 if new == u'*': self.ip = u''
310
312
311 port = Integer(8888, config=True,
313 port = Integer(8888, config=True,
312 help="The port the notebook server will listen on."
314 help="The port the notebook server will listen on."
313 )
315 )
314 port_retries = Integer(50, config=True,
316 port_retries = Integer(50, config=True,
315 help="The number of additional ports to try if the specified port is not available."
317 help="The number of additional ports to try if the specified port is not available."
316 )
318 )
317
319
318 certfile = Unicode(u'', config=True,
320 certfile = Unicode(u'', config=True,
319 help="""The full path to an SSL/TLS certificate file."""
321 help="""The full path to an SSL/TLS certificate file."""
320 )
322 )
321
323
322 keyfile = Unicode(u'', config=True,
324 keyfile = Unicode(u'', config=True,
323 help="""The full path to a private key file for usage with SSL/TLS."""
325 help="""The full path to a private key file for usage with SSL/TLS."""
324 )
326 )
325
327
326 cookie_secret = Bytes(b'', config=True,
328 cookie_secret = Bytes(b'', config=True,
327 help="""The random bytes used to secure cookies.
329 help="""The random bytes used to secure cookies.
328 By default this is a new random number every time you start the Notebook.
330 By default this is a new random number every time you start the Notebook.
329 Set it to a value in a config file to enable logins to persist across server sessions.
331 Set it to a value in a config file to enable logins to persist across server sessions.
330
332
331 Note: Cookie secrets should be kept private, do not share config files with
333 Note: Cookie secrets should be kept private, do not share config files with
332 cookie_secret stored in plaintext (you can read the value from a file).
334 cookie_secret stored in plaintext (you can read the value from a file).
333 """
335 """
334 )
336 )
335 def _cookie_secret_default(self):
337 def _cookie_secret_default(self):
336 return os.urandom(1024)
338 return os.urandom(1024)
337
339
338 password = Unicode(u'', config=True,
340 password = Unicode(u'', config=True,
339 help="""Hashed password to use for web authentication.
341 help="""Hashed password to use for web authentication.
340
342
341 To generate, type in a python/IPython shell:
343 To generate, type in a python/IPython shell:
342
344
343 from IPython.lib import passwd; passwd()
345 from IPython.lib import passwd; passwd()
344
346
345 The string should be of the form type:salt:hashed-password.
347 The string should be of the form type:salt:hashed-password.
346 """
348 """
347 )
349 )
348
350
349 open_browser = Bool(True, config=True,
351 open_browser = Bool(True, config=True,
350 help="""Whether to open in a browser after starting.
352 help="""Whether to open in a browser after starting.
351 The specific browser used is platform dependent and
353 The specific browser used is platform dependent and
352 determined by the python standard library `webbrowser`
354 determined by the python standard library `webbrowser`
353 module, unless it is overridden using the --browser
355 module, unless it is overridden using the --browser
354 (NotebookApp.browser) configuration option.
356 (NotebookApp.browser) configuration option.
355 """)
357 """)
356
358
357 browser = Unicode(u'', config=True,
359 browser = Unicode(u'', config=True,
358 help="""Specify what command to use to invoke a web
360 help="""Specify what command to use to invoke a web
359 browser when opening the notebook. If not specified, the
361 browser when opening the notebook. If not specified, the
360 default browser will be determined by the `webbrowser`
362 default browser will be determined by the `webbrowser`
361 standard library module, which allows setting of the
363 standard library module, which allows setting of the
362 BROWSER environment variable to override it.
364 BROWSER environment variable to override it.
363 """)
365 """)
364
366
365 use_less = Bool(False, config=True,
367 use_less = Bool(False, config=True,
366 help="""Wether to use Browser Side less-css parsing
368 help="""Wether to use Browser Side less-css parsing
367 instead of compiled css version in templates that allows
369 instead of compiled css version in templates that allows
368 it. This is mainly convenient when working on the less
370 it. This is mainly convenient when working on the less
369 file to avoid a build step, or if user want to overwrite
371 file to avoid a build step, or if user want to overwrite
370 some of the less variables without having to recompile
372 some of the less variables without having to recompile
371 everything.
373 everything.
372
374
373 You will need to install the less.js component in the static directory
375 You will need to install the less.js component in the static directory
374 either in the source tree or in your profile folder.
376 either in the source tree or in your profile folder.
375 """)
377 """)
376
378
377 webapp_settings = Dict(config=True,
379 webapp_settings = Dict(config=True,
378 help="Supply overrides for the tornado.web.Application that the "
380 help="Supply overrides for the tornado.web.Application that the "
379 "IPython notebook uses.")
381 "IPython notebook uses.")
380
382
381 enable_mathjax = Bool(True, config=True,
383 enable_mathjax = Bool(True, config=True,
382 help="""Whether to enable MathJax for typesetting math/TeX
384 help="""Whether to enable MathJax for typesetting math/TeX
383
385
384 MathJax is the javascript library IPython uses to render math/LaTeX. It is
386 MathJax is the javascript library IPython uses to render math/LaTeX. It is
385 very large, so you may want to disable it if you have a slow internet
387 very large, so you may want to disable it if you have a slow internet
386 connection, or for offline use of the notebook.
388 connection, or for offline use of the notebook.
387
389
388 When disabled, equations etc. will appear as their untransformed TeX source.
390 When disabled, equations etc. will appear as their untransformed TeX source.
389 """
391 """
390 )
392 )
391 def _enable_mathjax_changed(self, name, old, new):
393 def _enable_mathjax_changed(self, name, old, new):
392 """set mathjax url to empty if mathjax is disabled"""
394 """set mathjax url to empty if mathjax is disabled"""
393 if not new:
395 if not new:
394 self.mathjax_url = u''
396 self.mathjax_url = u''
395
397
396 base_project_url = Unicode('/', config=True,
398 base_project_url = Unicode('/', config=True,
397 help='''The base URL for the notebook server.
399 help='''The base URL for the notebook server.
398
400
399 Leading and trailing slashes can be omitted,
401 Leading and trailing slashes can be omitted,
400 and will automatically be added.
402 and will automatically be added.
401 ''')
403 ''')
402 def _base_project_url_changed(self, name, old, new):
404 def _base_project_url_changed(self, name, old, new):
403 if not new.startswith('/'):
405 if not new.startswith('/'):
404 self.base_project_url = '/'+new
406 self.base_project_url = '/'+new
405 elif not new.endswith('/'):
407 elif not new.endswith('/'):
406 self.base_project_url = new+'/'
408 self.base_project_url = new+'/'
407
409
408 base_kernel_url = Unicode('/', config=True,
410 base_kernel_url = Unicode('/', config=True,
409 help='''The base URL for the kernel server
411 help='''The base URL for the kernel server
410
412
411 Leading and trailing slashes can be omitted,
413 Leading and trailing slashes can be omitted,
412 and will automatically be added.
414 and will automatically be added.
413 ''')
415 ''')
414 def _base_kernel_url_changed(self, name, old, new):
416 def _base_kernel_url_changed(self, name, old, new):
415 if not new.startswith('/'):
417 if not new.startswith('/'):
416 self.base_kernel_url = '/'+new
418 self.base_kernel_url = '/'+new
417 elif not new.endswith('/'):
419 elif not new.endswith('/'):
418 self.base_kernel_url = new+'/'
420 self.base_kernel_url = new+'/'
419
421
420 websocket_url = Unicode("", config=True,
422 websocket_url = Unicode("", config=True,
421 help="""The base URL for the websocket server,
423 help="""The base URL for the websocket server,
422 if it differs from the HTTP server (hint: it almost certainly doesn't).
424 if it differs from the HTTP server (hint: it almost certainly doesn't).
423
425
424 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
426 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
425 """
427 """
426 )
428 )
427
429
428 extra_static_paths = List(Unicode, config=True,
430 extra_static_paths = List(Unicode, config=True,
429 help="""Extra paths to search for serving static files.
431 help="""Extra paths to search for serving static files.
430
432
431 This allows adding javascript/css to be available from the notebook server machine,
433 This allows adding javascript/css to be available from the notebook server machine,
432 or overriding individual files in the IPython"""
434 or overriding individual files in the IPython"""
433 )
435 )
434 def _extra_static_paths_default(self):
436 def _extra_static_paths_default(self):
435 return [os.path.join(self.profile_dir.location, 'static')]
437 return [os.path.join(self.profile_dir.location, 'static')]
436
438
437 @property
439 @property
438 def static_file_path(self):
440 def static_file_path(self):
439 """return extra paths + the default location"""
441 """return extra paths + the default location"""
440 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
442 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
441
443
442 nbextensions_path = List(Unicode, config=True,
444 nbextensions_path = List(Unicode, config=True,
443 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
445 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
444 )
446 )
445 def _nbextensions_path_default(self):
447 def _nbextensions_path_default(self):
446 return [os.path.join(get_ipython_dir(), 'nbextensions')]
448 return [os.path.join(get_ipython_dir(), 'nbextensions')]
447
449
448 mathjax_url = Unicode("", config=True,
450 mathjax_url = Unicode("", config=True,
449 help="""The url for MathJax.js."""
451 help="""The url for MathJax.js."""
450 )
452 )
451 def _mathjax_url_default(self):
453 def _mathjax_url_default(self):
452 if not self.enable_mathjax:
454 if not self.enable_mathjax:
453 return u''
455 return u''
454 static_url_prefix = self.webapp_settings.get("static_url_prefix",
456 static_url_prefix = self.webapp_settings.get("static_url_prefix",
455 url_path_join(self.base_project_url, "static")
457 url_path_join(self.base_project_url, "static")
456 )
458 )
457
459
458 # try local mathjax, either in nbextensions/mathjax or static/mathjax
460 # try local mathjax, either in nbextensions/mathjax or static/mathjax
459 for (url_prefix, search_path) in [
461 for (url_prefix, search_path) in [
460 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
462 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
461 (static_url_prefix, self.static_file_path),
463 (static_url_prefix, self.static_file_path),
462 ]:
464 ]:
463 self.log.debug("searching for local mathjax in %s", search_path)
465 self.log.debug("searching for local mathjax in %s", search_path)
464 try:
466 try:
465 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
467 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
466 except IOError:
468 except IOError:
467 continue
469 continue
468 else:
470 else:
469 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
471 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
470 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
472 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
471 return url
473 return url
472
474
473 # no local mathjax, serve from CDN
475 # no local mathjax, serve from CDN
474 if self.certfile:
476 if self.certfile:
475 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
477 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
476 host = u"https://c328740.ssl.cf1.rackcdn.com"
478 host = u"https://c328740.ssl.cf1.rackcdn.com"
477 else:
479 else:
478 host = u"http://cdn.mathjax.org"
480 host = u"http://cdn.mathjax.org"
479
481
480 url = host + u"/mathjax/latest/MathJax.js"
482 url = host + u"/mathjax/latest/MathJax.js"
481 self.log.info("Using MathJax from CDN: %s", url)
483 self.log.info("Using MathJax from CDN: %s", url)
482 return url
484 return url
483
485
484 def _mathjax_url_changed(self, name, old, new):
486 def _mathjax_url_changed(self, name, old, new):
485 if new and not self.enable_mathjax:
487 if new and not self.enable_mathjax:
486 # enable_mathjax=False overrides mathjax_url
488 # enable_mathjax=False overrides mathjax_url
487 self.mathjax_url = u''
489 self.mathjax_url = u''
488 else:
490 else:
489 self.log.info("Using MathJax: %s", new)
491 self.log.info("Using MathJax: %s", new)
490
492
491 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
493 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
492 config=True,
494 config=True,
493 help='The notebook manager class to use.')
495 help='The notebook manager class to use.')
494
496
495 trust_xheaders = Bool(False, config=True,
497 trust_xheaders = Bool(False, config=True,
496 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
497 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
498 )
500 )
499
501
500 def parse_command_line(self, argv=None):
502 def parse_command_line(self, argv=None):
501 super(NotebookApp, self).parse_command_line(argv)
503 super(NotebookApp, self).parse_command_line(argv)
502
504
503 if self.extra_args:
505 if self.extra_args:
504 arg0 = self.extra_args[0]
506 arg0 = self.extra_args[0]
505 f = os.path.abspath(arg0)
507 f = os.path.abspath(arg0)
506 self.argv.remove(arg0)
508 self.argv.remove(arg0)
507 if not os.path.exists(f):
509 if not os.path.exists(f):
508 self.log.critical("No such file or directory: %s", f)
510 self.log.critical("No such file or directory: %s", f)
509 self.exit(1)
511 self.exit(1)
510 if os.path.isdir(f):
512 if os.path.isdir(f):
511 self.config.FileNotebookManager.notebook_dir = f
513 self.config.FileNotebookManager.notebook_dir = f
512 elif os.path.isfile(f):
514 elif os.path.isfile(f):
513 self.file_to_run = f
515 self.file_to_run = f
514
516
515 def init_kernel_argv(self):
517 def init_kernel_argv(self):
516 """construct the kernel arguments"""
518 """construct the kernel arguments"""
517 # Scrub frontend-specific flags
519 # Scrub frontend-specific flags
518 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
520 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
519 # Kernel should inherit default config file from frontend
521 # Kernel should inherit default config file from frontend
520 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
522 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
521 # Kernel should get *absolute* path to profile directory
523 # Kernel should get *absolute* path to profile directory
522 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
524 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
523
525
524 def init_configurables(self):
526 def init_configurables(self):
525 # force Session default to be secure
527 # force Session default to be secure
526 default_secure(self.config)
528 default_secure(self.config)
527 self.kernel_manager = MappingKernelManager(
529 self.kernel_manager = MappingKernelManager(
528 parent=self, log=self.log, kernel_argv=self.kernel_argv,
530 parent=self, log=self.log, kernel_argv=self.kernel_argv,
529 connection_dir = self.profile_dir.security_dir,
531 connection_dir = self.profile_dir.security_dir,
530 )
532 )
531 kls = import_item(self.notebook_manager_class)
533 kls = import_item(self.notebook_manager_class)
532 self.notebook_manager = kls(parent=self, log=self.log)
534 self.notebook_manager = kls(parent=self, log=self.log)
533 self.session_manager = SessionManager(parent=self, log=self.log)
535 self.session_manager = SessionManager(parent=self, log=self.log)
534 self.cluster_manager = ClusterManager(parent=self, log=self.log)
536 self.cluster_manager = ClusterManager(parent=self, log=self.log)
535 self.cluster_manager.update_profiles()
537 self.cluster_manager.update_profiles()
536
538
537 def init_logging(self):
539 def init_logging(self):
538 # This prevents double log messages because tornado use a root logger that
540 # This prevents double log messages because tornado use a root logger that
539 # self.log is a child of. The logging module dipatches log messages to a log
541 # self.log is a child of. The logging module dipatches log messages to a log
540 # and all of its ancenstors until propagate is set to False.
542 # and all of its ancenstors until propagate is set to False.
541 self.log.propagate = False
543 self.log.propagate = False
542
544
543 # hook up tornado 3's loggers to our app handlers
545 # hook up tornado 3's loggers to our app handlers
544 for name in ('access', 'application', 'general'):
546 for name in ('access', 'application', 'general'):
545 logger = logging.getLogger('tornado.%s' % name)
547 logger = logging.getLogger('tornado.%s' % name)
546 logger.parent = self.log
548 logger.parent = self.log
547 logger.setLevel(self.log.level)
549 logger.setLevel(self.log.level)
548
550
549 def init_webapp(self):
551 def init_webapp(self):
550 """initialize tornado webapp and httpserver"""
552 """initialize tornado webapp and httpserver"""
551 self.web_app = NotebookWebApplication(
553 self.web_app = NotebookWebApplication(
552 self, self.kernel_manager, self.notebook_manager,
554 self, self.kernel_manager, self.notebook_manager,
553 self.cluster_manager, self.session_manager,
555 self.cluster_manager, self.session_manager,
554 self.log, self.base_project_url, self.webapp_settings
556 self.log, self.base_project_url, self.webapp_settings
555 )
557 )
556 if self.certfile:
558 if self.certfile:
557 ssl_options = dict(certfile=self.certfile)
559 ssl_options = dict(certfile=self.certfile)
558 if self.keyfile:
560 if self.keyfile:
559 ssl_options['keyfile'] = self.keyfile
561 ssl_options['keyfile'] = self.keyfile
560 else:
562 else:
561 ssl_options = None
563 ssl_options = None
562 self.web_app.password = self.password
564 self.web_app.password = self.password
563 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
565 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
564 xheaders=self.trust_xheaders)
566 xheaders=self.trust_xheaders)
565 if not self.ip:
567 if not self.ip:
566 warning = "WARNING: The notebook server is listening on all IP addresses"
568 warning = "WARNING: The notebook server is listening on all IP addresses"
567 if ssl_options is None:
569 if ssl_options is None:
568 self.log.critical(warning + " and not using encryption. This "
570 self.log.critical(warning + " and not using encryption. This "
569 "is not recommended.")
571 "is not recommended.")
570 if not self.password:
572 if not self.password:
571 self.log.critical(warning + " and not using authentication. "
573 self.log.critical(warning + " and not using authentication. "
572 "This is highly insecure and not recommended.")
574 "This is highly insecure and not recommended.")
573 success = None
575 success = None
574 for port in random_ports(self.port, self.port_retries+1):
576 for port in random_ports(self.port, self.port_retries+1):
575 try:
577 try:
576 self.http_server.listen(port, self.ip)
578 self.http_server.listen(port, self.ip)
577 except socket.error as e:
579 except socket.error as e:
578 if e.errno == errno.EADDRINUSE:
580 if e.errno == errno.EADDRINUSE:
579 self.log.info('The port %i is already in use, trying another random port.' % port)
581 self.log.info('The port %i is already in use, trying another random port.' % port)
580 continue
582 continue
581 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
583 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
582 self.log.warn("Permission to listen on port %i denied" % port)
584 self.log.warn("Permission to listen on port %i denied" % port)
583 continue
585 continue
584 else:
586 else:
585 raise
587 raise
586 else:
588 else:
587 self.port = port
589 self.port = port
588 success = True
590 success = True
589 break
591 break
590 if not success:
592 if not success:
591 self.log.critical('ERROR: the notebook server could not be started because '
593 self.log.critical('ERROR: the notebook server could not be started because '
592 'no available port could be found.')
594 'no available port could be found.')
593 self.exit(1)
595 self.exit(1)
594
596
595 def init_signal(self):
597 def init_signal(self):
596 if not sys.platform.startswith('win'):
598 if not sys.platform.startswith('win'):
597 signal.signal(signal.SIGINT, self._handle_sigint)
599 signal.signal(signal.SIGINT, self._handle_sigint)
598 signal.signal(signal.SIGTERM, self._signal_stop)
600 signal.signal(signal.SIGTERM, self._signal_stop)
599 if hasattr(signal, 'SIGUSR1'):
601 if hasattr(signal, 'SIGUSR1'):
600 # Windows doesn't support SIGUSR1
602 # Windows doesn't support SIGUSR1
601 signal.signal(signal.SIGUSR1, self._signal_info)
603 signal.signal(signal.SIGUSR1, self._signal_info)
602 if hasattr(signal, 'SIGINFO'):
604 if hasattr(signal, 'SIGINFO'):
603 # only on BSD-based systems
605 # only on BSD-based systems
604 signal.signal(signal.SIGINFO, self._signal_info)
606 signal.signal(signal.SIGINFO, self._signal_info)
605
607
606 def _handle_sigint(self, sig, frame):
608 def _handle_sigint(self, sig, frame):
607 """SIGINT handler spawns confirmation dialog"""
609 """SIGINT handler spawns confirmation dialog"""
608 # register more forceful signal handler for ^C^C case
610 # register more forceful signal handler for ^C^C case
609 signal.signal(signal.SIGINT, self._signal_stop)
611 signal.signal(signal.SIGINT, self._signal_stop)
610 # request confirmation dialog in bg thread, to avoid
612 # request confirmation dialog in bg thread, to avoid
611 # blocking the App
613 # blocking the App
612 thread = threading.Thread(target=self._confirm_exit)
614 thread = threading.Thread(target=self._confirm_exit)
613 thread.daemon = True
615 thread.daemon = True
614 thread.start()
616 thread.start()
615
617
616 def _restore_sigint_handler(self):
618 def _restore_sigint_handler(self):
617 """callback for restoring original SIGINT handler"""
619 """callback for restoring original SIGINT handler"""
618 signal.signal(signal.SIGINT, self._handle_sigint)
620 signal.signal(signal.SIGINT, self._handle_sigint)
619
621
620 def _confirm_exit(self):
622 def _confirm_exit(self):
621 """confirm shutdown on ^C
623 """confirm shutdown on ^C
622
624
623 A second ^C, or answering 'y' within 5s will cause shutdown,
625 A second ^C, or answering 'y' within 5s will cause shutdown,
624 otherwise original SIGINT handler will be restored.
626 otherwise original SIGINT handler will be restored.
625
627
626 This doesn't work on Windows.
628 This doesn't work on Windows.
627 """
629 """
628 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
630 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
629 time.sleep(0.1)
631 time.sleep(0.1)
630 info = self.log.info
632 info = self.log.info
631 info('interrupted')
633 info('interrupted')
632 print(self.notebook_info())
634 print(self.notebook_info())
633 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
635 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
634 sys.stdout.flush()
636 sys.stdout.flush()
635 r,w,x = select.select([sys.stdin], [], [], 5)
637 r,w,x = select.select([sys.stdin], [], [], 5)
636 if r:
638 if r:
637 line = sys.stdin.readline()
639 line = sys.stdin.readline()
638 if line.lower().startswith('y'):
640 if line.lower().startswith('y'):
639 self.log.critical("Shutdown confirmed")
641 self.log.critical("Shutdown confirmed")
640 ioloop.IOLoop.instance().stop()
642 ioloop.IOLoop.instance().stop()
641 return
643 return
642 else:
644 else:
643 print("No answer for 5s:", end=' ')
645 print("No answer for 5s:", end=' ')
644 print("resuming operation...")
646 print("resuming operation...")
645 # no answer, or answer is no:
647 # no answer, or answer is no:
646 # set it back to original SIGINT handler
648 # set it back to original SIGINT handler
647 # use IOLoop.add_callback because signal.signal must be called
649 # use IOLoop.add_callback because signal.signal must be called
648 # from main thread
650 # from main thread
649 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
651 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
650
652
651 def _signal_stop(self, sig, frame):
653 def _signal_stop(self, sig, frame):
652 self.log.critical("received signal %s, stopping", sig)
654 self.log.critical("received signal %s, stopping", sig)
653 ioloop.IOLoop.instance().stop()
655 ioloop.IOLoop.instance().stop()
654
656
655 def _signal_info(self, sig, frame):
657 def _signal_info(self, sig, frame):
656 print(self.notebook_info())
658 print(self.notebook_info())
657
659
658 def init_components(self):
660 def init_components(self):
659 """Check the components submodule, and warn if it's unclean"""
661 """Check the components submodule, and warn if it's unclean"""
660 status = submodule.check_submodule_status()
662 status = submodule.check_submodule_status()
661 if status == 'missing':
663 if status == 'missing':
662 self.log.warn("components submodule missing, running `git submodule update`")
664 self.log.warn("components submodule missing, running `git submodule update`")
663 submodule.update_submodules(submodule.ipython_parent())
665 submodule.update_submodules(submodule.ipython_parent())
664 elif status == 'unclean':
666 elif status == 'unclean':
665 self.log.warn("components submodule unclean, you may see 404s on static/components")
667 self.log.warn("components submodule unclean, you may see 404s on static/components")
666 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
668 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
667
669
668
670
669 @catch_config_error
671 @catch_config_error
670 def initialize(self, argv=None):
672 def initialize(self, argv=None):
671 super(NotebookApp, self).initialize(argv)
673 super(NotebookApp, self).initialize(argv)
672 self.init_logging()
674 self.init_logging()
673 self.init_kernel_argv()
675 self.init_kernel_argv()
674 self.init_configurables()
676 self.init_configurables()
675 self.init_components()
677 self.init_components()
676 self.init_webapp()
678 self.init_webapp()
677 self.init_signal()
679 self.init_signal()
678
680
679 def cleanup_kernels(self):
681 def cleanup_kernels(self):
680 """Shutdown all kernels.
682 """Shutdown all kernels.
681
683
682 The kernels will shutdown themselves when this process no longer exists,
684 The kernels will shutdown themselves when this process no longer exists,
683 but explicit shutdown allows the KernelManagers to cleanup the connection files.
685 but explicit shutdown allows the KernelManagers to cleanup the connection files.
684 """
686 """
685 self.log.info('Shutting down kernels')
687 self.log.info('Shutting down kernels')
686 self.kernel_manager.shutdown_all()
688 self.kernel_manager.shutdown_all()
687
689
688 def notebook_info(self):
690 def notebook_info(self):
689 "Return the current working directory and the server url information"
691 "Return the current working directory and the server url information"
690 info = self.notebook_manager.info_string() + "\n"
692 info = self.notebook_manager.info_string() + "\n"
691 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
693 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
692 return info + "The IPython Notebook is running at: %s" % self._url
694 return info + "The IPython Notebook is running at: %s" % self._url
693
695
694 def start(self):
696 def start(self):
695 """ Start the IPython Notebook server app, after initialization
697 """ Start the IPython Notebook server app, after initialization
696
698
697 This method takes no arguments so all configuration and initialization
699 This method takes no arguments so all configuration and initialization
698 must be done prior to calling this method."""
700 must be done prior to calling this method."""
699 ip = self.ip if self.ip else '[all ip addresses on your system]'
701 ip = self.ip if self.ip else '[all ip addresses on your system]'
700 proto = 'https' if self.certfile else 'http'
702 proto = 'https' if self.certfile else 'http'
701 info = self.log.info
703 info = self.log.info
702 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
704 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
703 self.base_project_url)
705 self.base_project_url)
704 for line in self.notebook_info().split("\n"):
706 for line in self.notebook_info().split("\n"):
705 info(line)
707 info(line)
706 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
708 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
707
709
708 if self.open_browser or self.file_to_run:
710 if self.open_browser or self.file_to_run:
709 ip = self.ip or localhost()
711 ip = self.ip or localhost()
710 try:
712 try:
711 browser = webbrowser.get(self.browser or None)
713 browser = webbrowser.get(self.browser or None)
712 except webbrowser.Error as e:
714 except webbrowser.Error as e:
713 self.log.warn('No web browser found: %s.' % e)
715 self.log.warn('No web browser found: %s.' % e)
714 browser = None
716 browser = None
715
717
716 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
718 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
717 f = self.file_to_run
719 f = self.file_to_run
718 if f:
720 if f:
719 if f.startswith(nbdir):
721 if f.startswith(nbdir):
720 f = f[len(nbdir):]
722 f = f[len(nbdir):]
721 else:
723 else:
722 self.log.warn(
724 self.log.warn(
723 "Probably won't be able to open notebook %s "
725 "Probably won't be able to open notebook %s "
724 "because it is not in notebook_dir %s",
726 "because it is not in notebook_dir %s",
725 f, nbdir,
727 f, nbdir,
726 )
728 )
727
729
728 if os.path.isfile(self.file_to_run):
730 if os.path.isfile(self.file_to_run):
729 url = url_path_join('notebooks', f)
731 url = url_path_join('notebooks', f)
730 else:
732 else:
731 url = url_path_join('tree', f)
733 url = url_path_join('tree', f)
732 if browser:
734 if browser:
733 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
734 self.port, self.base_project_url, url), new=2)
736 self.port, self.base_project_url, url), new=2)
735 threading.Thread(target=b).start()
737 threading.Thread(target=b).start()
736 try:
738 try:
737 ioloop.IOLoop.instance().start()
739 ioloop.IOLoop.instance().start()
738 except KeyboardInterrupt:
740 except KeyboardInterrupt:
739 info("Interrupted...")
741 info("Interrupted...")
740 finally:
742 finally:
741 self.cleanup_kernels()
743 self.cleanup_kernels()
742
744
743
745
744 #-----------------------------------------------------------------------------
746 #-----------------------------------------------------------------------------
745 # Main entry point
747 # Main entry point
746 #-----------------------------------------------------------------------------
748 #-----------------------------------------------------------------------------
747
749
748 launch_new_instance = NotebookApp.launch_instance
750 launch_new_instance = NotebookApp.launch_instance
749
751
@@ -1,414 +1,414 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import io
20 import io
21 import itertools
21 import itertools
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from tornado import web
26 from tornado import web
27
27
28 from .nbmanager import NotebookManager
28 from .nbmanager import NotebookManager
29 from IPython.nbformat import current
29 from IPython.nbformat import current
30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
31 from IPython.utils import tz
31 from IPython.utils import tz
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes
34 # Classes
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class FileNotebookManager(NotebookManager):
37 class FileNotebookManager(NotebookManager):
38
38
39 save_script = Bool(False, config=True,
39 save_script = Bool(False, config=True,
40 help="""Automatically create a Python script when saving the notebook.
40 help="""Automatically create a Python script when saving the notebook.
41
41
42 For easier use of import, %run and %load across notebooks, a
42 For easier use of import, %run and %load across notebooks, a
43 <notebook-name>.py script will be created next to any
43 <notebook-name>.py script will be created next to any
44 <notebook-name>.ipynb on each save. This can also be set with the
44 <notebook-name>.ipynb on each save. This can also be set with the
45 short `--script` flag.
45 short `--script` flag.
46 """
46 """
47 )
47 )
48
48
49 checkpoint_dir = Unicode(config=True,
49 checkpoint_dir = Unicode(config=True,
50 help="""The location in which to keep notebook checkpoints
50 help="""The location in which to keep notebook checkpoints
51
51
52 By default, it is notebook-dir/.ipynb_checkpoints
52 By default, it is notebook-dir/.ipynb_checkpoints
53 """
53 """
54 )
54 )
55 def _checkpoint_dir_default(self):
55 def _checkpoint_dir_default(self):
56 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
56 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
57
57
58 def _checkpoint_dir_changed(self, name, old, new):
58 def _checkpoint_dir_changed(self, name, old, new):
59 """do a bit of validation of the checkpoint dir"""
59 """do a bit of validation of the checkpoint dir"""
60 if not os.path.isabs(new):
60 if not os.path.isabs(new):
61 # If we receive a non-absolute path, make it absolute.
61 # If we receive a non-absolute path, make it absolute.
62 abs_new = os.path.abspath(new)
62 abs_new = os.path.abspath(new)
63 self.checkpoint_dir = abs_new
63 self.checkpoint_dir = abs_new
64 return
64 return
65 if os.path.exists(new) and not os.path.isdir(new):
65 if os.path.exists(new) and not os.path.isdir(new):
66 raise TraitError("checkpoint dir %r is not a directory" % new)
66 raise TraitError("checkpoint dir %r is not a directory" % new)
67 if not os.path.exists(new):
67 if not os.path.exists(new):
68 self.log.info("Creating checkpoint dir %s", new)
68 self.log.info("Creating checkpoint dir %s", new)
69 try:
69 try:
70 os.mkdir(new)
70 os.mkdir(new)
71 except:
71 except:
72 raise TraitError("Couldn't create checkpoint dir %r" % new)
72 raise TraitError("Couldn't create checkpoint dir %r" % new)
73
73
74 def get_notebook_names(self, path=''):
74 def get_notebook_names(self, path=''):
75 """List all notebook names in the notebook dir and path."""
75 """List all notebook names in the notebook dir and path."""
76 path = path.strip('/')
76 path = path.strip('/')
77 if not os.path.isdir(self.get_os_path(path=path)):
77 if not os.path.isdir(self.get_os_path(path=path)):
78 raise web.HTTPError(404, 'Directory not found: ' + path)
78 raise web.HTTPError(404, 'Directory not found: ' + path)
79 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
79 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
80 names = [os.path.basename(name)
80 names = [os.path.basename(name)
81 for name in names]
81 for name in names]
82 return names
82 return names
83
83
84 def increment_filename(self, basename, path='', ext='.ipynb'):
84 def increment_filename(self, basename, path='', ext='.ipynb'):
85 """Return a non-used filename of the form basename<int>."""
85 """Return a non-used filename of the form basename<int>."""
86 path = path.strip('/')
86 path = path.strip('/')
87 for i in itertools.count():
87 for i in itertools.count():
88 name = u'{basename}{i}{ext}'.format(basename=basename, i=i, ext=ext)
88 name = u'{basename}{i}{ext}'.format(basename=basename, i=i, ext=ext)
89 os_path = self.get_os_path(name, path)
89 os_path = self.get_os_path(name, path)
90 if not os.path.isfile(os_path):
90 if not os.path.isfile(os_path):
91 break
91 break
92 return name
92 return name
93
93
94 def path_exists(self, path):
94 def path_exists(self, path):
95 """Does the API-style path (directory) actually exist?
95 """Does the API-style path (directory) actually exist?
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 path : string
99 path : string
100 The path to check. This is an API path (`/` separated,
100 The path to check. This is an API path (`/` separated,
101 relative to base notebook-dir).
101 relative to base notebook-dir).
102
102
103 Returns
103 Returns
104 -------
104 -------
105 exists : bool
105 exists : bool
106 Whether the path is indeed a directory.
106 Whether the path is indeed a directory.
107 """
107 """
108 path = path.strip('/')
108 path = path.strip('/')
109 os_path = self.get_os_path(path=path)
109 os_path = self.get_os_path(path=path)
110 return os.path.isdir(os_path)
110 return os.path.isdir(os_path)
111
111
112 def get_os_path(self, name=None, path=''):
112 def get_os_path(self, name=None, path=''):
113 """Given a notebook name and a URL path, return its file system
113 """Given a notebook name and a URL path, return its file system
114 path.
114 path.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 name : string
118 name : string
119 The name of a notebook file with the .ipynb extension
119 The name of a notebook file with the .ipynb extension
120 path : string
120 path : string
121 The relative URL path (with '/' as separator) to the named
121 The relative URL path (with '/' as separator) to the named
122 notebook.
122 notebook.
123
123
124 Returns
124 Returns
125 -------
125 -------
126 path : string
126 path : string
127 A file system path that combines notebook_dir (location where
127 A file system path that combines notebook_dir (location where
128 server started), the relative path, and the filename with the
128 server started), the relative path, and the filename with the
129 current operating system's url.
129 current operating system's url.
130 """
130 """
131 parts = path.strip('/').split('/')
131 parts = path.strip('/').split('/')
132 parts = [p for p in parts if p != ''] # remove duplicate splits
132 parts = [p for p in parts if p != ''] # remove duplicate splits
133 if name is not None:
133 if name is not None:
134 parts.append(name)
134 parts.append(name)
135 path = os.path.join(self.notebook_dir, *parts)
135 path = os.path.join(self.notebook_dir, *parts)
136 return path
136 return path
137
137
138 def notebook_exists(self, name, path=''):
138 def notebook_exists(self, name, path=''):
139 """Returns a True if the notebook exists. Else, returns False.
139 """Returns a True if the notebook exists. Else, returns False.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 name : string
143 name : string
144 The name of the notebook you are checking.
144 The name of the notebook you are checking.
145 path : string
145 path : string
146 The relative path to the notebook (with '/' as separator)
146 The relative path to the notebook (with '/' as separator)
147
147
148 Returns
148 Returns
149 -------
149 -------
150 bool
150 bool
151 """
151 """
152 path = path.strip('/')
152 path = path.strip('/')
153 nbpath = self.get_os_path(name, path=path)
153 nbpath = self.get_os_path(name, path=path)
154 return os.path.isfile(nbpath)
154 return os.path.isfile(nbpath)
155
155
156 def list_notebooks(self, path):
156 def list_notebooks(self, path):
157 """Returns a list of dictionaries that are the standard model
157 """Returns a list of dictionaries that are the standard model
158 for all notebooks in the relative 'path'.
158 for all notebooks in the relative 'path'.
159
159
160 Parameters
160 Parameters
161 ----------
161 ----------
162 path : str
162 path : str
163 the URL path that describes the relative path for the
163 the URL path that describes the relative path for the
164 listed notebooks
164 listed notebooks
165
165
166 Returns
166 Returns
167 -------
167 -------
168 notebooks : list of dicts
168 notebooks : list of dicts
169 a list of the notebook models without 'content'
169 a list of the notebook models without 'content'
170 """
170 """
171 path = path.strip('/')
171 path = path.strip('/')
172 notebook_names = self.get_notebook_names(path)
172 notebook_names = self.get_notebook_names(path)
173 notebooks = []
173 notebooks = []
174 for name in notebook_names:
174 for name in notebook_names:
175 model = self.get_notebook_model(name, path, content=False)
175 model = self.get_notebook_model(name, path, content=False)
176 notebooks.append(model)
176 notebooks.append(model)
177 notebooks = sorted(notebooks, key=lambda item: item['name'])
177 notebooks = sorted(notebooks, key=lambda item: item['name'])
178 return notebooks
178 return notebooks
179
179
180 def get_notebook_model(self, name, path='', content=True):
180 def get_notebook_model(self, name, path='', content=True):
181 """ Takes a path and name for a notebook and returns it's model
181 """ Takes a path and name for a notebook and returns its model
182
182
183 Parameters
183 Parameters
184 ----------
184 ----------
185 name : str
185 name : str
186 the name of the notebook
186 the name of the notebook
187 path : str
187 path : str
188 the URL path that describes the relative path for
188 the URL path that describes the relative path for
189 the notebook
189 the notebook
190
190
191 Returns
191 Returns
192 -------
192 -------
193 model : dict
193 model : dict
194 the notebook model. If contents=True, returns the 'contents'
194 the notebook model. If contents=True, returns the 'contents'
195 dict in the model as well.
195 dict in the model as well.
196 """
196 """
197 path = path.strip('/')
197 path = path.strip('/')
198 if not self.notebook_exists(name=name, path=path):
198 if not self.notebook_exists(name=name, path=path):
199 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
199 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
200 os_path = self.get_os_path(name, path)
200 os_path = self.get_os_path(name, path)
201 info = os.stat(os_path)
201 info = os.stat(os_path)
202 last_modified = tz.utcfromtimestamp(info.st_mtime)
202 last_modified = tz.utcfromtimestamp(info.st_mtime)
203 created = tz.utcfromtimestamp(info.st_ctime)
203 created = tz.utcfromtimestamp(info.st_ctime)
204 # Create the notebook model.
204 # Create the notebook model.
205 model ={}
205 model ={}
206 model['name'] = name
206 model['name'] = name
207 model['path'] = path
207 model['path'] = path
208 model['last_modified'] = last_modified
208 model['last_modified'] = last_modified
209 model['created'] = created
209 model['created'] = created
210 if content is True:
210 if content is True:
211 with io.open(os_path, 'r', encoding='utf-8') as f:
211 with io.open(os_path, 'r', encoding='utf-8') as f:
212 try:
212 try:
213 nb = current.read(f, u'json')
213 nb = current.read(f, u'json')
214 except Exception as e:
214 except Exception as e:
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
216 model['content'] = nb
216 model['content'] = nb
217 return model
217 return model
218
218
219 def save_notebook_model(self, model, name='', path=''):
219 def save_notebook_model(self, model, name='', path=''):
220 """Save the notebook model and return the model with no content."""
220 """Save the notebook model and return the model with no content."""
221 path = path.strip('/')
221 path = path.strip('/')
222
222
223 if 'content' not in model:
223 if 'content' not in model:
224 raise web.HTTPError(400, u'No notebook JSON data provided')
224 raise web.HTTPError(400, u'No notebook JSON data provided')
225
225
226 # One checkpoint should always exist
226 # One checkpoint should always exist
227 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
227 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
228 self.create_checkpoint(name, path)
228 self.create_checkpoint(name, path)
229
229
230 new_path = model.get('path', path).strip('/')
230 new_path = model.get('path', path).strip('/')
231 new_name = model.get('name', name)
231 new_name = model.get('name', name)
232
232
233 if path != new_path or name != new_name:
233 if path != new_path or name != new_name:
234 self.rename_notebook(name, path, new_name, new_path)
234 self.rename_notebook(name, path, new_name, new_path)
235
235
236 # Save the notebook file
236 # Save the notebook file
237 os_path = self.get_os_path(new_name, new_path)
237 os_path = self.get_os_path(new_name, new_path)
238 nb = current.to_notebook_json(model['content'])
238 nb = current.to_notebook_json(model['content'])
239 if 'name' in nb['metadata']:
239 if 'name' in nb['metadata']:
240 nb['metadata']['name'] = u''
240 nb['metadata']['name'] = u''
241 try:
241 try:
242 self.log.debug("Autosaving notebook %s", os_path)
242 self.log.debug("Autosaving notebook %s", os_path)
243 with io.open(os_path, 'w', encoding='utf-8') as f:
243 with io.open(os_path, 'w', encoding='utf-8') as f:
244 current.write(nb, f, u'json')
244 current.write(nb, f, u'json')
245 except Exception as e:
245 except Exception as e:
246 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
246 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
247
247
248 # Save .py script as well
248 # Save .py script as well
249 if self.save_script:
249 if self.save_script:
250 py_path = os.path.splitext(os_path)[0] + '.py'
250 py_path = os.path.splitext(os_path)[0] + '.py'
251 self.log.debug("Writing script %s", py_path)
251 self.log.debug("Writing script %s", py_path)
252 try:
252 try:
253 with io.open(py_path, 'w', encoding='utf-8') as f:
253 with io.open(py_path, 'w', encoding='utf-8') as f:
254 current.write(nb, f, u'py')
254 current.write(nb, f, u'py')
255 except Exception as e:
255 except Exception as e:
256 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
256 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
257
257
258 model = self.get_notebook_model(new_name, new_path, content=False)
258 model = self.get_notebook_model(new_name, new_path, content=False)
259 return model
259 return model
260
260
261 def update_notebook_model(self, model, name, path=''):
261 def update_notebook_model(self, model, name, path=''):
262 """Update the notebook's path and/or name"""
262 """Update the notebook's path and/or name"""
263 path = path.strip('/')
263 path = path.strip('/')
264 new_name = model.get('name', name)
264 new_name = model.get('name', name)
265 new_path = model.get('path', path).strip('/')
265 new_path = model.get('path', path).strip('/')
266 if path != new_path or name != new_name:
266 if path != new_path or name != new_name:
267 self.rename_notebook(name, path, new_name, new_path)
267 self.rename_notebook(name, path, new_name, new_path)
268 model = self.get_notebook_model(new_name, new_path, content=False)
268 model = self.get_notebook_model(new_name, new_path, content=False)
269 return model
269 return model
270
270
271 def delete_notebook_model(self, name, path=''):
271 def delete_notebook_model(self, name, path=''):
272 """Delete notebook by name and path."""
272 """Delete notebook by name and path."""
273 path = path.strip('/')
273 path = path.strip('/')
274 os_path = self.get_os_path(name, path)
274 os_path = self.get_os_path(name, path)
275 if not os.path.isfile(os_path):
275 if not os.path.isfile(os_path):
276 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
276 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
277
277
278 # clear checkpoints
278 # clear checkpoints
279 for checkpoint in self.list_checkpoints(name, path):
279 for checkpoint in self.list_checkpoints(name, path):
280 checkpoint_id = checkpoint['id']
280 checkpoint_id = checkpoint['id']
281 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
281 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
282 if os.path.isfile(cp_path):
282 if os.path.isfile(cp_path):
283 self.log.debug("Unlinking checkpoint %s", cp_path)
283 self.log.debug("Unlinking checkpoint %s", cp_path)
284 os.unlink(cp_path)
284 os.unlink(cp_path)
285
285
286 self.log.debug("Unlinking notebook %s", os_path)
286 self.log.debug("Unlinking notebook %s", os_path)
287 os.unlink(os_path)
287 os.unlink(os_path)
288
288
289 def rename_notebook(self, old_name, old_path, new_name, new_path):
289 def rename_notebook(self, old_name, old_path, new_name, new_path):
290 """Rename a notebook."""
290 """Rename a notebook."""
291 old_path = old_path.strip('/')
291 old_path = old_path.strip('/')
292 new_path = new_path.strip('/')
292 new_path = new_path.strip('/')
293 if new_name == old_name and new_path == old_path:
293 if new_name == old_name and new_path == old_path:
294 return
294 return
295
295
296 new_os_path = self.get_os_path(new_name, new_path)
296 new_os_path = self.get_os_path(new_name, new_path)
297 old_os_path = self.get_os_path(old_name, old_path)
297 old_os_path = self.get_os_path(old_name, old_path)
298
298
299 # Should we proceed with the move?
299 # Should we proceed with the move?
300 if os.path.isfile(new_os_path):
300 if os.path.isfile(new_os_path):
301 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
301 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
302 if self.save_script:
302 if self.save_script:
303 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
303 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
304 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
304 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
305 if os.path.isfile(new_py_path):
305 if os.path.isfile(new_py_path):
306 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
306 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
307
307
308 # Move the notebook file
308 # Move the notebook file
309 try:
309 try:
310 os.rename(old_os_path, new_os_path)
310 os.rename(old_os_path, new_os_path)
311 except Exception as e:
311 except Exception as e:
312 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
312 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
313
313
314 # Move the checkpoints
314 # Move the checkpoints
315 old_checkpoints = self.list_checkpoints(old_name, old_path)
315 old_checkpoints = self.list_checkpoints(old_name, old_path)
316 for cp in old_checkpoints:
316 for cp in old_checkpoints:
317 checkpoint_id = cp['id']
317 checkpoint_id = cp['id']
318 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
318 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
319 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
319 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
320 if os.path.isfile(old_cp_path):
320 if os.path.isfile(old_cp_path):
321 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
321 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
322 os.rename(old_cp_path, new_cp_path)
322 os.rename(old_cp_path, new_cp_path)
323
323
324 # Move the .py script
324 # Move the .py script
325 if self.save_script:
325 if self.save_script:
326 os.rename(old_py_path, new_py_path)
326 os.rename(old_py_path, new_py_path)
327
327
328 # Checkpoint-related utilities
328 # Checkpoint-related utilities
329
329
330 def get_checkpoint_path(self, checkpoint_id, name, path=''):
330 def get_checkpoint_path(self, checkpoint_id, name, path=''):
331 """find the path to a checkpoint"""
331 """find the path to a checkpoint"""
332 path = path.strip('/')
332 path = path.strip('/')
333 basename, _ = os.path.splitext(name)
333 basename, _ = os.path.splitext(name)
334 filename = u"{name}-{checkpoint_id}{ext}".format(
334 filename = u"{name}-{checkpoint_id}{ext}".format(
335 name=basename,
335 name=basename,
336 checkpoint_id=checkpoint_id,
336 checkpoint_id=checkpoint_id,
337 ext=self.filename_ext,
337 ext=self.filename_ext,
338 )
338 )
339 cp_path = os.path.join(path, self.checkpoint_dir, filename)
339 cp_path = os.path.join(path, self.checkpoint_dir, filename)
340 return cp_path
340 return cp_path
341
341
342 def get_checkpoint_model(self, checkpoint_id, name, path=''):
342 def get_checkpoint_model(self, checkpoint_id, name, path=''):
343 """construct the info dict for a given checkpoint"""
343 """construct the info dict for a given checkpoint"""
344 path = path.strip('/')
344 path = path.strip('/')
345 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
345 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
346 stats = os.stat(cp_path)
346 stats = os.stat(cp_path)
347 last_modified = tz.utcfromtimestamp(stats.st_mtime)
347 last_modified = tz.utcfromtimestamp(stats.st_mtime)
348 info = dict(
348 info = dict(
349 id = checkpoint_id,
349 id = checkpoint_id,
350 last_modified = last_modified,
350 last_modified = last_modified,
351 )
351 )
352 return info
352 return info
353
353
354 # public checkpoint API
354 # public checkpoint API
355
355
356 def create_checkpoint(self, name, path=''):
356 def create_checkpoint(self, name, path=''):
357 """Create a checkpoint from the current state of a notebook"""
357 """Create a checkpoint from the current state of a notebook"""
358 path = path.strip('/')
358 path = path.strip('/')
359 nb_path = self.get_os_path(name, path)
359 nb_path = self.get_os_path(name, path)
360 # only the one checkpoint ID:
360 # only the one checkpoint ID:
361 checkpoint_id = u"checkpoint"
361 checkpoint_id = u"checkpoint"
362 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
362 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
363 self.log.debug("creating checkpoint for notebook %s", name)
363 self.log.debug("creating checkpoint for notebook %s", name)
364 if not os.path.exists(self.checkpoint_dir):
364 if not os.path.exists(self.checkpoint_dir):
365 os.mkdir(self.checkpoint_dir)
365 os.mkdir(self.checkpoint_dir)
366 shutil.copy2(nb_path, cp_path)
366 shutil.copy2(nb_path, cp_path)
367
367
368 # return the checkpoint info
368 # return the checkpoint info
369 return self.get_checkpoint_model(checkpoint_id, name, path)
369 return self.get_checkpoint_model(checkpoint_id, name, path)
370
370
371 def list_checkpoints(self, name, path=''):
371 def list_checkpoints(self, name, path=''):
372 """list the checkpoints for a given notebook
372 """list the checkpoints for a given notebook
373
373
374 This notebook manager currently only supports one checkpoint per notebook.
374 This notebook manager currently only supports one checkpoint per notebook.
375 """
375 """
376 path = path.strip('/')
376 path = path.strip('/')
377 checkpoint_id = "checkpoint"
377 checkpoint_id = "checkpoint"
378 path = self.get_checkpoint_path(checkpoint_id, name, path)
378 path = self.get_checkpoint_path(checkpoint_id, name, path)
379 if not os.path.exists(path):
379 if not os.path.exists(path):
380 return []
380 return []
381 else:
381 else:
382 return [self.get_checkpoint_model(checkpoint_id, name, path)]
382 return [self.get_checkpoint_model(checkpoint_id, name, path)]
383
383
384
384
385 def restore_checkpoint(self, checkpoint_id, name, path=''):
385 def restore_checkpoint(self, checkpoint_id, name, path=''):
386 """restore a notebook to a checkpointed state"""
386 """restore a notebook to a checkpointed state"""
387 path = path.strip('/')
387 path = path.strip('/')
388 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
388 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
389 nb_path = self.get_os_path(name, path)
389 nb_path = self.get_os_path(name, path)
390 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
390 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
391 if not os.path.isfile(cp_path):
391 if not os.path.isfile(cp_path):
392 self.log.debug("checkpoint file does not exist: %s", cp_path)
392 self.log.debug("checkpoint file does not exist: %s", cp_path)
393 raise web.HTTPError(404,
393 raise web.HTTPError(404,
394 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
394 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
395 )
395 )
396 # ensure notebook is readable (never restore from an unreadable notebook)
396 # ensure notebook is readable (never restore from an unreadable notebook)
397 with io.open(cp_path, 'r', encoding='utf-8') as f:
397 with io.open(cp_path, 'r', encoding='utf-8') as f:
398 nb = current.read(f, u'json')
398 nb = current.read(f, u'json')
399 shutil.copy2(cp_path, nb_path)
399 shutil.copy2(cp_path, nb_path)
400 self.log.debug("copying %s -> %s", cp_path, nb_path)
400 self.log.debug("copying %s -> %s", cp_path, nb_path)
401
401
402 def delete_checkpoint(self, checkpoint_id, name, path=''):
402 def delete_checkpoint(self, checkpoint_id, name, path=''):
403 """delete a notebook's checkpoint"""
403 """delete a notebook's checkpoint"""
404 path = path.strip('/')
404 path = path.strip('/')
405 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
405 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
406 if not os.path.isfile(cp_path):
406 if not os.path.isfile(cp_path):
407 raise web.HTTPError(404,
407 raise web.HTTPError(404,
408 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
408 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
409 )
409 )
410 self.log.debug("unlinking %s", cp_path)
410 self.log.debug("unlinking %s", cp_path)
411 os.unlink(cp_path)
411 os.unlink(cp_path)
412
412
413 def info_string(self):
413 def info_string(self):
414 return "Serving notebooks from local directory: %s" % self.notebook_dir
414 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,281 +1,280 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the notebooks web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import json
19 import json
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.html.utils import url_path_join, url_escape
23 from IPython.html.utils import url_path_join, url_escape
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25
25
26 from IPython.html.base.handlers import IPythonHandler, json_errors
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
27 notebook_path_regex, path_regex,
28 notebook_name_regex)
27
29
28 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
29 # Notebook web service handlers
31 # Notebook web service handlers
30 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
31
33
32
34
33 class NotebookHandler(IPythonHandler):
35 class NotebookHandler(IPythonHandler):
34
36
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
36
38
37 def notebook_location(self, name, path=''):
39 def notebook_location(self, name, path=''):
38 """Return the full URL location of a notebook based.
40 """Return the full URL location of a notebook based.
39
41
40 Parameters
42 Parameters
41 ----------
43 ----------
42 name : unicode
44 name : unicode
43 The base name of the notebook, such as "foo.ipynb".
45 The base name of the notebook, such as "foo.ipynb".
44 path : unicode
46 path : unicode
45 The URL path of the notebook.
47 The URL path of the notebook.
46 """
48 """
47 return url_escape(url_path_join(
49 return url_escape(url_path_join(
48 self.base_project_url, 'api', 'notebooks', path, name
50 self.base_project_url, 'api', 'notebooks', path, name
49 ))
51 ))
50
52
51 def _finish_model(self, model, location=True):
53 def _finish_model(self, model, location=True):
52 """Finish a JSON request with a model, setting relevant headers, etc."""
54 """Finish a JSON request with a model, setting relevant headers, etc."""
53 if location:
55 if location:
54 location = self.notebook_location(model['name'], model['path'])
56 location = self.notebook_location(model['name'], model['path'])
55 self.set_header('Location', location)
57 self.set_header('Location', location)
56 self.set_header('Last-Modified', model['last_modified'])
58 self.set_header('Last-Modified', model['last_modified'])
57 self.finish(json.dumps(model, default=date_default))
59 self.finish(json.dumps(model, default=date_default))
58
60
59 @web.authenticated
61 @web.authenticated
60 @json_errors
62 @json_errors
61 def get(self, path='', name=None):
63 def get(self, path='', name=None):
62 """Return a Notebook or list of notebooks.
64 """Return a Notebook or list of notebooks.
63
65
64 * GET with path and no notebook name lists notebooks in a directory
66 * GET with path and no notebook name lists notebooks in a directory
65 * GET with path and notebook name returns notebook JSON
67 * GET with path and notebook name returns notebook JSON
66 """
68 """
67 nbm = self.notebook_manager
69 nbm = self.notebook_manager
68 # Check to see if a notebook name was given
70 # Check to see if a notebook name was given
69 if name is None:
71 if name is None:
70 # List notebooks in 'path'
72 # List notebooks in 'path'
71 notebooks = nbm.list_notebooks(path)
73 notebooks = nbm.list_notebooks(path)
72 self.finish(json.dumps(notebooks, default=date_default))
74 self.finish(json.dumps(notebooks, default=date_default))
73 return
75 return
74 # get and return notebook representation
76 # get and return notebook representation
75 model = nbm.get_notebook_model(name, path)
77 model = nbm.get_notebook_model(name, path)
76 self._finish_model(model, location=False)
78 self._finish_model(model, location=False)
77
79
78 @web.authenticated
80 @web.authenticated
79 @json_errors
81 @json_errors
80 def patch(self, path='', name=None):
82 def patch(self, path='', name=None):
81 """PATCH renames a notebook without re-uploading content."""
83 """PATCH renames a notebook without re-uploading content."""
82 nbm = self.notebook_manager
84 nbm = self.notebook_manager
83 if name is None:
85 if name is None:
84 raise web.HTTPError(400, u'Notebook name missing')
86 raise web.HTTPError(400, u'Notebook name missing')
85 model = self.get_json_body()
87 model = self.get_json_body()
86 if model is None:
88 if model is None:
87 raise web.HTTPError(400, u'JSON body missing')
89 raise web.HTTPError(400, u'JSON body missing')
88 model = nbm.update_notebook_model(model, name, path)
90 model = nbm.update_notebook_model(model, name, path)
89 self._finish_model(model)
91 self._finish_model(model)
90
92
91 def _copy_notebook(self, copy_from, path, copy_to=None):
93 def _copy_notebook(self, copy_from, path, copy_to=None):
92 """Copy a notebook in path, optionally specifying the new name.
94 """Copy a notebook in path, optionally specifying the new name.
93
95
94 Only support copying within the same directory.
96 Only support copying within the same directory.
95 """
97 """
96 self.log.info(u"Copying notebook from %s/%s to %s/%s",
98 self.log.info(u"Copying notebook from %s/%s to %s/%s",
97 path, copy_from,
99 path, copy_from,
98 path, copy_to or '',
100 path, copy_to or '',
99 )
101 )
100 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
102 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
101 self.set_status(201)
103 self.set_status(201)
102 self._finish_model(model)
104 self._finish_model(model)
103
105
104 def _upload_notebook(self, model, path, name=None):
106 def _upload_notebook(self, model, path, name=None):
105 """Upload a notebook
107 """Upload a notebook
106
108
107 If name specified, create it in path/name.
109 If name specified, create it in path/name.
108 """
110 """
109 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
111 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
110 if name:
112 if name:
111 model['name'] = name
113 model['name'] = name
112
114
113 model = self.notebook_manager.create_notebook_model(model, path)
115 model = self.notebook_manager.create_notebook_model(model, path)
114 self.set_status(201)
116 self.set_status(201)
115 self._finish_model(model)
117 self._finish_model(model)
116
118
117 def _create_empty_notebook(self, path, name=None):
119 def _create_empty_notebook(self, path, name=None):
118 """Create an empty notebook in path
120 """Create an empty notebook in path
119
121
120 If name specified, create it in path/name.
122 If name specified, create it in path/name.
121 """
123 """
122 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
124 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
123 model = {}
125 model = {}
124 if name:
126 if name:
125 model['name'] = name
127 model['name'] = name
126 model = self.notebook_manager.create_notebook_model(model, path=path)
128 model = self.notebook_manager.create_notebook_model(model, path=path)
127 self.set_status(201)
129 self.set_status(201)
128 self._finish_model(model)
130 self._finish_model(model)
129
131
130 def _save_notebook(self, model, path, name):
132 def _save_notebook(self, model, path, name):
131 """Save an existing notebook."""
133 """Save an existing notebook."""
132 self.log.info(u"Saving notebook at %s/%s", path, name)
134 self.log.info(u"Saving notebook at %s/%s", path, name)
133 model = self.notebook_manager.save_notebook_model(model, name, path)
135 model = self.notebook_manager.save_notebook_model(model, name, path)
134 if model['path'] != path.strip('/') or model['name'] != name:
136 if model['path'] != path.strip('/') or model['name'] != name:
135 # a rename happened, set Location header
137 # a rename happened, set Location header
136 location = True
138 location = True
137 else:
139 else:
138 location = False
140 location = False
139 self._finish_model(model, location)
141 self._finish_model(model, location)
140
142
141 @web.authenticated
143 @web.authenticated
142 @json_errors
144 @json_errors
143 def post(self, path='', name=None):
145 def post(self, path='', name=None):
144 """Create a new notebook in the specified path.
146 """Create a new notebook in the specified path.
145
147
146 POST creates new notebooks. The server always decides on the notebook name.
148 POST creates new notebooks. The server always decides on the notebook name.
147
149
148 POST /api/notebooks/path
150 POST /api/notebooks/path
149 New untitled notebook in path. If content specified, upload a
151 New untitled notebook in path. If content specified, upload a
150 notebook, otherwise start empty.
152 notebook, otherwise start empty.
151 POST /api/notebooks/path?copy=OtherNotebook.ipynb
153 POST /api/notebooks/path?copy=OtherNotebook.ipynb
152 New copy of OtherNotebook in path
154 New copy of OtherNotebook in path
153 """
155 """
154
156
155 if name is not None:
157 if name is not None:
156 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
158 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
157
159
158 model = self.get_json_body()
160 model = self.get_json_body()
159
161
160 if model is not None:
162 if model is not None:
161 copy_from = model.get('copy_from')
163 copy_from = model.get('copy_from')
162 if copy_from:
164 if copy_from:
163 if model.get('content'):
165 if model.get('content'):
164 raise web.HTTPError(400, "Can't upload and copy at the same time.")
166 raise web.HTTPError(400, "Can't upload and copy at the same time.")
165 self._copy_notebook(copy_from, path)
167 self._copy_notebook(copy_from, path)
166 else:
168 else:
167 self._upload_notebook(model, path)
169 self._upload_notebook(model, path)
168 else:
170 else:
169 self._create_empty_notebook(path)
171 self._create_empty_notebook(path)
170
172
171 @web.authenticated
173 @web.authenticated
172 @json_errors
174 @json_errors
173 def put(self, path='', name=None):
175 def put(self, path='', name=None):
174 """Saves the notebook in the location specified by name and path.
176 """Saves the notebook in the location specified by name and path.
175
177
176 PUT is very similar to POST, but the requester specifies the name,
178 PUT is very similar to POST, but the requester specifies the name,
177 whereas with POST, the server picks the name.
179 whereas with POST, the server picks the name.
178
180
179 PUT /api/notebooks/path/Name.ipynb
181 PUT /api/notebooks/path/Name.ipynb
180 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
182 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
181 in `content` key of JSON request body. If content is not specified,
183 in `content` key of JSON request body. If content is not specified,
182 create a new empty notebook.
184 create a new empty notebook.
183 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
185 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
184 Copy OtherNotebook to Name
186 Copy OtherNotebook to Name
185 """
187 """
186 if name is None:
188 if name is None:
187 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
189 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
188
190
189 model = self.get_json_body()
191 model = self.get_json_body()
190 if model:
192 if model:
191 copy_from = model.get('copy_from')
193 copy_from = model.get('copy_from')
192 if copy_from:
194 if copy_from:
193 if model.get('content'):
195 if model.get('content'):
194 raise web.HTTPError(400, "Can't upload and copy at the same time.")
196 raise web.HTTPError(400, "Can't upload and copy at the same time.")
195 self._copy_notebook(copy_from, path, name)
197 self._copy_notebook(copy_from, path, name)
196 elif self.notebook_manager.notebook_exists(name, path):
198 elif self.notebook_manager.notebook_exists(name, path):
197 self._save_notebook(model, path, name)
199 self._save_notebook(model, path, name)
198 else:
200 else:
199 self._upload_notebook(model, path, name)
201 self._upload_notebook(model, path, name)
200 else:
202 else:
201 self._create_empty_notebook(path, name)
203 self._create_empty_notebook(path, name)
202
204
203 @web.authenticated
205 @web.authenticated
204 @json_errors
206 @json_errors
205 def delete(self, path='', name=None):
207 def delete(self, path='', name=None):
206 """delete the notebook in the given notebook path"""
208 """delete the notebook in the given notebook path"""
207 nbm = self.notebook_manager
209 nbm = self.notebook_manager
208 nbm.delete_notebook_model(name, path)
210 nbm.delete_notebook_model(name, path)
209 self.set_status(204)
211 self.set_status(204)
210 self.finish()
212 self.finish()
211
213
212
214
213 class NotebookCheckpointsHandler(IPythonHandler):
215 class NotebookCheckpointsHandler(IPythonHandler):
214
216
215 SUPPORTED_METHODS = ('GET', 'POST')
217 SUPPORTED_METHODS = ('GET', 'POST')
216
218
217 @web.authenticated
219 @web.authenticated
218 @json_errors
220 @json_errors
219 def get(self, path='', name=None):
221 def get(self, path='', name=None):
220 """get lists checkpoints for a notebook"""
222 """get lists checkpoints for a notebook"""
221 nbm = self.notebook_manager
223 nbm = self.notebook_manager
222 checkpoints = nbm.list_checkpoints(name, path)
224 checkpoints = nbm.list_checkpoints(name, path)
223 data = json.dumps(checkpoints, default=date_default)
225 data = json.dumps(checkpoints, default=date_default)
224 self.finish(data)
226 self.finish(data)
225
227
226 @web.authenticated
228 @web.authenticated
227 @json_errors
229 @json_errors
228 def post(self, path='', name=None):
230 def post(self, path='', name=None):
229 """post creates a new checkpoint"""
231 """post creates a new checkpoint"""
230 nbm = self.notebook_manager
232 nbm = self.notebook_manager
231 checkpoint = nbm.create_checkpoint(name, path)
233 checkpoint = nbm.create_checkpoint(name, path)
232 data = json.dumps(checkpoint, default=date_default)
234 data = json.dumps(checkpoint, default=date_default)
233 location = url_path_join(self.base_project_url, 'api/notebooks',
235 location = url_path_join(self.base_project_url, 'api/notebooks',
234 path, name, 'checkpoints', checkpoint['id'])
236 path, name, 'checkpoints', checkpoint['id'])
235 self.set_header('Location', url_escape(location))
237 self.set_header('Location', url_escape(location))
236 self.set_status(201)
238 self.set_status(201)
237 self.finish(data)
239 self.finish(data)
238
240
239
241
240 class ModifyNotebookCheckpointsHandler(IPythonHandler):
242 class ModifyNotebookCheckpointsHandler(IPythonHandler):
241
243
242 SUPPORTED_METHODS = ('POST', 'DELETE')
244 SUPPORTED_METHODS = ('POST', 'DELETE')
243
245
244 @web.authenticated
246 @web.authenticated
245 @json_errors
247 @json_errors
246 def post(self, path, name, checkpoint_id):
248 def post(self, path, name, checkpoint_id):
247 """post restores a notebook from a checkpoint"""
249 """post restores a notebook from a checkpoint"""
248 nbm = self.notebook_manager
250 nbm = self.notebook_manager
249 nbm.restore_checkpoint(checkpoint_id, name, path)
251 nbm.restore_checkpoint(checkpoint_id, name, path)
250 self.set_status(204)
252 self.set_status(204)
251 self.finish()
253 self.finish()
252
254
253 @web.authenticated
255 @web.authenticated
254 @json_errors
256 @json_errors
255 def delete(self, path, name, checkpoint_id):
257 def delete(self, path, name, checkpoint_id):
256 """delete clears a checkpoint for a given notebook"""
258 """delete clears a checkpoint for a given notebook"""
257 nbm = self.notebook_manager
259 nbm = self.notebook_manager
258 nbm.delete_checkpoint(checkpoint_id, name, path)
260 nbm.delete_checkpoint(checkpoint_id, name, path)
259 self.set_status(204)
261 self.set_status(204)
260 self.finish()
262 self.finish()
261
263
262 #-----------------------------------------------------------------------------
264 #-----------------------------------------------------------------------------
263 # URL to handler mappings
265 # URL to handler mappings
264 #-----------------------------------------------------------------------------
266 #-----------------------------------------------------------------------------
265
267
266
268
267 _path_regex = r"(?P<path>(?:/.*)*)"
268 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
269 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
269 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
270 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
271
270
272 default_handlers = [
271 default_handlers = [
273 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
272 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
274 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
273 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
275 ModifyNotebookCheckpointsHandler),
274 ModifyNotebookCheckpointsHandler),
276 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
275 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
277 (r"/api/notebooks%s" % _path_regex, NotebookHandler),
276 (r"/api/notebooks%s" % path_regex, NotebookHandler),
278 ]
277 ]
279
278
280
279
281
280
@@ -1,90 +1,90 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CellToolbar Example
9 // CellToolbar Example
10 //============================================================================
10 //============================================================================
11
11
12 (function(IPython) {
12 (function(IPython) {
13 "use strict";
13 "use strict";
14
14
15 var CellToolbar = IPython.CellToolbar;
15 var CellToolbar = IPython.CellToolbar;
16 var raw_cell_preset = [];
16 var raw_cell_preset = [];
17 var utils = IPython.utils;
17 var utils = IPython.utils;
18
18
19 var select_type = CellToolbar.utils.select_ui_generator([
19 var select_type = CellToolbar.utils.select_ui_generator([
20 ["None", "-"],
20 ["None", "-"],
21 ["LaTeX", "text/latex"],
21 ["LaTeX", "text/latex"],
22 ["reST", "text/restructuredtext"],
22 ["reST", "text/restructuredtext"],
23 ["HTML", "text/html"],
23 ["HTML", "text/html"],
24 ["Markdown", "text/markdown"],
24 ["Markdown", "text/markdown"],
25 ["Python", "application/x-python"],
25 ["Python", "text/x-python"],
26 ["Custom", "dialog"],
26 ["Custom", "dialog"],
27
27
28 ],
28 ],
29 // setter
29 // setter
30 function(cell, value) {
30 function(cell, value) {
31 if (value === "-") {
31 if (value === "-") {
32 delete cell.metadata.raw_mimetype;
32 delete cell.metadata.raw_mimetype;
33 } else if (value === 'dialog'){
33 } else if (value === 'dialog'){
34 var dialog = $('<div/>').append(
34 var dialog = $('<div/>').append(
35 $("<p/>")
35 $("<p/>")
36 .html("Set the MIME type of the raw cell:")
36 .html("Set the MIME type of the raw cell:")
37 ).append(
37 ).append(
38 $("<br/>")
38 $("<br/>")
39 ).append(
39 ).append(
40 $('<input/>').attr('type','text').attr('size','25')
40 $('<input/>').attr('type','text').attr('size','25')
41 .val(cell.metadata.raw_mimetype || "-")
41 .val(cell.metadata.raw_mimetype || "-")
42 );
42 );
43 IPython.dialog.modal({
43 IPython.dialog.modal({
44 title: "Raw Cell MIME Type",
44 title: "Raw Cell MIME Type",
45 body: dialog,
45 body: dialog,
46 buttons : {
46 buttons : {
47 "Cancel": {},
47 "Cancel": {},
48 "OK": {
48 "OK": {
49 class: "btn-primary",
49 class: "btn-primary",
50 click: function () {
50 click: function () {
51 console.log(cell);
51 console.log(cell);
52 cell.metadata.raw_mimetype = $(this).find('input').val();
52 cell.metadata.raw_mimetype = $(this).find('input').val();
53 console.log(cell.metadata);
53 console.log(cell.metadata);
54 }
54 }
55 }
55 }
56 },
56 },
57 open : function (event, ui) {
57 open : function (event, ui) {
58 var that = $(this);
58 var that = $(this);
59 // Upon ENTER, click the OK button.
59 // Upon ENTER, click the OK button.
60 that.find('input[type="text"]').keydown(function (event, ui) {
60 that.find('input[type="text"]').keydown(function (event, ui) {
61 if (event.which === utils.keycodes.ENTER) {
61 if (event.which === utils.keycodes.ENTER) {
62 that.find('.btn-primary').first().click();
62 that.find('.btn-primary').first().click();
63 return false;
63 return false;
64 }
64 }
65 });
65 });
66 that.find('input[type="text"]').focus().select();
66 that.find('input[type="text"]').focus().select();
67 }
67 }
68 });
68 });
69 } else {
69 } else {
70 cell.metadata.raw_mimetype = value;
70 cell.metadata.raw_mimetype = value;
71 }
71 }
72 },
72 },
73 //getter
73 //getter
74 function(cell) {
74 function(cell) {
75 return cell.metadata.raw_mimetype || "";
75 return cell.metadata.raw_mimetype || "";
76 },
76 },
77 // name
77 // name
78 "Raw NBConvert Format",
78 "Raw NBConvert Format",
79 // cell_types
79 // cell_types
80 ["raw"]
80 ["raw"]
81 );
81 );
82
82
83 CellToolbar.register_callback('raw_cell.select', select_type);
83 CellToolbar.register_callback('raw_cell.select', select_type);
84
84
85 raw_cell_preset.push('raw_cell.select');
85 raw_cell_preset.push('raw_cell.select');
86
86
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
88 console.log('Raw Cell Format toolbar preset loaded.');
88 console.log('Raw Cell Format toolbar preset loaded.');
89
89
90 }(IPython)); No newline at end of file
90 }(IPython));
@@ -1,309 +1,322 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 var utils = IPython.utils;
22 var utils = IPython.utils;
23
23
24 /**
24 /**
25 * A MenuBar Class to generate the menubar of IPython notebook
25 * A MenuBar Class to generate the menubar of IPython notebook
26 * @Class MenuBar
26 * @Class MenuBar
27 *
27 *
28 * @constructor
28 * @constructor
29 *
29 *
30 *
30 *
31 * @param selector {string} selector for the menubar element in DOM
31 * @param selector {string} selector for the menubar element in DOM
32 * @param {object} [options]
32 * @param {object} [options]
33 * @param [options.baseProjectUrl] {String} String to use for the
33 * @param [options.baseProjectUrl] {String} String to use for the
34 * Base Project url, default would be to inspect
34 * Base Project url, default would be to inspect
35 * $('body').data('baseProjectUrl');
35 * $('body').data('baseProjectUrl');
36 * does not support change for now is set through this option
36 * does not support change for now is set through this option
37 */
37 */
38 var MenuBar = function (selector, options) {
38 var MenuBar = function (selector, options) {
39 options = options || {};
39 options = options || {};
40 if (options.baseProjectUrl !== undefined) {
40 if (options.baseProjectUrl !== undefined) {
41 this._baseProjectUrl = options.baseProjectUrl;
41 this._baseProjectUrl = options.baseProjectUrl;
42 }
42 }
43 this.selector = selector;
43 this.selector = selector;
44 if (this.selector !== undefined) {
44 if (this.selector !== undefined) {
45 this.element = $(selector);
45 this.element = $(selector);
46 this.style();
46 this.style();
47 this.bind_events();
47 this.bind_events();
48 }
48 }
49 };
49 };
50
50
51 MenuBar.prototype.baseProjectUrl = function(){
51 MenuBar.prototype.baseProjectUrl = function(){
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
53 };
53 };
54
54
55 MenuBar.prototype.notebookPath = function() {
55 MenuBar.prototype.notebookPath = function() {
56 var path = $('body').data('notebookPath');
56 var path = $('body').data('notebookPath');
57 path = decodeURIComponent(path);
57 path = decodeURIComponent(path);
58 return path;
58 return path;
59 };
59 };
60
60
61 MenuBar.prototype.style = function () {
61 MenuBar.prototype.style = function () {
62 this.element.addClass('border-box-sizing');
62 this.element.addClass('border-box-sizing');
63 this.element.find("li").click(function (event, ui) {
63 this.element.find("li").click(function (event, ui) {
64 // The selected cell loses focus when the menu is entered, so we
64 // The selected cell loses focus when the menu is entered, so we
65 // re-select it upon selection.
65 // re-select it upon selection.
66 var i = IPython.notebook.get_selected_index();
66 var i = IPython.notebook.get_selected_index();
67 IPython.notebook.select(i);
67 IPython.notebook.select(i);
68 }
68 }
69 );
69 );
70 };
70 };
71
71
72 MenuBar.prototype._nbconvert = function (format, download) {
73 download = download || false;
74 var notebook_name = IPython.notebook.get_notebook_name();
75 if (IPython.notebook.dirty) {
76 IPython.notebook.save_notebook({async : false});
77 }
78 var url = utils.url_path_join(
79 this.baseProjectUrl(),
80 'nbconvert',
81 format,
82 this.notebookPath(),
83 notebook_name + '.ipynb'
84 ) + "?download=" + download.toString();
85
86 window.open(url);
87 }
72
88
73 MenuBar.prototype.bind_events = function () {
89 MenuBar.prototype.bind_events = function () {
74 // File
90 // File
75 var that = this;
91 var that = this;
76 this.element.find('#new_notebook').click(function () {
92 this.element.find('#new_notebook').click(function () {
77 IPython.notebook.new_notebook();
93 IPython.notebook.new_notebook();
78 });
94 });
79 this.element.find('#open_notebook').click(function () {
95 this.element.find('#open_notebook').click(function () {
80 window.open(utils.url_join_encode(
96 window.open(utils.url_join_encode(
81 that.baseProjectUrl(),
97 that.baseProjectUrl(),
82 'tree',
98 'tree',
83 that.notebookPath()
99 that.notebookPath()
84 ));
100 ));
85 });
101 });
86 this.element.find('#copy_notebook').click(function () {
102 this.element.find('#copy_notebook').click(function () {
87 IPython.notebook.copy_notebook();
103 IPython.notebook.copy_notebook();
88 return false;
104 return false;
89 });
105 });
90 this.element.find('#download_ipynb').click(function () {
106 this.element.find('#download_ipynb').click(function () {
91 var notebook_name = IPython.notebook.get_notebook_name();
107 var notebook_name = IPython.notebook.get_notebook_name();
92 if (IPython.notebook.dirty) {
108 if (IPython.notebook.dirty) {
93 IPython.notebook.save_notebook({async : false});
109 IPython.notebook.save_notebook({async : false});
94 }
110 }
95
111
96 var url = utils.url_join_encode(
112 var url = utils.url_join_encode(
97 that.baseProjectUrl(),
113 that.baseProjectUrl(),
98 'files',
114 'files',
99 that.notebookPath(),
115 that.notebookPath(),
100 notebook_name + '.ipynb'
116 notebook_name + '.ipynb'
101 );
117 );
102 window.location.assign(url);
118 window.location.assign(url);
103 });
119 });
104
120
105 /* FIXME: download-as-py doesn't work right now
121 this.element.find('#print_preview').click(function () {
106 * We will need nbconvert hooked up to get this back
122 that._nbconvert('html', false);
107
123 });
124
108 this.element.find('#download_py').click(function () {
125 this.element.find('#download_py').click(function () {
109 var notebook_name = IPython.notebook.get_notebook_name();
126 that._nbconvert('python', true);
110 if (IPython.notebook.dirty) {
111 IPython.notebook.save_notebook({async : false});
112 }
113 var url = utils.url_path_join(
114 that.baseProjectUrl(),
115 'api/notebooks',
116 that.notebookPath(),
117 notebook_name + '.ipynb?format=py&download=True'
118 );
119 window.location.assign(url);
120 });
127 });
121
128
122 */
129 this.element.find('#download_html').click(function () {
123
130 that._nbconvert('html', true);
131 });
132
133 this.element.find('#download_rst').click(function () {
134 that._nbconvert('rst', true);
135 });
136
124 this.element.find('#rename_notebook').click(function () {
137 this.element.find('#rename_notebook').click(function () {
125 IPython.save_widget.rename_notebook();
138 IPython.save_widget.rename_notebook();
126 });
139 });
127 this.element.find('#save_checkpoint').click(function () {
140 this.element.find('#save_checkpoint').click(function () {
128 IPython.notebook.save_checkpoint();
141 IPython.notebook.save_checkpoint();
129 });
142 });
130 this.element.find('#restore_checkpoint').click(function () {
143 this.element.find('#restore_checkpoint').click(function () {
131 });
144 });
132 this.element.find('#kill_and_exit').click(function () {
145 this.element.find('#kill_and_exit').click(function () {
133 IPython.notebook.session.delete();
146 IPython.notebook.session.delete();
134 setTimeout(function(){
147 setTimeout(function(){
135 // allow closing of new tabs in Chromium, impossible in FF
148 // allow closing of new tabs in Chromium, impossible in FF
136 window.open('', '_self', '');
149 window.open('', '_self', '');
137 window.close();
150 window.close();
138 }, 500);
151 }, 500);
139 });
152 });
140 // Edit
153 // Edit
141 this.element.find('#cut_cell').click(function () {
154 this.element.find('#cut_cell').click(function () {
142 IPython.notebook.cut_cell();
155 IPython.notebook.cut_cell();
143 });
156 });
144 this.element.find('#copy_cell').click(function () {
157 this.element.find('#copy_cell').click(function () {
145 IPython.notebook.copy_cell();
158 IPython.notebook.copy_cell();
146 });
159 });
147 this.element.find('#delete_cell').click(function () {
160 this.element.find('#delete_cell').click(function () {
148 IPython.notebook.delete_cell();
161 IPython.notebook.delete_cell();
149 });
162 });
150 this.element.find('#undelete_cell').click(function () {
163 this.element.find('#undelete_cell').click(function () {
151 IPython.notebook.undelete();
164 IPython.notebook.undelete();
152 });
165 });
153 this.element.find('#split_cell').click(function () {
166 this.element.find('#split_cell').click(function () {
154 IPython.notebook.split_cell();
167 IPython.notebook.split_cell();
155 });
168 });
156 this.element.find('#merge_cell_above').click(function () {
169 this.element.find('#merge_cell_above').click(function () {
157 IPython.notebook.merge_cell_above();
170 IPython.notebook.merge_cell_above();
158 });
171 });
159 this.element.find('#merge_cell_below').click(function () {
172 this.element.find('#merge_cell_below').click(function () {
160 IPython.notebook.merge_cell_below();
173 IPython.notebook.merge_cell_below();
161 });
174 });
162 this.element.find('#move_cell_up').click(function () {
175 this.element.find('#move_cell_up').click(function () {
163 IPython.notebook.move_cell_up();
176 IPython.notebook.move_cell_up();
164 });
177 });
165 this.element.find('#move_cell_down').click(function () {
178 this.element.find('#move_cell_down').click(function () {
166 IPython.notebook.move_cell_down();
179 IPython.notebook.move_cell_down();
167 });
180 });
168 this.element.find('#select_previous').click(function () {
181 this.element.find('#select_previous').click(function () {
169 IPython.notebook.select_prev();
182 IPython.notebook.select_prev();
170 });
183 });
171 this.element.find('#select_next').click(function () {
184 this.element.find('#select_next').click(function () {
172 IPython.notebook.select_next();
185 IPython.notebook.select_next();
173 });
186 });
174 this.element.find('#edit_nb_metadata').click(function () {
187 this.element.find('#edit_nb_metadata').click(function () {
175 IPython.notebook.edit_metadata();
188 IPython.notebook.edit_metadata();
176 });
189 });
177
190
178 // View
191 // View
179 this.element.find('#toggle_header').click(function () {
192 this.element.find('#toggle_header').click(function () {
180 $('div#header').toggle();
193 $('div#header').toggle();
181 IPython.layout_manager.do_resize();
194 IPython.layout_manager.do_resize();
182 });
195 });
183 this.element.find('#toggle_toolbar').click(function () {
196 this.element.find('#toggle_toolbar').click(function () {
184 $('div#maintoolbar').toggle();
197 $('div#maintoolbar').toggle();
185 IPython.layout_manager.do_resize();
198 IPython.layout_manager.do_resize();
186 });
199 });
187 // Insert
200 // Insert
188 this.element.find('#insert_cell_above').click(function () {
201 this.element.find('#insert_cell_above').click(function () {
189 IPython.notebook.insert_cell_above('code');
202 IPython.notebook.insert_cell_above('code');
190 });
203 });
191 this.element.find('#insert_cell_below').click(function () {
204 this.element.find('#insert_cell_below').click(function () {
192 IPython.notebook.insert_cell_below('code');
205 IPython.notebook.insert_cell_below('code');
193 });
206 });
194 // Cell
207 // Cell
195 this.element.find('#run_cell').click(function () {
208 this.element.find('#run_cell').click(function () {
196 IPython.notebook.execute_selected_cell();
209 IPython.notebook.execute_selected_cell();
197 });
210 });
198 this.element.find('#run_cell_in_place').click(function () {
211 this.element.find('#run_cell_in_place').click(function () {
199 IPython.notebook.execute_selected_cell({terminal:true});
212 IPython.notebook.execute_selected_cell({terminal:true});
200 });
213 });
201 this.element.find('#run_all_cells').click(function () {
214 this.element.find('#run_all_cells').click(function () {
202 IPython.notebook.execute_all_cells();
215 IPython.notebook.execute_all_cells();
203 });
216 });
204 this.element.find('#run_all_cells_above').click(function () {
217 this.element.find('#run_all_cells_above').click(function () {
205 IPython.notebook.execute_cells_above();
218 IPython.notebook.execute_cells_above();
206 });
219 });
207 this.element.find('#run_all_cells_below').click(function () {
220 this.element.find('#run_all_cells_below').click(function () {
208 IPython.notebook.execute_cells_below();
221 IPython.notebook.execute_cells_below();
209 });
222 });
210 this.element.find('#to_code').click(function () {
223 this.element.find('#to_code').click(function () {
211 IPython.notebook.to_code();
224 IPython.notebook.to_code();
212 });
225 });
213 this.element.find('#to_markdown').click(function () {
226 this.element.find('#to_markdown').click(function () {
214 IPython.notebook.to_markdown();
227 IPython.notebook.to_markdown();
215 });
228 });
216 this.element.find('#to_raw').click(function () {
229 this.element.find('#to_raw').click(function () {
217 IPython.notebook.to_raw();
230 IPython.notebook.to_raw();
218 });
231 });
219 this.element.find('#to_heading1').click(function () {
232 this.element.find('#to_heading1').click(function () {
220 IPython.notebook.to_heading(undefined, 1);
233 IPython.notebook.to_heading(undefined, 1);
221 });
234 });
222 this.element.find('#to_heading2').click(function () {
235 this.element.find('#to_heading2').click(function () {
223 IPython.notebook.to_heading(undefined, 2);
236 IPython.notebook.to_heading(undefined, 2);
224 });
237 });
225 this.element.find('#to_heading3').click(function () {
238 this.element.find('#to_heading3').click(function () {
226 IPython.notebook.to_heading(undefined, 3);
239 IPython.notebook.to_heading(undefined, 3);
227 });
240 });
228 this.element.find('#to_heading4').click(function () {
241 this.element.find('#to_heading4').click(function () {
229 IPython.notebook.to_heading(undefined, 4);
242 IPython.notebook.to_heading(undefined, 4);
230 });
243 });
231 this.element.find('#to_heading5').click(function () {
244 this.element.find('#to_heading5').click(function () {
232 IPython.notebook.to_heading(undefined, 5);
245 IPython.notebook.to_heading(undefined, 5);
233 });
246 });
234 this.element.find('#to_heading6').click(function () {
247 this.element.find('#to_heading6').click(function () {
235 IPython.notebook.to_heading(undefined, 6);
248 IPython.notebook.to_heading(undefined, 6);
236 });
249 });
237 this.element.find('#toggle_output').click(function () {
250 this.element.find('#toggle_output').click(function () {
238 IPython.notebook.toggle_output();
251 IPython.notebook.toggle_output();
239 });
252 });
240 this.element.find('#collapse_all_output').click(function () {
253 this.element.find('#collapse_all_output').click(function () {
241 IPython.notebook.collapse_all_output();
254 IPython.notebook.collapse_all_output();
242 });
255 });
243 this.element.find('#scroll_all_output').click(function () {
256 this.element.find('#scroll_all_output').click(function () {
244 IPython.notebook.scroll_all_output();
257 IPython.notebook.scroll_all_output();
245 });
258 });
246 this.element.find('#expand_all_output').click(function () {
259 this.element.find('#expand_all_output').click(function () {
247 IPython.notebook.expand_all_output();
260 IPython.notebook.expand_all_output();
248 });
261 });
249 this.element.find('#clear_all_output').click(function () {
262 this.element.find('#clear_all_output').click(function () {
250 IPython.notebook.clear_all_output();
263 IPython.notebook.clear_all_output();
251 });
264 });
252 // Kernel
265 // Kernel
253 this.element.find('#int_kernel').click(function () {
266 this.element.find('#int_kernel').click(function () {
254 IPython.notebook.session.interrupt_kernel();
267 IPython.notebook.session.interrupt_kernel();
255 });
268 });
256 this.element.find('#restart_kernel').click(function () {
269 this.element.find('#restart_kernel').click(function () {
257 IPython.notebook.restart_kernel();
270 IPython.notebook.restart_kernel();
258 });
271 });
259 // Help
272 // Help
260 this.element.find('#keyboard_shortcuts').click(function () {
273 this.element.find('#keyboard_shortcuts').click(function () {
261 IPython.quick_help.show_keyboard_shortcuts();
274 IPython.quick_help.show_keyboard_shortcuts();
262 });
275 });
263
276
264 this.update_restore_checkpoint(null);
277 this.update_restore_checkpoint(null);
265
278
266 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
279 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
267 that.update_restore_checkpoint(IPython.notebook.checkpoints);
280 that.update_restore_checkpoint(IPython.notebook.checkpoints);
268 });
281 });
269
282
270 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
283 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
271 that.update_restore_checkpoint(IPython.notebook.checkpoints);
284 that.update_restore_checkpoint(IPython.notebook.checkpoints);
272 });
285 });
273 };
286 };
274
287
275 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
288 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
276 var ul = this.element.find("#restore_checkpoint").find("ul");
289 var ul = this.element.find("#restore_checkpoint").find("ul");
277 ul.empty();
290 ul.empty();
278 if (!checkpoints || checkpoints.length === 0) {
291 if (!checkpoints || checkpoints.length === 0) {
279 ul.append(
292 ul.append(
280 $("<li/>")
293 $("<li/>")
281 .addClass("disabled")
294 .addClass("disabled")
282 .append(
295 .append(
283 $("<a/>")
296 $("<a/>")
284 .text("No checkpoints")
297 .text("No checkpoints")
285 )
298 )
286 );
299 );
287 return;
300 return;
288 }
301 }
289
302
290 checkpoints.map(function (checkpoint) {
303 checkpoints.map(function (checkpoint) {
291 var d = new Date(checkpoint.last_modified);
304 var d = new Date(checkpoint.last_modified);
292 ul.append(
305 ul.append(
293 $("<li/>").append(
306 $("<li/>").append(
294 $("<a/>")
307 $("<a/>")
295 .attr("href", "#")
308 .attr("href", "#")
296 .text(d.format("mmm dd HH:MM:ss"))
309 .text(d.format("mmm dd HH:MM:ss"))
297 .click(function () {
310 .click(function () {
298 IPython.notebook.restore_checkpoint_dialog(checkpoint);
311 IPython.notebook.restore_checkpoint_dialog(checkpoint);
299 })
312 })
300 )
313 )
301 );
314 );
302 });
315 });
303 };
316 };
304
317
305 IPython.MenuBar = MenuBar;
318 IPython.MenuBar = MenuBar;
306
319
307 return IPython;
320 return IPython;
308
321
309 }(IPython));
322 }(IPython));
@@ -1,303 +1,306 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project="{{project}}"
24 data-project="{{project}}"
25 data-base-project-url="{{base_project_url}}"
25 data-base-project-url="{{base_project_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
27 data-notebook-name="{{notebook_name}}"
27 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
28 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
29 class="notebook_app"
30
30
31 {% endblock %}
31 {% endblock %}
32
32
33
33
34 {% block header %}
34 {% block header %}
35
35
36 <span id="save_widget" class="nav pull-left">
36 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
37 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
38 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
39 <span id="autosave_status"></span>
40 </span>
40 </span>
41
41
42 {% endblock %}
42 {% endblock %}
43
43
44
44
45 {% block site %}
45 {% block site %}
46
46
47 <div id="menubar-container" class="container">
47 <div id="menubar-container" class="container">
48 <div id="menubar">
48 <div id="menubar">
49 <div class="navbar">
49 <div class="navbar">
50 <div class="navbar-inner">
50 <div class="navbar-inner">
51 <div class="container">
51 <div class="container">
52 <ul id="menus" class="nav">
52 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul class="dropdown-menu">
54 <ul class="dropdown-menu">
55 <li id="new_notebook"
55 <li id="new_notebook"
56 title="Make a new notebook (Opens a new window)">
56 title="Make a new notebook (Opens a new window)">
57 <a href="#">New</a></li>
57 <a href="#">New</a></li>
58 <li id="open_notebook"
58 <li id="open_notebook"
59 title="Opens a new window with the Dashboard view">
59 title="Opens a new window with the Dashboard view">
60 <a href="#">Open...</a></li>
60 <a href="#">Open...</a></li>
61 <!-- <hr/> -->
61 <!-- <hr/> -->
62 <li class="divider"></li>
62 <li class="divider"></li>
63 <li id="copy_notebook"
63 <li id="copy_notebook"
64 title="Open a copy of this notebook's contents and start a new kernel">
64 title="Open a copy of this notebook's contents and start a new kernel">
65 <a href="#">Make a Copy...</a></li>
65 <a href="#">Make a Copy...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 <!-- <hr/> -->
68 <!-- <hr/> -->
69 <li class="divider"></li>
69 <li class="divider"></li>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 <ul class="dropdown-menu">
71 <ul class="dropdown-menu">
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <li class="divider"></li>
79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <!-- <li id="download_py"><a href="#">Python (.py)</a></li> -->
84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
84 </ul>
87 </ul>
85 </li>
88 </li>
86 <li class="divider"></li>
89 <li class="divider"></li>
87
90
88 <li id="kill_and_exit"
91 <li id="kill_and_exit"
89 title="Shutdown this notebook's kernel, and close this window">
92 title="Shutdown this notebook's kernel, and close this window">
90 <a href="#" >Close and halt</a></li>
93 <a href="#" >Close and halt</a></li>
91 </ul>
94 </ul>
92 </li>
95 </li>
93 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
94 <ul class="dropdown-menu">
97 <ul class="dropdown-menu">
95 <li id="cut_cell"><a href="#">Cut Cell</a></li>
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
96 <li id="copy_cell"><a href="#">Copy Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
97 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
98 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
99 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
100 <li id="delete_cell"><a href="#">Delete Cell</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
101 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
102 <li class="divider"></li>
105 <li class="divider"></li>
103 <li id="split_cell"><a href="#">Split Cell</a></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
104 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
105 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
106 <li class="divider"></li>
109 <li class="divider"></li>
107 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
108 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
109 <li class="divider"></li>
112 <li class="divider"></li>
110 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
113 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
111 <li id="select_next"><a href="#">Select Next Cell</a></li>
114 <li id="select_next"><a href="#">Select Next Cell</a></li>
112 <li class="divider"></li>
115 <li class="divider"></li>
113 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
114 </ul>
117 </ul>
115 </li>
118 </li>
116 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
117 <ul class="dropdown-menu">
120 <ul class="dropdown-menu">
118 <li id="toggle_header"
121 <li id="toggle_header"
119 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
120 <a href="#">Toggle Header</a></li>
123 <a href="#">Toggle Header</a></li>
121 <li id="toggle_toolbar"
124 <li id="toggle_toolbar"
122 title="Show/Hide the action icons (below menu bar)">
125 title="Show/Hide the action icons (below menu bar)">
123 <a href="#">Toggle Toolbar</a></li>
126 <a href="#">Toggle Toolbar</a></li>
124 </ul>
127 </ul>
125 </li>
128 </li>
126 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
127 <ul class="dropdown-menu">
130 <ul class="dropdown-menu">
128 <li id="insert_cell_above"
131 <li id="insert_cell_above"
129 title="Insert an empty Code cell above the currently active cell">
132 title="Insert an empty Code cell above the currently active cell">
130 <a href="#">Insert Cell Above</a></li>
133 <a href="#">Insert Cell Above</a></li>
131 <li id="insert_cell_below"
134 <li id="insert_cell_below"
132 title="Insert an empty Code cell below the currently active cell">
135 title="Insert an empty Code cell below the currently active cell">
133 <a href="#">Insert Cell Below</a></li>
136 <a href="#">Insert Cell Below</a></li>
134 </ul>
137 </ul>
135 </li>
138 </li>
136 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
137 <ul class="dropdown-menu">
140 <ul class="dropdown-menu">
138 <li id="run_cell" title="Run this cell, and move cursor to the next one">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
139 <a href="#">Run</a></li>
142 <a href="#">Run</a></li>
140 <li id="run_cell_in_place" title="Run this cell, without moving to the next one">
143 <li id="run_cell_in_place" title="Run this cell, without moving to the next one">
141 <a href="#">Run in Place</a></li>
144 <a href="#">Run in Place</a></li>
142 <li id="run_all_cells" title="Run all cells in the notebook">
145 <li id="run_all_cells" title="Run all cells in the notebook">
143 <a href="#">Run All</a></li>
146 <a href="#">Run All</a></li>
144 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
147 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
145 <a href="#">Run All Above</a></li>
148 <a href="#">Run All Above</a></li>
146 <li id="run_all_cells_below" title="Run this cell and all cells below it">
149 <li id="run_all_cells_below" title="Run this cell and all cells below it">
147 <a href="#">Run All Below</a></li>
150 <a href="#">Run All Below</a></li>
148 <li class="divider"></li>
151 <li class="divider"></li>
149 <li id="change_cell_type" class="dropdown-submenu"
152 <li id="change_cell_type" class="dropdown-submenu"
150 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
153 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
151 <a href="#">Cell Type</a>
154 <a href="#">Cell Type</a>
152 <ul class="dropdown-menu">
155 <ul class="dropdown-menu">
153 <li id="to_code"
156 <li id="to_code"
154 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
157 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
155 <a href="#">Code</a></li>
158 <a href="#">Code</a></li>
156 <li id="to_markdown"
159 <li id="to_markdown"
157 title="Contents will be rendered as HTML and serve as explanatory text">
160 title="Contents will be rendered as HTML and serve as explanatory text">
158 <a href="#">Markdown</a></li>
161 <a href="#">Markdown</a></li>
159 <li id="to_raw"
162 <li id="to_raw"
160 title="Contents will pass through nbconvert unmodified">
163 title="Contents will pass through nbconvert unmodified">
161 <a href="#">Raw NBConvert</a></li>
164 <a href="#">Raw NBConvert</a></li>
162 <li id="to_heading1"><a href="#">Heading 1</a></li>
165 <li id="to_heading1"><a href="#">Heading 1</a></li>
163 <li id="to_heading2"><a href="#">Heading 2</a></li>
166 <li id="to_heading2"><a href="#">Heading 2</a></li>
164 <li id="to_heading3"><a href="#">Heading 3</a></li>
167 <li id="to_heading3"><a href="#">Heading 3</a></li>
165 <li id="to_heading4"><a href="#">Heading 4</a></li>
168 <li id="to_heading4"><a href="#">Heading 4</a></li>
166 <li id="to_heading5"><a href="#">Heading 5</a></li>
169 <li id="to_heading5"><a href="#">Heading 5</a></li>
167 <li id="to_heading6"><a href="#">Heading 6</a></li>
170 <li id="to_heading6"><a href="#">Heading 6</a></li>
168 </ul>
171 </ul>
169 </li>
172 </li>
170 <li class="divider"></li>
173 <li class="divider"></li>
171 <li id="toggle_output"
174 <li id="toggle_output"
172 title="Show/Hide the output portion of a Code cell">
175 title="Show/Hide the output portion of a Code cell">
173 <a href="#">Toggle Current Output</a></li>
176 <a href="#">Toggle Current Output</a></li>
174 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
177 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
175 <ul class="dropdown-menu">
178 <ul class="dropdown-menu">
176 <li id="expand_all_output"><a href="#">Expand</a></li>
179 <li id="expand_all_output"><a href="#">Expand</a></li>
177 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
180 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
178 <li id="collapse_all_output"><a href="#">Collapse</a></li>
181 <li id="collapse_all_output"><a href="#">Collapse</a></li>
179 <li id="clear_all_output"
182 <li id="clear_all_output"
180 title="Remove the output portion of all Code cells">
183 title="Remove the output portion of all Code cells">
181 <a href="#">Clear</a></li>
184 <a href="#">Clear</a></li>
182 </ul>
185 </ul>
183 </li>
186 </li>
184 </ul>
187 </ul>
185 </li>
188 </li>
186 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
189 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
187 <ul class="dropdown-menu">
190 <ul class="dropdown-menu">
188 <li id="int_kernel"
191 <li id="int_kernel"
189 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
192 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
190 <a href="#">Interrupt</a></li>
193 <a href="#">Interrupt</a></li>
191 <li id="restart_kernel"
194 <li id="restart_kernel"
192 title="Restart the Kernel">
195 title="Restart the Kernel">
193 <a href="#">Restart</a></li>
196 <a href="#">Restart</a></li>
194 </ul>
197 </ul>
195 </li>
198 </li>
196 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
199 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
197 <ul class="dropdown-menu" title="Opens in a new window">
200 <ul class="dropdown-menu" title="Opens in a new window">
198 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
201 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
199 <li><a href="http://ipython.org/ipython-doc/stable/interactive/notebook.html" target="_blank">Notebook Help</a></li>
202 <li><a href="http://ipython.org/ipython-doc/stable/interactive/notebook.html" target="_blank">Notebook Help</a></li>
200 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
203 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
201 <li><a href="http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html" target="_blank">Editor Shortcuts</a></li>
204 <li><a href="http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html" target="_blank">Editor Shortcuts</a></li>
202 <li class="divider"></li>
205 <li class="divider"></li>
203 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
206 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
204 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
207 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
205 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
208 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
206 <li><a href="http://matplotlib.org/" target="_blank">Matplotlib</a></li>
209 <li><a href="http://matplotlib.org/" target="_blank">Matplotlib</a></li>
207 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
210 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
208 <li><a href="http://pandas.pydata.org/pandas-docs/stable/" target="_blank">pandas</a></li>
211 <li><a href="http://pandas.pydata.org/pandas-docs/stable/" target="_blank">pandas</a></li>
209 </ul>
212 </ul>
210 </li>
213 </li>
211 </ul>
214 </ul>
212 <div id="notification_area"></div>
215 <div id="notification_area"></div>
213 </div>
216 </div>
214 </div>
217 </div>
215 </div>
218 </div>
216 </div>
219 </div>
217 <div id="maintoolbar" class="navbar">
220 <div id="maintoolbar" class="navbar">
218 <div class="toolbar-inner navbar-inner navbar-nobg">
221 <div class="toolbar-inner navbar-inner navbar-nobg">
219 <div id="maintoolbar-container" class="container"></div>
222 <div id="maintoolbar-container" class="container"></div>
220 </div>
223 </div>
221 </div>
224 </div>
222 </div>
225 </div>
223
226
224 <div id="ipython-main-app">
227 <div id="ipython-main-app">
225
228
226 <div id="notebook_panel">
229 <div id="notebook_panel">
227 <div id="notebook"></div>
230 <div id="notebook"></div>
228 <div id="pager_splitter"></div>
231 <div id="pager_splitter"></div>
229 <div id="pager">
232 <div id="pager">
230 <div id='pager_button_area'>
233 <div id='pager_button_area'>
231 </div>
234 </div>
232 <div id="pager-container" class="container"></div>
235 <div id="pager-container" class="container"></div>
233 </div>
236 </div>
234 </div>
237 </div>
235
238
236 </div>
239 </div>
237 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
240 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
238
241
239
242
240 {% endblock %}
243 {% endblock %}
241
244
242
245
243 {% block script %}
246 {% block script %}
244
247
245 {{super()}}
248 {{super()}}
246
249
247 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
250 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
248 <script type="text/javascript">
251 <script type="text/javascript">
249 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
252 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
250 </script>
253 </script>
251 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
254 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
252 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
255 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
253 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
256 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
254 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
257 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
255 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
258 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
256 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
259 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
257 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
260 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
258 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
261 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
259 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
262 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
260 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
263 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
261 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
264 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
262 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
265 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
263 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
266 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
264 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
267 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
265
268
266 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
269 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
267
270
268 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
271 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
269
272
270 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
273 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
271 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
274 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
272 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
275 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
273 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
276 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
274 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
277 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
275 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
278 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
276 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
279 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
277 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
280 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
278 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
281 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
279 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
282 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
280 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
283 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
281 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
284 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
282 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
285 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
283 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
286 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
284 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
287 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
285 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
288 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
286 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
289 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
287 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
290 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
288 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
291 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
289 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
292 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
290 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
293 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
291 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
294 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
292 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
295 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
293 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
296 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
294 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
297 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
295 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
298 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
296
299
297 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
298
301
299 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
302 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
300 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
303 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
301 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
304 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
302
305
303 {% endblock %}
306 {% endblock %}
@@ -1,88 +1,88 b''
1 """Base class for notebook tests."""
1 """Base class for notebook tests."""
2
2
3 import os
4 import sys
3 import sys
5 import time
4 import time
6 import requests
5 import requests
7 from contextlib import contextmanager
6 from contextlib import contextmanager
8 from subprocess import Popen, PIPE
7 from subprocess import Popen, STDOUT
9 from unittest import TestCase
8 from unittest import TestCase
10
9
10 import nose
11
11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils.tempdir import TemporaryDirectory
12
13
13 class NotebookTestBase(TestCase):
14 class NotebookTestBase(TestCase):
14 """A base class for tests that need a running notebook.
15 """A base class for tests that need a running notebook.
15
16
16 This creates an empty profile in a temp ipython_dir
17 This creates an empty profile in a temp ipython_dir
17 and then starts the notebook server with a separate temp notebook_dir.
18 and then starts the notebook server with a separate temp notebook_dir.
18 """
19 """
19
20
20 port = 12341
21 port = 12341
21
22
22 @classmethod
23 @classmethod
23 def wait_until_alive(cls):
24 def wait_until_alive(cls):
24 """Wait for the server to be alive"""
25 """Wait for the server to be alive"""
25 url = 'http://localhost:%i/api/notebooks' % cls.port
26 url = 'http://localhost:%i/api/notebooks' % cls.port
26 while True:
27 while True:
27 try:
28 try:
28 requests.get(url)
29 requests.get(url)
29 except requests.exceptions.ConnectionError:
30 except requests.exceptions.ConnectionError:
30 time.sleep(.1)
31 time.sleep(.1)
31 else:
32 else:
32 break
33 break
33
34
34 @classmethod
35 @classmethod
35 def wait_until_dead(cls):
36 def wait_until_dead(cls):
36 """Wait for the server to stop getting requests after shutdown"""
37 """Wait for the server to stop getting requests after shutdown"""
37 url = 'http://localhost:%i/api/notebooks' % cls.port
38 url = 'http://localhost:%i/api/notebooks' % cls.port
38 while True:
39 while True:
39 try:
40 try:
40 requests.get(url)
41 requests.get(url)
41 except requests.exceptions.ConnectionError:
42 except requests.exceptions.ConnectionError:
42 break
43 break
43 else:
44 else:
44 time.sleep(.1)
45 time.sleep(.1)
45
46
46 @classmethod
47 @classmethod
47 def setup_class(cls):
48 def setup_class(cls):
48 cls.ipython_dir = TemporaryDirectory()
49 cls.ipython_dir = TemporaryDirectory()
49 cls.notebook_dir = TemporaryDirectory()
50 cls.notebook_dir = TemporaryDirectory()
50 notebook_args = [
51 notebook_args = [
51 sys.executable, '-c',
52 sys.executable, '-c',
52 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()',
53 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()',
53 '--port=%d' % cls.port,
54 '--port=%d' % cls.port,
54 '--no-browser',
55 '--no-browser',
55 '--ipython-dir=%s' % cls.ipython_dir.name,
56 '--ipython-dir=%s' % cls.ipython_dir.name,
56 '--notebook-dir=%s' % cls.notebook_dir.name,
57 '--notebook-dir=%s' % cls.notebook_dir.name,
57 ]
58 ]
58 devnull = open(os.devnull, 'w')
59 cls.notebook = Popen(notebook_args,
59 cls.notebook = Popen(notebook_args,
60 stdout=devnull,
60 stdout=nose.iptest_stdstreams_fileno(),
61 stderr=devnull,
61 stderr=STDOUT,
62 )
62 )
63 cls.wait_until_alive()
63 cls.wait_until_alive()
64
64
65 @classmethod
65 @classmethod
66 def teardown_class(cls):
66 def teardown_class(cls):
67 cls.notebook.terminate()
67 cls.notebook.terminate()
68 cls.ipython_dir.cleanup()
68 cls.ipython_dir.cleanup()
69 cls.notebook_dir.cleanup()
69 cls.notebook_dir.cleanup()
70 cls.wait_until_dead()
70 cls.wait_until_dead()
71
71
72 @classmethod
72 @classmethod
73 def base_url(cls):
73 def base_url(cls):
74 return 'http://localhost:%i/' % cls.port
74 return 'http://localhost:%i/' % cls.port
75
75
76
76
77 @contextmanager
77 @contextmanager
78 def assert_http_error(status, msg=None):
78 def assert_http_error(status, msg=None):
79 try:
79 try:
80 yield
80 yield
81 except requests.HTTPError as e:
81 except requests.HTTPError as e:
82 real_status = e.response.status_code
82 real_status = e.response.status_code
83 assert real_status == status, \
83 assert real_status == status, \
84 "Expected status %d, got %d" % (real_status, status)
84 "Expected status %d, got %d" % (real_status, status)
85 if msg:
85 if msg:
86 assert msg in str(e), e
86 assert msg in str(e), e
87 else:
87 else:
88 assert False, "Expected HTTP error status" No newline at end of file
88 assert False, "Expected HTTP error status"
@@ -1,77 +1,76 b''
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 import os
18 import os
19
19
20 from tornado import web
20 from tornado import web
21 from ..base.handlers import IPythonHandler
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
22 from ..utils import url_path_join, path2url, url2path, url_escape
22 from ..utils import url_path_join, path2url, url2path, url_escape
23 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
24
23
25 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
26 # Handlers
25 # Handlers
27 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
28
27
29
28
30 class TreeHandler(IPythonHandler):
29 class TreeHandler(IPythonHandler):
31 """Render the tree view, listing notebooks, clusters, etc."""
30 """Render the tree view, listing notebooks, clusters, etc."""
32
31
33 @web.authenticated
32 @web.authenticated
34 def get(self, path='', name=None):
33 def get(self, path='', name=None):
35 path = path.strip('/')
34 path = path.strip('/')
36 nbm = self.notebook_manager
35 nbm = self.notebook_manager
37 if name is not None:
36 if name is not None:
38 # is a notebook, redirect to notebook handler
37 # is a notebook, redirect to notebook handler
39 url = url_escape(url_path_join(
38 url = url_escape(url_path_join(
40 self.base_project_url, 'notebooks', path, name
39 self.base_project_url, 'notebooks', path, name
41 ))
40 ))
42 self.log.debug("Redirecting %s to %s", self.request.path, url)
41 self.log.debug("Redirecting %s to %s", self.request.path, url)
43 self.redirect(url)
42 self.redirect(url)
44 else:
43 else:
45 if not nbm.path_exists(path=path):
44 if not nbm.path_exists(path=path):
46 # no such directory, 404
45 # no such directory, 404
47 raise web.HTTPError(404)
46 raise web.HTTPError(404)
48 self.write(self.render_template('tree.html',
47 self.write(self.render_template('tree.html',
49 project=self.project_dir,
48 project=self.project_dir,
50 tree_url_path=path,
49 tree_url_path=path,
51 notebook_path=path,
50 notebook_path=path,
52 ))
51 ))
53
52
54
53
55 class TreeRedirectHandler(IPythonHandler):
54 class TreeRedirectHandler(IPythonHandler):
56 """Redirect a request to the corresponding tree URL"""
55 """Redirect a request to the corresponding tree URL"""
57
56
58 @web.authenticated
57 @web.authenticated
59 def get(self, path=''):
58 def get(self, path=''):
60 url = url_escape(url_path_join(
59 url = url_escape(url_path_join(
61 self.base_project_url, 'tree', path.strip('/')
60 self.base_project_url, 'tree', path.strip('/')
62 ))
61 ))
63 self.log.debug("Redirecting %s to %s", self.request.path, url)
62 self.log.debug("Redirecting %s to %s", self.request.path, url)
64 self.redirect(url)
63 self.redirect(url)
65
64
66
65
67 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
68 # URL to handler mappings
67 # URL to handler mappings
69 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
70
69
71
70
72 default_handlers = [
71 default_handlers = [
73 (r"/tree%s" % _notebook_path_regex, TreeHandler),
72 (r"/tree%s" % notebook_path_regex, TreeHandler),
74 (r"/tree%s" % _path_regex, TreeHandler),
73 (r"/tree%s" % path_regex, TreeHandler),
75 (r"/tree", TreeHandler),
74 (r"/tree", TreeHandler),
76 (r"/", TreeRedirectHandler),
75 (r"/", TreeRedirectHandler),
77 ]
76 ]
@@ -1,270 +1,275 b''
1 """This module defines a base Exporter class. For Jinja template-based export,
1 """This module defines a base Exporter class. For Jinja template-based export,
2 see templateexporter.py.
2 see templateexporter.py.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import io
20 import io
21 import os
21 import os
22 import copy
22 import copy
23 import collections
23 import collections
24 import datetime
24 import datetime
25
25
26
26
27 # IPython imports
27 # IPython imports
28 from IPython.config.configurable import LoggingConfigurable
28 from IPython.config.configurable import LoggingConfigurable
29 from IPython.config import Config
29 from IPython.config import Config
30 from IPython.nbformat import current as nbformat
30 from IPython.nbformat import current as nbformat
31 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
31 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
32 from IPython.utils.importstring import import_item
32 from IPython.utils.importstring import import_item
33 from IPython.utils import text, py3compat
33 from IPython.utils import text, py3compat
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Class
36 # Class
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class ResourcesDict(collections.defaultdict):
39 class ResourcesDict(collections.defaultdict):
40 def __missing__(self, key):
40 def __missing__(self, key):
41 return ''
41 return ''
42
42
43
43
44 class Exporter(LoggingConfigurable):
44 class Exporter(LoggingConfigurable):
45 """
45 """
46 Class containing methods that sequentially run a list of preprocessors on a
46 Class containing methods that sequentially run a list of preprocessors on a
47 NotebookNode object and then return the modified NotebookNode object and
47 NotebookNode object and then return the modified NotebookNode object and
48 accompanying resources dict.
48 accompanying resources dict.
49 """
49 """
50
50
51 file_extension = Unicode(
51 file_extension = Unicode(
52 'txt', config=True,
52 'txt', config=True,
53 help="Extension of the file that should be written to disk"
53 help="Extension of the file that should be written to disk"
54 )
54 )
55
55
56 # MIME type of the result file, for HTTP response headers.
57 # This is *not* a traitlet, because we want to be able to access it from
58 # the class, not just on instances.
59 output_mimetype = ''
60
56 #Configurability, allows the user to easily add filters and preprocessors.
61 #Configurability, allows the user to easily add filters and preprocessors.
57 preprocessors = List(config=True,
62 preprocessors = List(config=True,
58 help="""List of preprocessors, by name or namespace, to enable.""")
63 help="""List of preprocessors, by name or namespace, to enable.""")
59
64
60 _preprocessors = None
65 _preprocessors = None
61
66
62 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
67 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
63 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
68 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
64 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
69 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
65 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
70 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
66 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
71 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
67 'IPython.nbconvert.preprocessors.LatexPreprocessor',
72 'IPython.nbconvert.preprocessors.LatexPreprocessor',
68 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
73 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
69 config=True,
74 config=True,
70 help="""List of preprocessors available by default, by name, namespace,
75 help="""List of preprocessors available by default, by name, namespace,
71 instance, or type.""")
76 instance, or type.""")
72
77
73
78
74 def __init__(self, config=None, **kw):
79 def __init__(self, config=None, **kw):
75 """
80 """
76 Public constructor
81 Public constructor
77
82
78 Parameters
83 Parameters
79 ----------
84 ----------
80 config : config
85 config : config
81 User configuration instance.
86 User configuration instance.
82 """
87 """
83 with_default_config = self.default_config
88 with_default_config = self.default_config
84 if config:
89 if config:
85 with_default_config.merge(config)
90 with_default_config.merge(config)
86
91
87 super(Exporter, self).__init__(config=with_default_config, **kw)
92 super(Exporter, self).__init__(config=with_default_config, **kw)
88
93
89 self._init_preprocessors()
94 self._init_preprocessors()
90
95
91
96
92 @property
97 @property
93 def default_config(self):
98 def default_config(self):
94 return Config()
99 return Config()
95
100
96 @nbformat.docstring_nbformat_mod
101 @nbformat.docstring_nbformat_mod
97 def from_notebook_node(self, nb, resources=None, **kw):
102 def from_notebook_node(self, nb, resources=None, **kw):
98 """
103 """
99 Convert a notebook from a notebook node instance.
104 Convert a notebook from a notebook node instance.
100
105
101 Parameters
106 Parameters
102 ----------
107 ----------
103 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
108 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
104 Notebook node
109 Notebook node
105 resources : dict
110 resources : dict
106 Additional resources that can be accessed read/write by
111 Additional resources that can be accessed read/write by
107 preprocessors and filters.
112 preprocessors and filters.
108 **kw
113 **kw
109 Ignored (?)
114 Ignored (?)
110 """
115 """
111 nb_copy = copy.deepcopy(nb)
116 nb_copy = copy.deepcopy(nb)
112 resources = self._init_resources(resources)
117 resources = self._init_resources(resources)
113
118
114 # Preprocess
119 # Preprocess
115 nb_copy, resources = self._preprocess(nb_copy, resources)
120 nb_copy, resources = self._preprocess(nb_copy, resources)
116
121
117 return nb_copy, resources
122 return nb_copy, resources
118
123
119
124
120 def from_filename(self, filename, resources=None, **kw):
125 def from_filename(self, filename, resources=None, **kw):
121 """
126 """
122 Convert a notebook from a notebook file.
127 Convert a notebook from a notebook file.
123
128
124 Parameters
129 Parameters
125 ----------
130 ----------
126 filename : str
131 filename : str
127 Full filename of the notebook file to open and convert.
132 Full filename of the notebook file to open and convert.
128 """
133 """
129
134
130 # Pull the metadata from the filesystem.
135 # Pull the metadata from the filesystem.
131 if resources is None:
136 if resources is None:
132 resources = ResourcesDict()
137 resources = ResourcesDict()
133 if not 'metadata' in resources or resources['metadata'] == '':
138 if not 'metadata' in resources or resources['metadata'] == '':
134 resources['metadata'] = ResourcesDict()
139 resources['metadata'] = ResourcesDict()
135 basename = os.path.basename(filename)
140 basename = os.path.basename(filename)
136 notebook_name = basename[:basename.rfind('.')]
141 notebook_name = basename[:basename.rfind('.')]
137 resources['metadata']['name'] = notebook_name
142 resources['metadata']['name'] = notebook_name
138
143
139 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
144 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
140 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
145 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
141
146
142 with io.open(filename, encoding='utf-8') as f:
147 with io.open(filename, encoding='utf-8') as f:
143 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw)
148 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw)
144
149
145
150
146 def from_file(self, file_stream, resources=None, **kw):
151 def from_file(self, file_stream, resources=None, **kw):
147 """
152 """
148 Convert a notebook from a notebook file.
153 Convert a notebook from a notebook file.
149
154
150 Parameters
155 Parameters
151 ----------
156 ----------
152 file_stream : file-like object
157 file_stream : file-like object
153 Notebook file-like object to convert.
158 Notebook file-like object to convert.
154 """
159 """
155 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
160 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
156
161
157
162
158 def register_preprocessor(self, preprocessor, enabled=False):
163 def register_preprocessor(self, preprocessor, enabled=False):
159 """
164 """
160 Register a preprocessor.
165 Register a preprocessor.
161 Preprocessors are classes that act upon the notebook before it is
166 Preprocessors are classes that act upon the notebook before it is
162 passed into the Jinja templating engine. preprocessors are also
167 passed into the Jinja templating engine. preprocessors are also
163 capable of passing additional information to the Jinja
168 capable of passing additional information to the Jinja
164 templating engine.
169 templating engine.
165
170
166 Parameters
171 Parameters
167 ----------
172 ----------
168 preprocessor : preprocessor
173 preprocessor : preprocessor
169 """
174 """
170 if preprocessor is None:
175 if preprocessor is None:
171 raise TypeError('preprocessor')
176 raise TypeError('preprocessor')
172 isclass = isinstance(preprocessor, type)
177 isclass = isinstance(preprocessor, type)
173 constructed = not isclass
178 constructed = not isclass
174
179
175 # Handle preprocessor's registration based on it's type
180 # Handle preprocessor's registration based on it's type
176 if constructed and isinstance(preprocessor, py3compat.string_types):
181 if constructed and isinstance(preprocessor, py3compat.string_types):
177 # Preprocessor is a string, import the namespace and recursively call
182 # Preprocessor is a string, import the namespace and recursively call
178 # this register_preprocessor method
183 # this register_preprocessor method
179 preprocessor_cls = import_item(preprocessor)
184 preprocessor_cls = import_item(preprocessor)
180 return self.register_preprocessor(preprocessor_cls, enabled)
185 return self.register_preprocessor(preprocessor_cls, enabled)
181
186
182 if constructed and hasattr(preprocessor, '__call__'):
187 if constructed and hasattr(preprocessor, '__call__'):
183 # Preprocessor is a function, no need to construct it.
188 # Preprocessor is a function, no need to construct it.
184 # Register and return the preprocessor.
189 # Register and return the preprocessor.
185 if enabled:
190 if enabled:
186 preprocessor.enabled = True
191 preprocessor.enabled = True
187 self._preprocessors.append(preprocessor)
192 self._preprocessors.append(preprocessor)
188 return preprocessor
193 return preprocessor
189
194
190 elif isclass and isinstance(preprocessor, MetaHasTraits):
195 elif isclass and isinstance(preprocessor, MetaHasTraits):
191 # Preprocessor is configurable. Make sure to pass in new default for
196 # Preprocessor is configurable. Make sure to pass in new default for
192 # the enabled flag if one was specified.
197 # the enabled flag if one was specified.
193 self.register_preprocessor(preprocessor(parent=self), enabled)
198 self.register_preprocessor(preprocessor(parent=self), enabled)
194
199
195 elif isclass:
200 elif isclass:
196 # Preprocessor is not configurable, construct it
201 # Preprocessor is not configurable, construct it
197 self.register_preprocessor(preprocessor(), enabled)
202 self.register_preprocessor(preprocessor(), enabled)
198
203
199 else:
204 else:
200 # Preprocessor is an instance of something without a __call__
205 # Preprocessor is an instance of something without a __call__
201 # attribute.
206 # attribute.
202 raise TypeError('preprocessor')
207 raise TypeError('preprocessor')
203
208
204
209
205 def _init_preprocessors(self):
210 def _init_preprocessors(self):
206 """
211 """
207 Register all of the preprocessors needed for this exporter, disabled
212 Register all of the preprocessors needed for this exporter, disabled
208 unless specified explicitly.
213 unless specified explicitly.
209 """
214 """
210 if self._preprocessors is None:
215 if self._preprocessors is None:
211 self._preprocessors = []
216 self._preprocessors = []
212
217
213 #Load default preprocessors (not necessarly enabled by default).
218 #Load default preprocessors (not necessarly enabled by default).
214 if self.default_preprocessors:
219 if self.default_preprocessors:
215 for preprocessor in self.default_preprocessors:
220 for preprocessor in self.default_preprocessors:
216 self.register_preprocessor(preprocessor)
221 self.register_preprocessor(preprocessor)
217
222
218 #Load user preprocessors. Enable by default.
223 #Load user preprocessors. Enable by default.
219 if self.preprocessors:
224 if self.preprocessors:
220 for preprocessor in self.preprocessors:
225 for preprocessor in self.preprocessors:
221 self.register_preprocessor(preprocessor, enabled=True)
226 self.register_preprocessor(preprocessor, enabled=True)
222
227
223
228
224 def _init_resources(self, resources):
229 def _init_resources(self, resources):
225
230
226 #Make sure the resources dict is of ResourcesDict type.
231 #Make sure the resources dict is of ResourcesDict type.
227 if resources is None:
232 if resources is None:
228 resources = ResourcesDict()
233 resources = ResourcesDict()
229 if not isinstance(resources, ResourcesDict):
234 if not isinstance(resources, ResourcesDict):
230 new_resources = ResourcesDict()
235 new_resources = ResourcesDict()
231 new_resources.update(resources)
236 new_resources.update(resources)
232 resources = new_resources
237 resources = new_resources
233
238
234 #Make sure the metadata extension exists in resources
239 #Make sure the metadata extension exists in resources
235 if 'metadata' in resources:
240 if 'metadata' in resources:
236 if not isinstance(resources['metadata'], ResourcesDict):
241 if not isinstance(resources['metadata'], ResourcesDict):
237 resources['metadata'] = ResourcesDict(resources['metadata'])
242 resources['metadata'] = ResourcesDict(resources['metadata'])
238 else:
243 else:
239 resources['metadata'] = ResourcesDict()
244 resources['metadata'] = ResourcesDict()
240 if not resources['metadata']['name']:
245 if not resources['metadata']['name']:
241 resources['metadata']['name'] = 'Notebook'
246 resources['metadata']['name'] = 'Notebook'
242
247
243 #Set the output extension
248 #Set the output extension
244 resources['output_extension'] = self.file_extension
249 resources['output_extension'] = self.file_extension
245 return resources
250 return resources
246
251
247
252
248 def _preprocess(self, nb, resources):
253 def _preprocess(self, nb, resources):
249 """
254 """
250 Preprocess the notebook before passing it into the Jinja engine.
255 Preprocess the notebook before passing it into the Jinja engine.
251 To preprocess the notebook is to apply all of the
256 To preprocess the notebook is to apply all of the
252
257
253 Parameters
258 Parameters
254 ----------
259 ----------
255 nb : notebook node
260 nb : notebook node
256 notebook that is being exported.
261 notebook that is being exported.
257 resources : a dict of additional resources that
262 resources : a dict of additional resources that
258 can be accessed read/write by preprocessors
263 can be accessed read/write by preprocessors
259 """
264 """
260
265
261 # Do a copy.deepcopy first,
266 # Do a copy.deepcopy first,
262 # we are never safe enough with what the preprocessors could do.
267 # we are never safe enough with what the preprocessors could do.
263 nbc = copy.deepcopy(nb)
268 nbc = copy.deepcopy(nb)
264 resc = copy.deepcopy(resources)
269 resc = copy.deepcopy(resources)
265
270
266 #Run each preprocessor on the notebook. Carry the output along
271 #Run each preprocessor on the notebook. Carry the output along
267 #to each preprocessor
272 #to each preprocessor
268 for preprocessor in self._preprocessors:
273 for preprocessor in self._preprocessors:
269 nbc, resc = preprocessor(nbc, resc)
274 nbc, resc = preprocessor(nbc, resc)
270 return nbc, resc
275 return nbc, resc
@@ -1,56 +1,59 b''
1 """HTML Exporter class"""
1 """HTML Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.utils.traitlets import Unicode, List
15 from IPython.utils.traitlets import Unicode, List
16
16
17 from IPython.nbconvert import preprocessors
17 from IPython.nbconvert import preprocessors
18 from IPython.config import Config
18 from IPython.config import Config
19
19
20 from .templateexporter import TemplateExporter
20 from .templateexporter import TemplateExporter
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 class HTMLExporter(TemplateExporter):
26 class HTMLExporter(TemplateExporter):
27 """
27 """
28 Exports a basic HTML document. This exporter assists with the export of
28 Exports a basic HTML document. This exporter assists with the export of
29 HTML. Inherit from it if you are writing your own HTML template and need
29 HTML. Inherit from it if you are writing your own HTML template and need
30 custom preprocessors/filters. If you don't need custom preprocessors/
30 custom preprocessors/filters. If you don't need custom preprocessors/
31 filters, just change the 'template_file' config option.
31 filters, just change the 'template_file' config option.
32 """
32 """
33
33
34 file_extension = Unicode(
34 file_extension = Unicode(
35 'html', config=True,
35 'html', config=True,
36 help="Extension of the file that should be written to disk"
36 help="Extension of the file that should be written to disk"
37 )
37 )
38
38
39 mime_type = Unicode('text/html', config=True,
40 help="MIME type of the result file, for HTTP response headers."
41 )
42
39 default_template = Unicode('full', config=True, help="""Flavor of the data
43 default_template = Unicode('full', config=True, help="""Flavor of the data
40 format to use. I.E. 'full' or 'basic'""")
44 format to use. I.E. 'full' or 'basic'""")
41
45
42 def _raw_mimetype_default(self):
46 output_mimetype = 'text/html'
43 return 'text/html'
44
47
45 @property
48 @property
46 def default_config(self):
49 def default_config(self):
47 c = Config({
50 c = Config({
48 'CSSHTMLHeaderPreprocessor':{
51 'CSSHTMLHeaderPreprocessor':{
49 'enabled':True
52 'enabled':True
50 },
53 },
51 'HighlightMagicsPreprocessor': {
54 'HighlightMagicsPreprocessor': {
52 'enabled':True
55 'enabled':True
53 }
56 }
54 })
57 })
55 c.merge(super(HTMLExporter,self).default_config)
58 c.merge(super(HTMLExporter,self).default_config)
56 return c
59 return c
@@ -1,93 +1,92 b''
1 """LaTeX Exporter class"""
1 """LaTeX Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import os
16 import os
17
17
18 # IPython imports
18 # IPython imports
19 from IPython.utils.traitlets import Unicode, List
19 from IPython.utils.traitlets import Unicode, List
20 from IPython.config import Config
20 from IPython.config import Config
21
21
22 from IPython.nbconvert import filters, preprocessors
22 from IPython.nbconvert import filters, preprocessors
23 from .templateexporter import TemplateExporter
23 from .templateexporter import TemplateExporter
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes and functions
26 # Classes and functions
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class LatexExporter(TemplateExporter):
29 class LatexExporter(TemplateExporter):
30 """
30 """
31 Exports to a Latex template. Inherit from this class if your template is
31 Exports to a Latex template. Inherit from this class if your template is
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
33 you are writing your own HTML template and need custom tranformers/filters.
33 you are writing your own HTML template and need custom tranformers/filters.
34 If you don't need custom tranformers/filters, just change the
34 If you don't need custom tranformers/filters, just change the
35 'template_file' config option. Place your template in the special "/latex"
35 'template_file' config option. Place your template in the special "/latex"
36 subfolder of the "../templates" folder.
36 subfolder of the "../templates" folder.
37 """
37 """
38
38
39 file_extension = Unicode(
39 file_extension = Unicode(
40 'tex', config=True,
40 'tex', config=True,
41 help="Extension of the file that should be written to disk")
41 help="Extension of the file that should be written to disk")
42
42
43 default_template = Unicode('article', config=True, help="""Template of the
43 default_template = Unicode('article', config=True, help="""Template of the
44 data format to use. I.E. 'article' or 'report'""")
44 data format to use. I.E. 'article' or 'report'""")
45
45
46 #Latex constants
46 #Latex constants
47 default_template_path = Unicode(
47 default_template_path = Unicode(
48 os.path.join("..", "templates", "latex"), config=True,
48 os.path.join("..", "templates", "latex"), config=True,
49 help="Path where the template files are located.")
49 help="Path where the template files are located.")
50
50
51 template_skeleton_path = Unicode(
51 template_skeleton_path = Unicode(
52 os.path.join("..", "templates", "latex", "skeleton"), config=True,
52 os.path.join("..", "templates", "latex", "skeleton"), config=True,
53 help="Path where the template skeleton files are located.")
53 help="Path where the template skeleton files are located.")
54
54
55 #Special Jinja2 syntax that will not conflict when exporting latex.
55 #Special Jinja2 syntax that will not conflict when exporting latex.
56 jinja_comment_block_start = Unicode("((=", config=True)
56 jinja_comment_block_start = Unicode("((=", config=True)
57 jinja_comment_block_end = Unicode("=))", config=True)
57 jinja_comment_block_end = Unicode("=))", config=True)
58 jinja_variable_block_start = Unicode("(((", config=True)
58 jinja_variable_block_start = Unicode("(((", config=True)
59 jinja_variable_block_end = Unicode(")))", config=True)
59 jinja_variable_block_end = Unicode(")))", config=True)
60 jinja_logic_block_start = Unicode("((*", config=True)
60 jinja_logic_block_start = Unicode("((*", config=True)
61 jinja_logic_block_end = Unicode("*))", config=True)
61 jinja_logic_block_end = Unicode("*))", config=True)
62
62
63 #Extension that the template files use.
63 #Extension that the template files use.
64 template_extension = Unicode(".tplx", config=True)
64 template_extension = Unicode(".tplx", config=True)
65
65
66 def _raw_mimetype_default(self):
66 output_mimetype = 'text/latex'
67 return 'text/latex'
68
67
69
68
70 @property
69 @property
71 def default_config(self):
70 def default_config(self):
72 c = Config({
71 c = Config({
73 'NbConvertBase': {
72 'NbConvertBase': {
74 'display_data_priority' : ['latex', 'pdf', 'png', 'jpg', 'svg', 'jpeg', 'text']
73 'display_data_priority' : ['latex', 'pdf', 'png', 'jpg', 'svg', 'jpeg', 'text']
75 },
74 },
76 'ExtractOutputPreprocessor': {
75 'ExtractOutputPreprocessor': {
77 'enabled':True
76 'enabled':True
78 },
77 },
79 'SVG2PDFPreprocessor': {
78 'SVG2PDFPreprocessor': {
80 'enabled':True
79 'enabled':True
81 },
80 },
82 'LatexPreprocessor': {
81 'LatexPreprocessor': {
83 'enabled':True
82 'enabled':True
84 },
83 },
85 'SphinxPreprocessor': {
84 'SphinxPreprocessor': {
86 'enabled':True
85 'enabled':True
87 },
86 },
88 'HighlightMagicsPreprocessor': {
87 'HighlightMagicsPreprocessor': {
89 'enabled':True
88 'enabled':True
90 }
89 }
91 })
90 })
92 c.merge(super(LatexExporter,self).default_config)
91 c.merge(super(LatexExporter,self).default_config)
93 return c
92 return c
@@ -1,43 +1,42 b''
1 """Markdown Exporter class"""
1 """Markdown Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.config import Config
15 from IPython.config import Config
16 from IPython.utils.traitlets import Unicode
16 from IPython.utils.traitlets import Unicode
17
17
18 from .templateexporter import TemplateExporter
18 from .templateexporter import TemplateExporter
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 class MarkdownExporter(TemplateExporter):
24 class MarkdownExporter(TemplateExporter):
25 """
25 """
26 Exports to a markdown document (.md)
26 Exports to a markdown document (.md)
27 """
27 """
28
28
29 file_extension = Unicode(
29 file_extension = Unicode(
30 'md', config=True,
30 'md', config=True,
31 help="Extension of the file that should be written to disk")
31 help="Extension of the file that should be written to disk")
32
32
33 def _raw_mimetype_default(self):
33 output_mimetype = 'text/markdown'
34 return 'text/markdown'
35
34
36 def _raw_mimetypes_default(self):
35 def _raw_mimetypes_default(self):
37 return ['text/markdown', 'text/html']
36 return ['text/markdown', 'text/html', '']
38
37
39 @property
38 @property
40 def default_config(self):
39 def default_config(self):
41 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
40 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
42 c.merge(super(MarkdownExporter,self).default_config)
41 c.merge(super(MarkdownExporter,self).default_config)
43 return c
42 return c
@@ -1,34 +1,32 b''
1 """Python script Exporter class"""
1 """Python script Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.utils.traitlets import Unicode
15 from IPython.utils.traitlets import Unicode
16
16
17 from .templateexporter import TemplateExporter
17 from .templateexporter import TemplateExporter
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 class PythonExporter(TemplateExporter):
23 class PythonExporter(TemplateExporter):
24 """
24 """
25 Exports a Python code file.
25 Exports a Python code file.
26 """
26 """
27
27
28 file_extension = Unicode(
28 file_extension = Unicode(
29 'py', config=True,
29 'py', config=True,
30 help="Extension of the file that should be written to disk")
30 help="Extension of the file that should be written to disk")
31
31
32 def _raw_mimetype_default(self):
32 output_mimetype = 'text/x-python'
33 return 'application/x-python'
34
@@ -1,40 +1,39 b''
1 """restructuredText Exporter class"""
1 """restructuredText Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.utils.traitlets import Unicode
15 from IPython.utils.traitlets import Unicode
16 from IPython.config import Config
16 from IPython.config import Config
17
17
18 from .templateexporter import TemplateExporter
18 from .templateexporter import TemplateExporter
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 class RSTExporter(TemplateExporter):
24 class RSTExporter(TemplateExporter):
25 """
25 """
26 Exports restructured text documents.
26 Exports restructured text documents.
27 """
27 """
28
28
29 file_extension = Unicode(
29 file_extension = Unicode(
30 'rst', config=True,
30 'rst', config=True,
31 help="Extension of the file that should be written to disk")
31 help="Extension of the file that should be written to disk")
32
32
33 def _raw_mimetype_default(self):
33 output_mimetype = 'text/restructuredtext'
34 return 'text/restructuredtext'
34
35
36 @property
35 @property
37 def default_config(self):
36 def default_config(self):
38 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
37 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
39 c.merge(super(RSTExporter,self).default_config)
38 c.merge(super(RSTExporter,self).default_config)
40 return c
39 return c
@@ -1,45 +1,47 b''
1 """HTML slide show Exporter class"""
1 """HTML slide show Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.utils.traitlets import Unicode
15 from IPython.utils.traitlets import Unicode
16
16
17 from IPython.nbconvert import preprocessors
17 from IPython.nbconvert import preprocessors
18 from IPython.config import Config
18 from IPython.config import Config
19
19
20 from .html import HTMLExporter
20 from .html import HTMLExporter
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 class SlidesExporter(HTMLExporter):
26 class SlidesExporter(HTMLExporter):
27 """Exports HTML slides with reveal.js"""
27 """Exports HTML slides with reveal.js"""
28
28
29 file_extension = Unicode(
29 file_extension = Unicode(
30 'slides.html', config=True,
30 'slides.html', config=True,
31 help="Extension of the file that should be written to disk"
31 help="Extension of the file that should be written to disk"
32 )
32 )
33
33
34 output_mimetype = 'text/html'
35
34 default_template = Unicode('reveal', config=True, help="""Template of the
36 default_template = Unicode('reveal', config=True, help="""Template of the
35 data format to use. I.E. 'reveal'""")
37 data format to use. I.E. 'reveal'""")
36
38
37 @property
39 @property
38 def default_config(self):
40 def default_config(self):
39 c = Config({
41 c = Config({
40 'RevealHelpPreprocessor': {
42 'RevealHelpPreprocessor': {
41 'enabled': True,
43 'enabled': True,
42 },
44 },
43 })
45 })
44 c.merge(super(SlidesExporter,self).default_config)
46 c.merge(super(SlidesExporter,self).default_config)
45 return c
47 return c
@@ -1,324 +1,322 b''
1 """This module defines TemplateExporter, a highly configurable converter
1 """This module defines TemplateExporter, a highly configurable converter
2 that uses Jinja2 to export notebook files into different formats.
2 that uses Jinja2 to export notebook files into different formats.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from __future__ import print_function, absolute_import
17 from __future__ import print_function, absolute_import
18
18
19 # Stdlib imports
19 # Stdlib imports
20 import os
20 import os
21
21
22 # other libs/dependencies
22 # other libs/dependencies
23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
23 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, TemplateNotFound
24
24
25 # IPython imports
25 # IPython imports
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
26 from IPython.utils.traitlets import MetaHasTraits, Unicode, List, Dict, Any
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils import py3compat, text
28 from IPython.utils import py3compat, text
29
29
30 from IPython.nbformat.current import docstring_nbformat_mod
30 from IPython.nbformat.current import docstring_nbformat_mod
31 from IPython.nbconvert import filters
31 from IPython.nbconvert import filters
32 from .exporter import Exporter
32 from .exporter import Exporter
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Globals and constants
35 # Globals and constants
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 #Jinja2 extensions to load.
38 #Jinja2 extensions to load.
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
39 JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols']
40
40
41 default_filters = {
41 default_filters = {
42 'indent': text.indent,
42 'indent': text.indent,
43 'markdown2html': filters.markdown2html,
43 'markdown2html': filters.markdown2html,
44 'ansi2html': filters.ansi2html,
44 'ansi2html': filters.ansi2html,
45 'filter_data_type': filters.DataTypeFilter,
45 'filter_data_type': filters.DataTypeFilter,
46 'get_lines': filters.get_lines,
46 'get_lines': filters.get_lines,
47 'highlight2html': filters.Highlight2Html,
47 'highlight2html': filters.Highlight2Html,
48 'highlight2latex': filters.Highlight2Latex,
48 'highlight2latex': filters.Highlight2Latex,
49 'ipython2python': filters.ipython2python,
49 'ipython2python': filters.ipython2python,
50 'posix_path': filters.posix_path,
50 'posix_path': filters.posix_path,
51 'markdown2latex': filters.markdown2latex,
51 'markdown2latex': filters.markdown2latex,
52 'markdown2rst': filters.markdown2rst,
52 'markdown2rst': filters.markdown2rst,
53 'comment_lines': filters.comment_lines,
53 'comment_lines': filters.comment_lines,
54 'strip_ansi': filters.strip_ansi,
54 'strip_ansi': filters.strip_ansi,
55 'strip_dollars': filters.strip_dollars,
55 'strip_dollars': filters.strip_dollars,
56 'strip_files_prefix': filters.strip_files_prefix,
56 'strip_files_prefix': filters.strip_files_prefix,
57 'html2text' : filters.html2text,
57 'html2text' : filters.html2text,
58 'add_anchor': filters.add_anchor,
58 'add_anchor': filters.add_anchor,
59 'ansi2latex': filters.ansi2latex,
59 'ansi2latex': filters.ansi2latex,
60 'wrap_text': filters.wrap_text,
60 'wrap_text': filters.wrap_text,
61 'escape_latex': filters.escape_latex,
61 'escape_latex': filters.escape_latex,
62 'citation2latex': filters.citation2latex,
62 'citation2latex': filters.citation2latex,
63 'path2url': filters.path2url,
63 'path2url': filters.path2url,
64 'add_prompts': filters.add_prompts,
64 'add_prompts': filters.add_prompts,
65 }
65 }
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Class
68 # Class
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 class TemplateExporter(Exporter):
71 class TemplateExporter(Exporter):
72 """
72 """
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
73 Exports notebooks into other file formats. Uses Jinja 2 templating engine
74 to output new formats. Inherit from this class if you are creating a new
74 to output new formats. Inherit from this class if you are creating a new
75 template type along with new filters/preprocessors. If the filters/
75 template type along with new filters/preprocessors. If the filters/
76 preprocessors provided by default suffice, there is no need to inherit from
76 preprocessors provided by default suffice, there is no need to inherit from
77 this class. Instead, override the template_file and file_extension
77 this class. Instead, override the template_file and file_extension
78 traits via a config file.
78 traits via a config file.
79
79
80 {filters}
80 {filters}
81 """
81 """
82
82
83 # finish the docstring
83 # finish the docstring
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
84 __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys()))
85
85
86
86
87 template_file = Unicode(u'default',
87 template_file = Unicode(u'default',
88 config=True,
88 config=True,
89 help="Name of the template file to use")
89 help="Name of the template file to use")
90 def _template_file_changed(self, name, old, new):
90 def _template_file_changed(self, name, old, new):
91 if new == 'default':
91 if new == 'default':
92 self.template_file = self.default_template
92 self.template_file = self.default_template
93 else:
93 else:
94 self.template_file = new
94 self.template_file = new
95 self.template = None
95 self.template = None
96 self._load_template()
96 self._load_template()
97
97
98 default_template = Unicode(u'')
98 default_template = Unicode(u'')
99 template = Any()
99 template = Any()
100 environment = Any()
100 environment = Any()
101
101
102 template_path = List(['.'], config=True)
102 template_path = List(['.'], config=True)
103 def _template_path_changed(self, name, old, new):
103 def _template_path_changed(self, name, old, new):
104 self._load_template()
104 self._load_template()
105
105
106 default_template_path = Unicode(
106 default_template_path = Unicode(
107 os.path.join("..", "templates"),
107 os.path.join("..", "templates"),
108 help="Path where the template files are located.")
108 help="Path where the template files are located.")
109
109
110 template_skeleton_path = Unicode(
110 template_skeleton_path = Unicode(
111 os.path.join("..", "templates", "skeleton"),
111 os.path.join("..", "templates", "skeleton"),
112 help="Path where the template skeleton files are located.")
112 help="Path where the template skeleton files are located.")
113
113
114 #Jinja block definitions
114 #Jinja block definitions
115 jinja_comment_block_start = Unicode("", config=True)
115 jinja_comment_block_start = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
116 jinja_comment_block_end = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
117 jinja_variable_block_start = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
118 jinja_variable_block_end = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
119 jinja_logic_block_start = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
120 jinja_logic_block_end = Unicode("", config=True)
121
121
122 #Extension that the template files use.
122 #Extension that the template files use.
123 template_extension = Unicode(".tpl", config=True)
123 template_extension = Unicode(".tpl", config=True)
124
124
125 filters = Dict(config=True,
125 filters = Dict(config=True,
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 environment.""")
127 environment.""")
128
128
129 raw_mimetype = Unicode('')
130 raw_mimetypes = List(config=True,
129 raw_mimetypes = List(config=True,
131 help="""formats of raw cells to be included in this Exporter's output."""
130 help="""formats of raw cells to be included in this Exporter's output."""
132 )
131 )
133 def _raw_mimetypes_default(self):
132 def _raw_mimetypes_default(self):
134 return [self.raw_mimetype]
133 return [self.output_mimetype, '']
135
134
136
135
137 def __init__(self, config=None, extra_loaders=None, **kw):
136 def __init__(self, config=None, extra_loaders=None, **kw):
138 """
137 """
139 Public constructor
138 Public constructor
140
139
141 Parameters
140 Parameters
142 ----------
141 ----------
143 config : config
142 config : config
144 User configuration instance.
143 User configuration instance.
145 extra_loaders : list[of Jinja Loaders]
144 extra_loaders : list[of Jinja Loaders]
146 ordered list of Jinja loader to find templates. Will be tried in order
145 ordered list of Jinja loader to find templates. Will be tried in order
147 before the default FileSystem ones.
146 before the default FileSystem ones.
148 template : str (optional, kw arg)
147 template : str (optional, kw arg)
149 Template to use when exporting.
148 Template to use when exporting.
150 """
149 """
151 super(TemplateExporter, self).__init__(config=config, **kw)
150 super(TemplateExporter, self).__init__(config=config, **kw)
152
151
153 #Init
152 #Init
154 self._init_template()
153 self._init_template()
155 self._init_environment(extra_loaders=extra_loaders)
154 self._init_environment(extra_loaders=extra_loaders)
156 self._init_preprocessors()
155 self._init_preprocessors()
157 self._init_filters()
156 self._init_filters()
158
157
159
158
160 def _load_template(self):
159 def _load_template(self):
161 """Load the Jinja template object from the template file
160 """Load the Jinja template object from the template file
162
161
163 This is a no-op if the template attribute is already defined,
162 This is a no-op if the template attribute is already defined,
164 or the Jinja environment is not setup yet.
163 or the Jinja environment is not setup yet.
165
164
166 This is triggered by various trait changes that would change the template.
165 This is triggered by various trait changes that would change the template.
167 """
166 """
168 if self.template is not None:
167 if self.template is not None:
169 return
168 return
170 # called too early, do nothing
169 # called too early, do nothing
171 if self.environment is None:
170 if self.environment is None:
172 return
171 return
173 # Try different template names during conversion. First try to load the
172 # Try different template names during conversion. First try to load the
174 # template by name with extension added, then try loading the template
173 # template by name with extension added, then try loading the template
175 # as if the name is explicitly specified, then try the name as a
174 # as if the name is explicitly specified, then try the name as a
176 # 'flavor', and lastly just try to load the template by module name.
175 # 'flavor', and lastly just try to load the template by module name.
177 module_name = self.__module__.rsplit('.', 1)[-1]
176 module_name = self.__module__.rsplit('.', 1)[-1]
178 try_names = []
177 try_names = []
179 if self.template_file:
178 if self.template_file:
180 try_names.extend([
179 try_names.extend([
181 self.template_file + self.template_extension,
180 self.template_file + self.template_extension,
182 self.template_file,
181 self.template_file,
183 module_name + '_' + self.template_file + self.template_extension,
182 module_name + '_' + self.template_file + self.template_extension,
184 ])
183 ])
185 try_names.append(module_name + self.template_extension)
184 try_names.append(module_name + self.template_extension)
186 for try_name in try_names:
185 for try_name in try_names:
187 self.log.debug("Attempting to load template %s", try_name)
186 self.log.debug("Attempting to load template %s", try_name)
188 try:
187 try:
189 self.template = self.environment.get_template(try_name)
188 self.template = self.environment.get_template(try_name)
190 except (TemplateNotFound, IOError):
189 except (TemplateNotFound, IOError):
191 pass
190 pass
192 except Exception as e:
191 except Exception as e:
193 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
192 self.log.warn("Unexpected exception loading template: %s", try_name, exc_info=True)
194 else:
193 else:
195 self.log.info("Loaded template %s", try_name)
194 self.log.info("Loaded template %s", try_name)
196 break
195 break
197
196
198 @docstring_nbformat_mod
197 @docstring_nbformat_mod
199 def from_notebook_node(self, nb, resources=None, **kw):
198 def from_notebook_node(self, nb, resources=None, **kw):
200 """
199 """
201 Convert a notebook from a notebook node instance.
200 Convert a notebook from a notebook node instance.
202
201
203 Parameters
202 Parameters
204 ----------
203 ----------
205 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
204 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
206 Notebook node
205 Notebook node
207 resources : dict
206 resources : dict
208 Additional resources that can be accessed read/write by
207 Additional resources that can be accessed read/write by
209 preprocessors and filters.
208 preprocessors and filters.
210 """
209 """
211 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
210 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
212 resources.setdefault('raw_mimetype', self.raw_mimetype)
213 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
211 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
214
212
215 self._load_template()
213 self._load_template()
216
214
217 if self.template is not None:
215 if self.template is not None:
218 output = self.template.render(nb=nb_copy, resources=resources)
216 output = self.template.render(nb=nb_copy, resources=resources)
219 else:
217 else:
220 raise IOError('template file "%s" could not be found' % self.template_file)
218 raise IOError('template file "%s" could not be found' % self.template_file)
221 return output, resources
219 return output, resources
222
220
223
221
224 def register_filter(self, name, jinja_filter):
222 def register_filter(self, name, jinja_filter):
225 """
223 """
226 Register a filter.
224 Register a filter.
227 A filter is a function that accepts and acts on one string.
225 A filter is a function that accepts and acts on one string.
228 The filters are accesible within the Jinja templating engine.
226 The filters are accesible within the Jinja templating engine.
229
227
230 Parameters
228 Parameters
231 ----------
229 ----------
232 name : str
230 name : str
233 name to give the filter in the Jinja engine
231 name to give the filter in the Jinja engine
234 filter : filter
232 filter : filter
235 """
233 """
236 if jinja_filter is None:
234 if jinja_filter is None:
237 raise TypeError('filter')
235 raise TypeError('filter')
238 isclass = isinstance(jinja_filter, type)
236 isclass = isinstance(jinja_filter, type)
239 constructed = not isclass
237 constructed = not isclass
240
238
241 #Handle filter's registration based on it's type
239 #Handle filter's registration based on it's type
242 if constructed and isinstance(jinja_filter, py3compat.string_types):
240 if constructed and isinstance(jinja_filter, py3compat.string_types):
243 #filter is a string, import the namespace and recursively call
241 #filter is a string, import the namespace and recursively call
244 #this register_filter method
242 #this register_filter method
245 filter_cls = import_item(jinja_filter)
243 filter_cls = import_item(jinja_filter)
246 return self.register_filter(name, filter_cls)
244 return self.register_filter(name, filter_cls)
247
245
248 if constructed and hasattr(jinja_filter, '__call__'):
246 if constructed and hasattr(jinja_filter, '__call__'):
249 #filter is a function, no need to construct it.
247 #filter is a function, no need to construct it.
250 self.environment.filters[name] = jinja_filter
248 self.environment.filters[name] = jinja_filter
251 return jinja_filter
249 return jinja_filter
252
250
253 elif isclass and isinstance(jinja_filter, MetaHasTraits):
251 elif isclass and isinstance(jinja_filter, MetaHasTraits):
254 #filter is configurable. Make sure to pass in new default for
252 #filter is configurable. Make sure to pass in new default for
255 #the enabled flag if one was specified.
253 #the enabled flag if one was specified.
256 filter_instance = jinja_filter(parent=self)
254 filter_instance = jinja_filter(parent=self)
257 self.register_filter(name, filter_instance )
255 self.register_filter(name, filter_instance )
258
256
259 elif isclass:
257 elif isclass:
260 #filter is not configurable, construct it
258 #filter is not configurable, construct it
261 filter_instance = jinja_filter()
259 filter_instance = jinja_filter()
262 self.register_filter(name, filter_instance)
260 self.register_filter(name, filter_instance)
263
261
264 else:
262 else:
265 #filter is an instance of something without a __call__
263 #filter is an instance of something without a __call__
266 #attribute.
264 #attribute.
267 raise TypeError('filter')
265 raise TypeError('filter')
268
266
269
267
270 def _init_template(self):
268 def _init_template(self):
271 """
269 """
272 Make sure a template name is specified. If one isn't specified, try to
270 Make sure a template name is specified. If one isn't specified, try to
273 build one from the information we know.
271 build one from the information we know.
274 """
272 """
275 self._template_file_changed('template_file', self.template_file, self.template_file)
273 self._template_file_changed('template_file', self.template_file, self.template_file)
276
274
277
275
278 def _init_environment(self, extra_loaders=None):
276 def _init_environment(self, extra_loaders=None):
279 """
277 """
280 Create the Jinja templating environment.
278 Create the Jinja templating environment.
281 """
279 """
282 here = os.path.dirname(os.path.realpath(__file__))
280 here = os.path.dirname(os.path.realpath(__file__))
283 loaders = []
281 loaders = []
284 if extra_loaders:
282 if extra_loaders:
285 loaders.extend(extra_loaders)
283 loaders.extend(extra_loaders)
286
284
287 paths = self.template_path
285 paths = self.template_path
288 paths.extend([os.path.join(here, self.default_template_path),
286 paths.extend([os.path.join(here, self.default_template_path),
289 os.path.join(here, self.template_skeleton_path)])
287 os.path.join(here, self.template_skeleton_path)])
290 loaders.append(FileSystemLoader(paths))
288 loaders.append(FileSystemLoader(paths))
291
289
292 self.environment = Environment(
290 self.environment = Environment(
293 loader= ChoiceLoader(loaders),
291 loader= ChoiceLoader(loaders),
294 extensions=JINJA_EXTENSIONS
292 extensions=JINJA_EXTENSIONS
295 )
293 )
296
294
297 #Set special Jinja2 syntax that will not conflict with latex.
295 #Set special Jinja2 syntax that will not conflict with latex.
298 if self.jinja_logic_block_start:
296 if self.jinja_logic_block_start:
299 self.environment.block_start_string = self.jinja_logic_block_start
297 self.environment.block_start_string = self.jinja_logic_block_start
300 if self.jinja_logic_block_end:
298 if self.jinja_logic_block_end:
301 self.environment.block_end_string = self.jinja_logic_block_end
299 self.environment.block_end_string = self.jinja_logic_block_end
302 if self.jinja_variable_block_start:
300 if self.jinja_variable_block_start:
303 self.environment.variable_start_string = self.jinja_variable_block_start
301 self.environment.variable_start_string = self.jinja_variable_block_start
304 if self.jinja_variable_block_end:
302 if self.jinja_variable_block_end:
305 self.environment.variable_end_string = self.jinja_variable_block_end
303 self.environment.variable_end_string = self.jinja_variable_block_end
306 if self.jinja_comment_block_start:
304 if self.jinja_comment_block_start:
307 self.environment.comment_start_string = self.jinja_comment_block_start
305 self.environment.comment_start_string = self.jinja_comment_block_start
308 if self.jinja_comment_block_end:
306 if self.jinja_comment_block_end:
309 self.environment.comment_end_string = self.jinja_comment_block_end
307 self.environment.comment_end_string = self.jinja_comment_block_end
310
308
311
309
312 def _init_filters(self):
310 def _init_filters(self):
313 """
311 """
314 Register all of the filters required for the exporter.
312 Register all of the filters required for the exporter.
315 """
313 """
316
314
317 #Add default filters to the Jinja2 environment
315 #Add default filters to the Jinja2 environment
318 for key, value in default_filters.items():
316 for key, value in default_filters.items():
319 self.register_filter(key, value)
317 self.register_filter(key, value)
320
318
321 #Load user filters. Overwrite existing filters if need be.
319 #Load user filters. Overwrite existing filters if need be.
322 if self.filters:
320 if self.filters:
323 for key, user_filter in self.filters.items():
321 for key, user_filter in self.filters.items():
324 self.register_filter(key, user_filter)
322 self.register_filter(key, user_filter)
@@ -1,54 +1,54 b''
1 """Base TestCase class for testing Exporters"""
1 """Base TestCase class for testing Exporters"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16
16
17 from IPython.testing.decorators import onlyif_cmds_exist
17 from IPython.testing.decorators import onlyif_cmds_exist
18
18
19 from ...tests.base import TestsBase
19 from ...tests.base import TestsBase
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Class
22 # Class
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 all_raw_mimetypes = {
25 all_raw_mimetypes = {
26 'application/x-python',
26 'text/x-python',
27 'text/markdown',
27 'text/markdown',
28 'text/html',
28 'text/html',
29 'text/restructuredtext',
29 'text/restructuredtext',
30 'text/latex',
30 'text/latex',
31 }
31 }
32
32
33 class ExportersTestsBase(TestsBase):
33 class ExportersTestsBase(TestsBase):
34 """Contains base test functions for exporters"""
34 """Contains base test functions for exporters"""
35
35
36 exporter_class = None
36 exporter_class = None
37 should_include_raw = None
37 should_include_raw = None
38
38
39 def _get_notebook(self, nb_name='notebook2.ipynb'):
39 def _get_notebook(self, nb_name='notebook2.ipynb'):
40 return os.path.join(self._get_files_path(), nb_name)
40 return os.path.join(self._get_files_path(), nb_name)
41
41
42 @onlyif_cmds_exist('pandoc')
42 @onlyif_cmds_exist('pandoc')
43 def test_raw_cell_inclusion(self):
43 def test_raw_cell_inclusion(self):
44 """test raw cell inclusion based on raw_mimetype metadata"""
44 """test raw cell inclusion based on raw_mimetype metadata"""
45 if self.should_include_raw is None:
45 if self.should_include_raw is None:
46 return
46 return
47 exporter = self.exporter_class()
47 exporter = self.exporter_class()
48 (output, resources) = exporter.from_filename(self._get_notebook('rawtest.ipynb'))
48 (output, resources) = exporter.from_filename(self._get_notebook('rawtest.ipynb'))
49 for inc in self.should_include_raw:
49 for inc in self.should_include_raw:
50 self.assertIn('raw %s' % inc, output, "should include %s" % inc)
50 self.assertIn('raw %s' % inc, output, "should include %s" % inc)
51 self.assertIn('no raw_mimetype metadata', output)
51 self.assertIn('no raw_mimetype metadata', output)
52 for exc in all_raw_mimetypes.difference(self.should_include_raw):
52 for exc in all_raw_mimetypes.difference(self.should_include_raw):
53 self.assertNotIn('raw %s' % exc, output, "should exclude %s" % exc)
53 self.assertNotIn('raw %s' % exc, output, "should exclude %s" % exc)
54 self.assertNotIn('never be included', output)
54 self.assertNotIn('never be included', output)
@@ -1,84 +1,84 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "name": ""
3 "name": ""
4 },
4 },
5 "nbformat": 3,
5 "nbformat": 3,
6 "nbformat_minor": 0,
6 "nbformat_minor": 0,
7 "worksheets": [
7 "worksheets": [
8 {
8 {
9 "cells": [
9 "cells": [
10 {
10 {
11 "cell_type": "raw",
11 "cell_type": "raw",
12 "metadata": {
12 "metadata": {
13 "raw_mimetype": "text/html"
13 "raw_mimetype": "text/html"
14 },
14 },
15 "source": [
15 "source": [
16 "<b>raw html</b>"
16 "<b>raw html</b>"
17 ]
17 ]
18 },
18 },
19 {
19 {
20 "cell_type": "raw",
20 "cell_type": "raw",
21 "metadata": {
21 "metadata": {
22 "raw_mimetype": "text/markdown"
22 "raw_mimetype": "text/markdown"
23 },
23 },
24 "source": [
24 "source": [
25 "* raw markdown\n",
25 "* raw markdown\n",
26 "* bullet\n",
26 "* bullet\n",
27 "* list"
27 "* list"
28 ]
28 ]
29 },
29 },
30 {
30 {
31 "cell_type": "raw",
31 "cell_type": "raw",
32 "metadata": {
32 "metadata": {
33 "raw_mimetype": "text/restructuredtext"
33 "raw_mimetype": "text/restructuredtext"
34 },
34 },
35 "source": [
35 "source": [
36 "``raw rst``\n",
36 "``raw rst``\n",
37 "\n",
37 "\n",
38 ".. sourcecode:: python\n",
38 ".. sourcecode:: python\n",
39 "\n",
39 "\n",
40 " def foo(): pass\n"
40 " def foo(): pass\n"
41 ]
41 ]
42 },
42 },
43 {
43 {
44 "cell_type": "raw",
44 "cell_type": "raw",
45 "metadata": {
45 "metadata": {
46 "raw_mimetype": "application/x-python"
46 "raw_mimetype": "text/x-python"
47 },
47 },
48 "source": [
48 "source": [
49 "def bar():\n",
49 "def bar():\n",
50 " \"\"\"raw python\"\"\"\n",
50 " \"\"\"raw python\"\"\"\n",
51 " pass"
51 " pass"
52 ]
52 ]
53 },
53 },
54 {
54 {
55 "cell_type": "raw",
55 "cell_type": "raw",
56 "metadata": {
56 "metadata": {
57 "raw_mimetype": "text/latex"
57 "raw_mimetype": "text/latex"
58 },
58 },
59 "source": [
59 "source": [
60 "\\LaTeX\n",
60 "\\LaTeX\n",
61 "% raw latex"
61 "% raw latex"
62 ]
62 ]
63 },
63 },
64 {
64 {
65 "cell_type": "raw",
65 "cell_type": "raw",
66 "metadata": {},
66 "metadata": {},
67 "source": [
67 "source": [
68 "# no raw_mimetype metadata, should be included by default"
68 "# no raw_mimetype metadata, should be included by default"
69 ]
69 ]
70 },
70 },
71 {
71 {
72 "cell_type": "raw",
72 "cell_type": "raw",
73 "metadata": {
73 "metadata": {
74 "raw_mimetype": "doesnotexist"
74 "raw_mimetype": "doesnotexist"
75 },
75 },
76 "source": [
76 "source": [
77 "garbage format defined, should never be included"
77 "garbage format defined, should never be included"
78 ]
78 ]
79 }
79 }
80 ],
80 ],
81 "metadata": {}
81 "metadata": {}
82 }
82 }
83 ]
83 ]
84 } No newline at end of file
84 }
@@ -1,102 +1,103 b''
1 """Module containing a preprocessor that extracts all of the outputs from the
1 """Module containing a preprocessor that extracts all of the outputs from the
2 notebook file. The extracted outputs are returned in the 'resources' dictionary.
2 notebook file. The extracted outputs are returned in the 'resources' dictionary.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import base64
16 import base64
17 import sys
17 import sys
18 import os
18 import os
19
19
20 from IPython.utils.traitlets import Unicode
20 from IPython.utils.traitlets import Unicode, Set
21 from .base import Preprocessor
21 from .base import Preprocessor
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 class ExtractOutputPreprocessor(Preprocessor):
28 class ExtractOutputPreprocessor(Preprocessor):
29 """
29 """
30 Extracts all of the outputs from the notebook file. The extracted
30 Extracts all of the outputs from the notebook file. The extracted
31 outputs are returned in the 'resources' dictionary.
31 outputs are returned in the 'resources' dictionary.
32 """
32 """
33
33
34 output_filename_template = Unicode(
34 output_filename_template = Unicode(
35 "{unique_key}_{cell_index}_{index}.{extension}", config=True)
35 "{unique_key}_{cell_index}_{index}.{extension}", config=True)
36
36
37 extract_output_types = Set({'png', 'jpg', 'svg', 'pdf'}, config=True)
37
38
38 def preprocess_cell(self, cell, resources, cell_index):
39 def preprocess_cell(self, cell, resources, cell_index):
39 """
40 """
40 Apply a transformation on each cell,
41 Apply a transformation on each cell,
41
42
42 Parameters
43 Parameters
43 ----------
44 ----------
44 cell : NotebookNode cell
45 cell : NotebookNode cell
45 Notebook cell being processed
46 Notebook cell being processed
46 resources : dictionary
47 resources : dictionary
47 Additional resources used in the conversion process. Allows
48 Additional resources used in the conversion process. Allows
48 preprocessors to pass variables into the Jinja engine.
49 preprocessors to pass variables into the Jinja engine.
49 cell_index : int
50 cell_index : int
50 Index of the cell being processed (see base.py)
51 Index of the cell being processed (see base.py)
51 """
52 """
52
53
53 #Get the unique key from the resource dict if it exists. If it does not
54 #Get the unique key from the resource dict if it exists. If it does not
54 #exist, use 'output' as the default. Also, get files directory if it
55 #exist, use 'output' as the default. Also, get files directory if it
55 #has been specified
56 #has been specified
56 unique_key = resources.get('unique_key', 'output')
57 unique_key = resources.get('unique_key', 'output')
57 output_files_dir = resources.get('output_files_dir', None)
58 output_files_dir = resources.get('output_files_dir', None)
58
59
59 #Make sure outputs key exists
60 #Make sure outputs key exists
60 if not isinstance(resources['outputs'], dict):
61 if not isinstance(resources['outputs'], dict):
61 resources['outputs'] = {}
62 resources['outputs'] = {}
62
63
63 #Loop through all of the outputs in the cell
64 #Loop through all of the outputs in the cell
64 for index, out in enumerate(cell.get('outputs', [])):
65 for index, out in enumerate(cell.get('outputs', [])):
65
66
66 #Get the output in data formats that the template is interested in.
67 #Get the output in data formats that the template needs extracted
67 for out_type in self.display_data_priority:
68 for out_type in self.extract_output_types:
68 if out.hasattr(out_type):
69 if out.hasattr(out_type):
69 data = out[out_type]
70 data = out[out_type]
70
71
71 #Binary files are base64-encoded, SVG is already XML
72 #Binary files are base64-encoded, SVG is already XML
72 if out_type in ('png', 'jpg', 'jpeg', 'pdf'):
73 if out_type in ('png', 'jpg', 'jpeg', 'pdf'):
73
74
74 # data is b64-encoded as text (str, unicode)
75 # data is b64-encoded as text (str, unicode)
75 # decodestring only accepts bytes
76 # decodestring only accepts bytes
76 data = py3compat.cast_bytes(data)
77 data = py3compat.cast_bytes(data)
77 data = base64.decodestring(data)
78 data = base64.decodestring(data)
78 elif sys.platform == 'win32':
79 elif sys.platform == 'win32':
79 data = data.replace('\n', '\r\n').encode("UTF-8")
80 data = data.replace('\n', '\r\n').encode("UTF-8")
80 else:
81 else:
81 data = data.encode("UTF-8")
82 data = data.encode("UTF-8")
82
83
83 #Build an output name
84 #Build an output name
84 filename = self.output_filename_template.format(
85 filename = self.output_filename_template.format(
85 unique_key=unique_key,
86 unique_key=unique_key,
86 cell_index=cell_index,
87 cell_index=cell_index,
87 index=index,
88 index=index,
88 extension=out_type)
89 extension=out_type)
89
90
90 #On the cell, make the figure available via
91 #On the cell, make the figure available via
91 # cell.outputs[i].svg_filename ... etc (svg in example)
92 # cell.outputs[i].svg_filename ... etc (svg in example)
92 # Where
93 # Where
93 # cell.outputs[i].svg contains the data
94 # cell.outputs[i].svg contains the data
94 if output_files_dir is not None:
95 if output_files_dir is not None:
95 filename = os.path.join(output_files_dir, filename)
96 filename = os.path.join(output_files_dir, filename)
96 out[out_type + '_filename'] = filename
97 out[out_type + '_filename'] = filename
97
98
98 #In the resources, make the figure available via
99 #In the resources, make the figure available via
99 # resources['outputs']['filename'] = data
100 # resources['outputs']['filename'] = data
100 resources['outputs'][filename] = data
101 resources['outputs'][filename] = data
101
102
102 return cell, resources
103 return cell, resources
@@ -1,64 +1,65 b''
1 """
1 """
2 Module with tests for the extractoutput preprocessor
2 Module with tests for the extractoutput preprocessor
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from .base import PreprocessorTestsBase
17 from .base import PreprocessorTestsBase
18 from ..extractoutput import ExtractOutputPreprocessor
18 from ..extractoutput import ExtractOutputPreprocessor
19
19
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Class
22 # Class
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class TestExtractOutput(PreprocessorTestsBase):
25 class TestExtractOutput(PreprocessorTestsBase):
26 """Contains test functions for extractoutput.py"""
26 """Contains test functions for extractoutput.py"""
27
27
28
28
29 def build_preprocessor(self):
29 def build_preprocessor(self):
30 """Make an instance of a preprocessor"""
30 """Make an instance of a preprocessor"""
31 preprocessor = ExtractOutputPreprocessor()
31 preprocessor = ExtractOutputPreprocessor()
32 preprocessor.extract_output_types = {'text', 'png'}
32 preprocessor.enabled = True
33 preprocessor.enabled = True
33 return preprocessor
34 return preprocessor
34
35
35
36
36 def test_constructor(self):
37 def test_constructor(self):
37 """Can a ExtractOutputPreprocessor be constructed?"""
38 """Can a ExtractOutputPreprocessor be constructed?"""
38 self.build_preprocessor()
39 self.build_preprocessor()
39
40
40
41
41 def test_output(self):
42 def test_output(self):
42 """Test the output of the ExtractOutputPreprocessor"""
43 """Test the output of the ExtractOutputPreprocessor"""
43 nb = self.build_notebook()
44 nb = self.build_notebook()
44 res = self.build_resources()
45 res = self.build_resources()
45 preprocessor = self.build_preprocessor()
46 preprocessor = self.build_preprocessor()
46 nb, res = preprocessor(nb, res)
47 nb, res = preprocessor(nb, res)
47
48
48 # Check if text was extracted.
49 # Check if text was extracted.
49 output = nb.worksheets[0].cells[0].outputs[1]
50 output = nb.worksheets[0].cells[0].outputs[1]
50 assert 'text_filename' in output
51 assert 'text_filename' in output
51 text_filename = output['text_filename']
52 text_filename = output['text_filename']
52
53
53 # Check if png was extracted.
54 # Check if png was extracted.
54 output = nb.worksheets[0].cells[0].outputs[6]
55 output = nb.worksheets[0].cells[0].outputs[6]
55 assert 'png_filename' in output
56 assert 'png_filename' in output
56 png_filename = output['png_filename']
57 png_filename = output['png_filename']
57
58
58 # Verify text output
59 # Verify text output
59 assert text_filename in res['outputs']
60 assert text_filename in res['outputs']
60 self.assertEqual(res['outputs'][text_filename], b'b')
61 self.assertEqual(res['outputs'][text_filename], b'b')
61
62
62 # Verify png output
63 # Verify png output
63 assert png_filename in res['outputs']
64 assert png_filename in res['outputs']
64 self.assertEqual(res['outputs'][png_filename], b'g')
65 self.assertEqual(res['outputs'][png_filename], b'g')
@@ -1,98 +1,98 b''
1 ((= Auto-generated template file, DO NOT edit directly!
1 ((= Auto-generated template file, DO NOT edit directly!
2 To edit this file, please refer to ../../skeleton/README.md =))
2 To edit this file, please refer to ../../skeleton/README.md =))
3
3
4
4
5 ((=
5 ((=
6
6
7 DO NOT USE THIS AS A BASE,
7 DO NOT USE THIS AS A BASE,
8 IF YOU ARE COPY AND PASTING THIS FILE
8 IF YOU ARE COPY AND PASTING THIS FILE
9 YOU ARE PROBABLY DOING THINGS INCORRECTLY.
9 YOU ARE PROBABLY DOING THINGS INCORRECTLY.
10
10
11 Null template, does nothing except defining a basic structure
11 Null template, does nothing except defining a basic structure
12 To layout the different blocks of a notebook.
12 To layout the different blocks of a notebook.
13
13
14 Subtemplates can override blocks to define their custom representation.
14 Subtemplates can override blocks to define their custom representation.
15
15
16 If one of the block you do overwrite is not a leave block, consider
16 If one of the block you do overwrite is not a leave block, consider
17 calling super.
17 calling super.
18
18
19 ((*- block nonLeaveBlock -*))
19 ((*- block nonLeaveBlock -*))
20 #add stuff at beginning
20 #add stuff at beginning
21 ((( super() )))
21 ((( super() )))
22 #add stuff at end
22 #add stuff at end
23 ((*- endblock nonLeaveBlock -*))
23 ((*- endblock nonLeaveBlock -*))
24
24
25 consider calling super even if it is a leave block, we might insert more blocks later.
25 consider calling super even if it is a leave block, we might insert more blocks later.
26
26
27 =))
27 =))
28 ((*- block header -*))
28 ((*- block header -*))
29 ((*- endblock header -*))
29 ((*- endblock header -*))
30 ((*- block body -*))
30 ((*- block body -*))
31 ((*- for worksheet in nb.worksheets -*))
31 ((*- for worksheet in nb.worksheets -*))
32 ((*- for cell in worksheet.cells -*))
32 ((*- for cell in worksheet.cells -*))
33 ((*- block any_cell scoped -*))
33 ((*- block any_cell scoped -*))
34 ((*- if cell.cell_type in ['code'] -*))
34 ((*- if cell.cell_type in ['code'] -*))
35 ((*- block codecell scoped -*))
35 ((*- block codecell scoped -*))
36 ((*- block input_group -*))
36 ((*- block input_group -*))
37 ((*- block in_prompt -*))((*- endblock in_prompt -*))
37 ((*- block in_prompt -*))((*- endblock in_prompt -*))
38 ((*- block input -*))((*- endblock input -*))
38 ((*- block input -*))((*- endblock input -*))
39 ((*- endblock input_group -*))
39 ((*- endblock input_group -*))
40 ((*- if cell.outputs -*))
40 ((*- if cell.outputs -*))
41 ((*- block output_group -*))
41 ((*- block output_group -*))
42 ((*- block output_prompt -*))((*- endblock output_prompt -*))
42 ((*- block output_prompt -*))((*- endblock output_prompt -*))
43 ((*- block outputs scoped -*))
43 ((*- block outputs scoped -*))
44 ((*- for output in cell.outputs -*))
44 ((*- for output in cell.outputs -*))
45 ((*- block output scoped -*))
45 ((*- block output scoped -*))
46 ((*- if output.output_type in ['pyout'] -*))
46 ((*- if output.output_type in ['pyout'] -*))
47 ((*- block pyout scoped -*))((*- endblock pyout -*))
47 ((*- block pyout scoped -*))((*- endblock pyout -*))
48 ((*- elif output.output_type in ['stream'] -*))
48 ((*- elif output.output_type in ['stream'] -*))
49 ((*- block stream scoped -*))
49 ((*- block stream scoped -*))
50 ((*- if output.stream in ['stdout'] -*))
50 ((*- if output.stream in ['stdout'] -*))
51 ((*- block stream_stdout scoped -*))
51 ((*- block stream_stdout scoped -*))
52 ((*- endblock stream_stdout -*))
52 ((*- endblock stream_stdout -*))
53 ((*- elif output.stream in ['stderr'] -*))
53 ((*- elif output.stream in ['stderr'] -*))
54 ((*- block stream_stderr scoped -*))
54 ((*- block stream_stderr scoped -*))
55 ((*- endblock stream_stderr -*))
55 ((*- endblock stream_stderr -*))
56 ((*- endif -*))
56 ((*- endif -*))
57 ((*- endblock stream -*))
57 ((*- endblock stream -*))
58 ((*- elif output.output_type in ['display_data'] -*))
58 ((*- elif output.output_type in ['display_data'] -*))
59 ((*- block display_data scoped -*))
59 ((*- block display_data scoped -*))
60 ((*- block data_priority scoped -*))
60 ((*- block data_priority scoped -*))
61 ((*- endblock data_priority -*))
61 ((*- endblock data_priority -*))
62 ((*- endblock display_data -*))
62 ((*- endblock display_data -*))
63 ((*- elif output.output_type in ['pyerr'] -*))
63 ((*- elif output.output_type in ['pyerr'] -*))
64 ((*- block pyerr scoped -*))
64 ((*- block pyerr scoped -*))
65 ((*- for line in output.traceback -*))
65 ((*- for line in output.traceback -*))
66 ((*- block traceback_line scoped -*))((*- endblock traceback_line -*))
66 ((*- block traceback_line scoped -*))((*- endblock traceback_line -*))
67 ((*- endfor -*))
67 ((*- endfor -*))
68 ((*- endblock pyerr -*))
68 ((*- endblock pyerr -*))
69 ((*- endif -*))
69 ((*- endif -*))
70 ((*- endblock output -*))
70 ((*- endblock output -*))
71 ((*- endfor -*))
71 ((*- endfor -*))
72 ((*- endblock outputs -*))
72 ((*- endblock outputs -*))
73 ((*- endblock output_group -*))
73 ((*- endblock output_group -*))
74 ((*- endif -*))
74 ((*- endif -*))
75 ((*- endblock codecell -*))
75 ((*- endblock codecell -*))
76 ((*- elif cell.cell_type in ['markdown'] -*))
76 ((*- elif cell.cell_type in ['markdown'] -*))
77 ((*- block markdowncell scoped-*))
77 ((*- block markdowncell scoped-*))
78 ((*- endblock markdowncell -*))
78 ((*- endblock markdowncell -*))
79 ((*- elif cell.cell_type in ['heading'] -*))
79 ((*- elif cell.cell_type in ['heading'] -*))
80 ((*- block headingcell scoped-*))
80 ((*- block headingcell scoped-*))
81 ((*- endblock headingcell -*))
81 ((*- endblock headingcell -*))
82 ((*- elif cell.cell_type in ['raw'] -*))
82 ((*- elif cell.cell_type in ['raw'] -*))
83 ((*- block rawcell scoped -*))
83 ((*- block rawcell scoped -*))
84 ((* if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype')) == resources.get('raw_mimetype') *))
84 ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
85 ((( cell.source )))
85 ((( cell.source )))
86 ((* endif *))
86 ((* endif *))
87 ((*- endblock rawcell -*))
87 ((*- endblock rawcell -*))
88 ((*- else -*))
88 ((*- else -*))
89 ((*- block unknowncell scoped-*))
89 ((*- block unknowncell scoped-*))
90 ((*- endblock unknowncell -*))
90 ((*- endblock unknowncell -*))
91 ((*- endif -*))
91 ((*- endif -*))
92 ((*- endblock any_cell -*))
92 ((*- endblock any_cell -*))
93 ((*- endfor -*))
93 ((*- endfor -*))
94 ((*- endfor -*))
94 ((*- endfor -*))
95 ((*- endblock body -*))
95 ((*- endblock body -*))
96
96
97 ((*- block footer -*))
97 ((*- block footer -*))
98 ((*- endblock footer -*))
98 ((*- endblock footer -*))
@@ -1,51 +1,51 b''
1 {%- extends 'null.tpl' -%}
1 {%- extends 'null.tpl' -%}
2
2
3
3
4 {% block in_prompt %}
4 {% block in_prompt %}
5 # In[{{ cell.prompt_number if cell.prompt_number else ' ' }}]:
5 # In[{{ cell.prompt_number if cell.prompt_number else ' ' }}]:
6 {% endblock in_prompt %}
6 {% endblock in_prompt %}
7
7
8 {% block output_prompt %}
8 {% block output_prompt %}
9 # Out[{{ cell.prompt_number }}]:
9 # Out[{{ cell.prompt_number }}]:
10 {% endblock output_prompt %}
10 {% endblock output_prompt %}
11
11
12 {% block input %}
12 {% block input %}
13 {{ cell.input | ipython2python }}
13 {{ cell.input | ipython2python }}
14 {% endblock input %}
14 {% endblock input %}
15
15
16 {# Those Two are for error displaying
16 {# Those Two are for error displaying
17 even if the first one seem to do nothing,
17 even if the first one seem to do nothing,
18 it introduces a new line
18 it introduces a new line
19 #}
19 #}
20 {% block pyerr %}
20 {% block pyerr %}
21 {{ super() }}
21 {{ super() }}
22 {% endblock pyerr %}
22 {% endblock pyerr %}
23
23
24 {% block traceback_line %}
24 {% block traceback_line %}
25 {{ line | indent | strip_ansi }}
25 {{ line | indent | strip_ansi }}
26 {% endblock traceback_line %}
26 {% endblock traceback_line %}
27 {# .... #}
27 {# .... #}
28
28
29 {% block pyout %}
29 {% block pyout %}
30 {{ output.text | indent | comment_lines }}
30 {{ output.text or '' | indent | comment_lines }}
31 {% endblock pyout %}
31 {% endblock pyout %}
32
32
33 {% block stream %}
33 {% block stream %}
34 {{ output.text | indent | comment_lines }}
34 {{ output.text | indent | comment_lines }}
35 {% endblock stream %}
35 {% endblock stream %}
36
36
37 {% block display_data scoped %}
37 {% block display_data scoped %}
38 # image file:
38 # image file:
39 {% endblock display_data %}
39 {% endblock display_data %}
40
40
41 {% block markdowncell scoped %}
41 {% block markdowncell scoped %}
42 {{ cell.source | comment_lines }}
42 {{ cell.source | comment_lines }}
43 {% endblock markdowncell %}
43 {% endblock markdowncell %}
44
44
45 {% block headingcell scoped %}
45 {% block headingcell scoped %}
46 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
46 {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }}
47 {% endblock headingcell %}
47 {% endblock headingcell %}
48
48
49 {% block unknowncell scoped %}
49 {% block unknowncell scoped %}
50 unknown type {{ cell.type }}
50 unknown type {{ cell.type }}
51 {% endblock unknowncell %} No newline at end of file
51 {% endblock unknowncell %}
@@ -1,94 +1,94 b''
1 {#
1 {#
2
2
3 DO NOT USE THIS AS A BASE,
3 DO NOT USE THIS AS A BASE,
4 IF YOU ARE COPY AND PASTING THIS FILE
4 IF YOU ARE COPY AND PASTING THIS FILE
5 YOU ARE PROBABLY DOING THINGS INCORRECTLY.
5 YOU ARE PROBABLY DOING THINGS INCORRECTLY.
6
6
7 Null template, does nothing except defining a basic structure
7 Null template, does nothing except defining a basic structure
8 To layout the different blocks of a notebook.
8 To layout the different blocks of a notebook.
9
9
10 Subtemplates can override blocks to define their custom representation.
10 Subtemplates can override blocks to define their custom representation.
11
11
12 If one of the block you do overwrite is not a leave block, consider
12 If one of the block you do overwrite is not a leave block, consider
13 calling super.
13 calling super.
14
14
15 {%- block nonLeaveBlock -%}
15 {%- block nonLeaveBlock -%}
16 #add stuff at beginning
16 #add stuff at beginning
17 {{ super() }}
17 {{ super() }}
18 #add stuff at end
18 #add stuff at end
19 {%- endblock nonLeaveBlock -%}
19 {%- endblock nonLeaveBlock -%}
20
20
21 consider calling super even if it is a leave block, we might insert more blocks later.
21 consider calling super even if it is a leave block, we might insert more blocks later.
22
22
23 #}
23 #}
24 {%- block header -%}
24 {%- block header -%}
25 {%- endblock header -%}
25 {%- endblock header -%}
26 {%- block body -%}
26 {%- block body -%}
27 {%- for worksheet in nb.worksheets -%}
27 {%- for worksheet in nb.worksheets -%}
28 {%- for cell in worksheet.cells -%}
28 {%- for cell in worksheet.cells -%}
29 {%- block any_cell scoped -%}
29 {%- block any_cell scoped -%}
30 {%- if cell.cell_type in ['code'] -%}
30 {%- if cell.cell_type in ['code'] -%}
31 {%- block codecell scoped -%}
31 {%- block codecell scoped -%}
32 {%- block input_group -%}
32 {%- block input_group -%}
33 {%- block in_prompt -%}{%- endblock in_prompt -%}
33 {%- block in_prompt -%}{%- endblock in_prompt -%}
34 {%- block input -%}{%- endblock input -%}
34 {%- block input -%}{%- endblock input -%}
35 {%- endblock input_group -%}
35 {%- endblock input_group -%}
36 {%- if cell.outputs -%}
36 {%- if cell.outputs -%}
37 {%- block output_group -%}
37 {%- block output_group -%}
38 {%- block output_prompt -%}{%- endblock output_prompt -%}
38 {%- block output_prompt -%}{%- endblock output_prompt -%}
39 {%- block outputs scoped -%}
39 {%- block outputs scoped -%}
40 {%- for output in cell.outputs -%}
40 {%- for output in cell.outputs -%}
41 {%- block output scoped -%}
41 {%- block output scoped -%}
42 {%- if output.output_type in ['pyout'] -%}
42 {%- if output.output_type in ['pyout'] -%}
43 {%- block pyout scoped -%}{%- endblock pyout -%}
43 {%- block pyout scoped -%}{%- endblock pyout -%}
44 {%- elif output.output_type in ['stream'] -%}
44 {%- elif output.output_type in ['stream'] -%}
45 {%- block stream scoped -%}
45 {%- block stream scoped -%}
46 {%- if output.stream in ['stdout'] -%}
46 {%- if output.stream in ['stdout'] -%}
47 {%- block stream_stdout scoped -%}
47 {%- block stream_stdout scoped -%}
48 {%- endblock stream_stdout -%}
48 {%- endblock stream_stdout -%}
49 {%- elif output.stream in ['stderr'] -%}
49 {%- elif output.stream in ['stderr'] -%}
50 {%- block stream_stderr scoped -%}
50 {%- block stream_stderr scoped -%}
51 {%- endblock stream_stderr -%}
51 {%- endblock stream_stderr -%}
52 {%- endif -%}
52 {%- endif -%}
53 {%- endblock stream -%}
53 {%- endblock stream -%}
54 {%- elif output.output_type in ['display_data'] -%}
54 {%- elif output.output_type in ['display_data'] -%}
55 {%- block display_data scoped -%}
55 {%- block display_data scoped -%}
56 {%- block data_priority scoped -%}
56 {%- block data_priority scoped -%}
57 {%- endblock data_priority -%}
57 {%- endblock data_priority -%}
58 {%- endblock display_data -%}
58 {%- endblock display_data -%}
59 {%- elif output.output_type in ['pyerr'] -%}
59 {%- elif output.output_type in ['pyerr'] -%}
60 {%- block pyerr scoped -%}
60 {%- block pyerr scoped -%}
61 {%- for line in output.traceback -%}
61 {%- for line in output.traceback -%}
62 {%- block traceback_line scoped -%}{%- endblock traceback_line -%}
62 {%- block traceback_line scoped -%}{%- endblock traceback_line -%}
63 {%- endfor -%}
63 {%- endfor -%}
64 {%- endblock pyerr -%}
64 {%- endblock pyerr -%}
65 {%- endif -%}
65 {%- endif -%}
66 {%- endblock output -%}
66 {%- endblock output -%}
67 {%- endfor -%}
67 {%- endfor -%}
68 {%- endblock outputs -%}
68 {%- endblock outputs -%}
69 {%- endblock output_group -%}
69 {%- endblock output_group -%}
70 {%- endif -%}
70 {%- endif -%}
71 {%- endblock codecell -%}
71 {%- endblock codecell -%}
72 {%- elif cell.cell_type in ['markdown'] -%}
72 {%- elif cell.cell_type in ['markdown'] -%}
73 {%- block markdowncell scoped-%}
73 {%- block markdowncell scoped-%}
74 {%- endblock markdowncell -%}
74 {%- endblock markdowncell -%}
75 {%- elif cell.cell_type in ['heading'] -%}
75 {%- elif cell.cell_type in ['heading'] -%}
76 {%- block headingcell scoped-%}
76 {%- block headingcell scoped-%}
77 {%- endblock headingcell -%}
77 {%- endblock headingcell -%}
78 {%- elif cell.cell_type in ['raw'] -%}
78 {%- elif cell.cell_type in ['raw'] -%}
79 {%- block rawcell scoped -%}
79 {%- block rawcell scoped -%}
80 {% if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype', '')).lower() in resources.get('raw_mimetypes', ['']) %}
80 {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
81 {{ cell.source }}
81 {{ cell.source }}
82 {% endif %}
82 {% endif %}
83 {%- endblock rawcell -%}
83 {%- endblock rawcell -%}
84 {%- else -%}
84 {%- else -%}
85 {%- block unknowncell scoped-%}
85 {%- block unknowncell scoped-%}
86 {%- endblock unknowncell -%}
86 {%- endblock unknowncell -%}
87 {%- endif -%}
87 {%- endif -%}
88 {%- endblock any_cell -%}
88 {%- endblock any_cell -%}
89 {%- endfor -%}
89 {%- endfor -%}
90 {%- endfor -%}
90 {%- endfor -%}
91 {%- endblock body -%}
91 {%- endblock body -%}
92
92
93 {%- block footer -%}
93 {%- block footer -%}
94 {%- endblock footer -%}
94 {%- endblock footer -%}
@@ -1,216 +1,217 b''
1 """The basic dict based notebook format.
1 """The basic dict based notebook format.
2
2
3 The Python representation of a notebook is a nested structure of
3 The Python representation of a notebook is a nested structure of
4 dictionary subclasses that support attribute access
4 dictionary subclasses that support attribute access
5 (IPython.utils.ipstruct.Struct). The functions in this module are merely
5 (IPython.utils.ipstruct.Struct). The functions in this module are merely
6 helpers to build the structs in the right form.
6 helpers to build the structs in the right form.
7
7
8 Authors:
8 Authors:
9
9
10 * Brian Granger
10 * Brian Granger
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import pprint
24 import pprint
25 import uuid
25 import uuid
26
26
27 from IPython.utils.ipstruct import Struct
27 from IPython.utils.ipstruct import Struct
28 from IPython.utils.py3compat import cast_unicode, unicode_type
28 from IPython.utils.py3compat import cast_unicode, unicode_type
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Code
31 # Code
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 # Change this when incrementing the nbformat version
34 # Change this when incrementing the nbformat version
35 nbformat = 3
35 nbformat = 3
36 nbformat_minor = 0
36 nbformat_minor = 0
37
37
38 class NotebookNode(Struct):
38 class NotebookNode(Struct):
39 pass
39 pass
40
40
41
41
42 def from_dict(d):
42 def from_dict(d):
43 if isinstance(d, dict):
43 if isinstance(d, dict):
44 newd = NotebookNode()
44 newd = NotebookNode()
45 for k,v in d.items():
45 for k,v in d.items():
46 newd[k] = from_dict(v)
46 newd[k] = from_dict(v)
47 return newd
47 return newd
48 elif isinstance(d, (tuple, list)):
48 elif isinstance(d, (tuple, list)):
49 return [from_dict(i) for i in d]
49 return [from_dict(i) for i in d]
50 else:
50 else:
51 return d
51 return d
52
52
53
53
54 def new_output(output_type=None, output_text=None, output_png=None,
54 def new_output(output_type=None, output_text=None, output_png=None,
55 output_html=None, output_svg=None, output_latex=None, output_json=None,
55 output_html=None, output_svg=None, output_latex=None, output_json=None,
56 output_javascript=None, output_jpeg=None, prompt_number=None,
56 output_javascript=None, output_jpeg=None, prompt_number=None,
57 ename=None, evalue=None, traceback=None, stream=None, metadata=None):
57 ename=None, evalue=None, traceback=None, stream=None, metadata=None):
58 """Create a new code cell with input and output"""
58 """Create a new output, to go in the ``cell.outputs`` list of a code cell.
59 """
59 output = NotebookNode()
60 output = NotebookNode()
60 if output_type is not None:
61 if output_type is not None:
61 output.output_type = unicode_type(output_type)
62 output.output_type = unicode_type(output_type)
62
63
63 if metadata is None:
64 if metadata is None:
64 metadata = {}
65 metadata = {}
65 if not isinstance(metadata, dict):
66 if not isinstance(metadata, dict):
66 raise TypeError("metadata must be dict")
67 raise TypeError("metadata must be dict")
67 output.metadata = metadata
68 output.metadata = metadata
68
69
69 if output_type != 'pyerr':
70 if output_type != 'pyerr':
70 if output_text is not None:
71 if output_text is not None:
71 output.text = cast_unicode(output_text)
72 output.text = cast_unicode(output_text)
72 if output_png is not None:
73 if output_png is not None:
73 output.png = cast_unicode(output_png)
74 output.png = cast_unicode(output_png)
74 if output_jpeg is not None:
75 if output_jpeg is not None:
75 output.jpeg = cast_unicode(output_jpeg)
76 output.jpeg = cast_unicode(output_jpeg)
76 if output_html is not None:
77 if output_html is not None:
77 output.html = cast_unicode(output_html)
78 output.html = cast_unicode(output_html)
78 if output_svg is not None:
79 if output_svg is not None:
79 output.svg = cast_unicode(output_svg)
80 output.svg = cast_unicode(output_svg)
80 if output_latex is not None:
81 if output_latex is not None:
81 output.latex = cast_unicode(output_latex)
82 output.latex = cast_unicode(output_latex)
82 if output_json is not None:
83 if output_json is not None:
83 output.json = cast_unicode(output_json)
84 output.json = cast_unicode(output_json)
84 if output_javascript is not None:
85 if output_javascript is not None:
85 output.javascript = cast_unicode(output_javascript)
86 output.javascript = cast_unicode(output_javascript)
86
87
87 if output_type == u'pyout':
88 if output_type == u'pyout':
88 if prompt_number is not None:
89 if prompt_number is not None:
89 output.prompt_number = int(prompt_number)
90 output.prompt_number = int(prompt_number)
90
91
91 if output_type == u'pyerr':
92 if output_type == u'pyerr':
92 if ename is not None:
93 if ename is not None:
93 output.ename = cast_unicode(ename)
94 output.ename = cast_unicode(ename)
94 if evalue is not None:
95 if evalue is not None:
95 output.evalue = cast_unicode(evalue)
96 output.evalue = cast_unicode(evalue)
96 if traceback is not None:
97 if traceback is not None:
97 output.traceback = [cast_unicode(frame) for frame in list(traceback)]
98 output.traceback = [cast_unicode(frame) for frame in list(traceback)]
98
99
99 if output_type == u'stream':
100 if output_type == u'stream':
100 output.stream = 'stdout' if stream is None else cast_unicode(stream)
101 output.stream = 'stdout' if stream is None else cast_unicode(stream)
101
102
102 return output
103 return output
103
104
104
105
105 def new_code_cell(input=None, prompt_number=None, outputs=None,
106 def new_code_cell(input=None, prompt_number=None, outputs=None,
106 language=u'python', collapsed=False, metadata=None):
107 language=u'python', collapsed=False, metadata=None):
107 """Create a new code cell with input and output"""
108 """Create a new code cell with input and output"""
108 cell = NotebookNode()
109 cell = NotebookNode()
109 cell.cell_type = u'code'
110 cell.cell_type = u'code'
110 if language is not None:
111 if language is not None:
111 cell.language = cast_unicode(language)
112 cell.language = cast_unicode(language)
112 if input is not None:
113 if input is not None:
113 cell.input = cast_unicode(input)
114 cell.input = cast_unicode(input)
114 if prompt_number is not None:
115 if prompt_number is not None:
115 cell.prompt_number = int(prompt_number)
116 cell.prompt_number = int(prompt_number)
116 if outputs is None:
117 if outputs is None:
117 cell.outputs = []
118 cell.outputs = []
118 else:
119 else:
119 cell.outputs = outputs
120 cell.outputs = outputs
120 if collapsed is not None:
121 if collapsed is not None:
121 cell.collapsed = bool(collapsed)
122 cell.collapsed = bool(collapsed)
122 cell.metadata = NotebookNode(metadata or {})
123 cell.metadata = NotebookNode(metadata or {})
123
124
124 return cell
125 return cell
125
126
126 def new_text_cell(cell_type, source=None, rendered=None, metadata=None):
127 def new_text_cell(cell_type, source=None, rendered=None, metadata=None):
127 """Create a new text cell."""
128 """Create a new text cell."""
128 cell = NotebookNode()
129 cell = NotebookNode()
129 # VERSIONHACK: plaintext -> raw
130 # VERSIONHACK: plaintext -> raw
130 # handle never-released plaintext name for raw cells
131 # handle never-released plaintext name for raw cells
131 if cell_type == 'plaintext':
132 if cell_type == 'plaintext':
132 cell_type = 'raw'
133 cell_type = 'raw'
133 if source is not None:
134 if source is not None:
134 cell.source = cast_unicode(source)
135 cell.source = cast_unicode(source)
135 if rendered is not None:
136 if rendered is not None:
136 cell.rendered = cast_unicode(rendered)
137 cell.rendered = cast_unicode(rendered)
137 cell.metadata = NotebookNode(metadata or {})
138 cell.metadata = NotebookNode(metadata or {})
138 cell.cell_type = cell_type
139 cell.cell_type = cell_type
139 return cell
140 return cell
140
141
141
142
142 def new_heading_cell(source=None, rendered=None, level=1, metadata=None):
143 def new_heading_cell(source=None, rendered=None, level=1, metadata=None):
143 """Create a new section cell with a given integer level."""
144 """Create a new section cell with a given integer level."""
144 cell = NotebookNode()
145 cell = NotebookNode()
145 cell.cell_type = u'heading'
146 cell.cell_type = u'heading'
146 if source is not None:
147 if source is not None:
147 cell.source = cast_unicode(source)
148 cell.source = cast_unicode(source)
148 if rendered is not None:
149 if rendered is not None:
149 cell.rendered = cast_unicode(rendered)
150 cell.rendered = cast_unicode(rendered)
150 cell.level = int(level)
151 cell.level = int(level)
151 cell.metadata = NotebookNode(metadata or {})
152 cell.metadata = NotebookNode(metadata or {})
152 return cell
153 return cell
153
154
154
155
155 def new_worksheet(name=None, cells=None, metadata=None):
156 def new_worksheet(name=None, cells=None, metadata=None):
156 """Create a worksheet by name with with a list of cells."""
157 """Create a worksheet by name with with a list of cells."""
157 ws = NotebookNode()
158 ws = NotebookNode()
158 if name is not None:
159 if name is not None:
159 ws.name = cast_unicode(name)
160 ws.name = cast_unicode(name)
160 if cells is None:
161 if cells is None:
161 ws.cells = []
162 ws.cells = []
162 else:
163 else:
163 ws.cells = list(cells)
164 ws.cells = list(cells)
164 ws.metadata = NotebookNode(metadata or {})
165 ws.metadata = NotebookNode(metadata or {})
165 return ws
166 return ws
166
167
167
168
168 def new_notebook(name=None, metadata=None, worksheets=None):
169 def new_notebook(name=None, metadata=None, worksheets=None):
169 """Create a notebook by name, id and a list of worksheets."""
170 """Create a notebook by name, id and a list of worksheets."""
170 nb = NotebookNode()
171 nb = NotebookNode()
171 nb.nbformat = nbformat
172 nb.nbformat = nbformat
172 nb.nbformat_minor = nbformat_minor
173 nb.nbformat_minor = nbformat_minor
173 if worksheets is None:
174 if worksheets is None:
174 nb.worksheets = []
175 nb.worksheets = []
175 else:
176 else:
176 nb.worksheets = list(worksheets)
177 nb.worksheets = list(worksheets)
177 if metadata is None:
178 if metadata is None:
178 nb.metadata = new_metadata()
179 nb.metadata = new_metadata()
179 else:
180 else:
180 nb.metadata = NotebookNode(metadata)
181 nb.metadata = NotebookNode(metadata)
181 if name is not None:
182 if name is not None:
182 nb.metadata.name = cast_unicode(name)
183 nb.metadata.name = cast_unicode(name)
183 return nb
184 return nb
184
185
185
186
186 def new_metadata(name=None, authors=None, license=None, created=None,
187 def new_metadata(name=None, authors=None, license=None, created=None,
187 modified=None, gistid=None):
188 modified=None, gistid=None):
188 """Create a new metadata node."""
189 """Create a new metadata node."""
189 metadata = NotebookNode()
190 metadata = NotebookNode()
190 if name is not None:
191 if name is not None:
191 metadata.name = cast_unicode(name)
192 metadata.name = cast_unicode(name)
192 if authors is not None:
193 if authors is not None:
193 metadata.authors = list(authors)
194 metadata.authors = list(authors)
194 if created is not None:
195 if created is not None:
195 metadata.created = cast_unicode(created)
196 metadata.created = cast_unicode(created)
196 if modified is not None:
197 if modified is not None:
197 metadata.modified = cast_unicode(modified)
198 metadata.modified = cast_unicode(modified)
198 if license is not None:
199 if license is not None:
199 metadata.license = cast_unicode(license)
200 metadata.license = cast_unicode(license)
200 if gistid is not None:
201 if gistid is not None:
201 metadata.gistid = cast_unicode(gistid)
202 metadata.gistid = cast_unicode(gistid)
202 return metadata
203 return metadata
203
204
204 def new_author(name=None, email=None, affiliation=None, url=None):
205 def new_author(name=None, email=None, affiliation=None, url=None):
205 """Create a new author."""
206 """Create a new author."""
206 author = NotebookNode()
207 author = NotebookNode()
207 if name is not None:
208 if name is not None:
208 author.name = cast_unicode(name)
209 author.name = cast_unicode(name)
209 if email is not None:
210 if email is not None:
210 author.email = cast_unicode(email)
211 author.email = cast_unicode(email)
211 if affiliation is not None:
212 if affiliation is not None:
212 author.affiliation = cast_unicode(affiliation)
213 author.affiliation = cast_unicode(affiliation)
213 if url is not None:
214 if url is not None:
214 author.url = cast_unicode(url)
215 author.url = cast_unicode(url)
215 return author
216 return author
216
217
General Comments 0
You need to be logged in to leave comments. Login now