Show More
@@ -0,0 +1,268 b'' | |||
|
1 | # coding: utf-8 | |
|
2 | """Utilities for installing Javascript extensions for the notebook""" | |
|
3 | ||
|
4 | #----------------------------------------------------------------------------- | |
|
5 | # Copyright (C) 2014 The IPython Development Team | |
|
6 | # | |
|
7 | # Distributed under the terms of the BSD License. The full license is in | |
|
8 | # the file COPYING, distributed as part of this software. | |
|
9 | #----------------------------------------------------------------------------- | |
|
10 | ||
|
11 | from __future__ import print_function | |
|
12 | ||
|
13 | import os | |
|
14 | import shutil | |
|
15 | import tarfile | |
|
16 | import zipfile | |
|
17 | from os.path import basename, join as pjoin | |
|
18 | ||
|
19 | # Deferred imports | |
|
20 | try: | |
|
21 | from urllib.parse import urlparse # Py3 | |
|
22 | from urllib.request import urlretrieve | |
|
23 | except ImportError: | |
|
24 | from urlparse import urlparse | |
|
25 | from urllib import urlretrieve | |
|
26 | ||
|
27 | from IPython.utils.path import get_ipython_dir | |
|
28 | from IPython.utils.py3compat import string_types, cast_unicode_py2 | |
|
29 | from IPython.utils.tempdir import TemporaryDirectory | |
|
30 | ||
|
31 | ||
|
32 | def _should_copy(src, dest, verbose=1): | |
|
33 | """should a file be copied?""" | |
|
34 | if not os.path.exists(dest): | |
|
35 | return True | |
|
36 | if os.stat(dest).st_mtime < os.stat(src).st_mtime: | |
|
37 | if verbose >= 2: | |
|
38 | print("%s is out of date" % dest) | |
|
39 | return True | |
|
40 | if verbose >= 2: | |
|
41 | print("%s is up to date" % dest) | |
|
42 | return False | |
|
43 | ||
|
44 | ||
|
45 | def _maybe_copy(src, dest, verbose=1): | |
|
46 | """copy a file if it needs updating""" | |
|
47 | if _should_copy(src, dest, verbose): | |
|
48 | if verbose >= 1: | |
|
49 | print("copying %s -> %s" % (src, dest)) | |
|
50 | shutil.copy2(src, dest) | |
|
51 | ||
|
52 | ||
|
53 | def _safe_is_tarfile(path): | |
|
54 | """safe version of is_tarfile, return False on IOError""" | |
|
55 | try: | |
|
56 | return tarfile.is_tarfile(path) | |
|
57 | except IOError: | |
|
58 | return False | |
|
59 | ||
|
60 | ||
|
61 | def check_nbextension(files, ipython_dir=None): | |
|
62 | """Check whether nbextension files have been installed | |
|
63 | ||
|
64 | files should be a list of relative paths within nbextensions. | |
|
65 | ||
|
66 | Returns True if all files are found, False if any are missing. | |
|
67 | """ | |
|
68 | ipython_dir = ipython_dir or get_ipython_dir() | |
|
69 | nbext = pjoin(ipython_dir, u'nbextensions') | |
|
70 | # make sure nbextensions dir exists | |
|
71 | if not os.path.exists(nbext): | |
|
72 | return False | |
|
73 | ||
|
74 | if isinstance(files, string_types): | |
|
75 | # one file given, turn it into a list | |
|
76 | files = [files] | |
|
77 | ||
|
78 | return all(os.path.exists(pjoin(nbext, f)) for f in files) | |
|
79 | ||
|
80 | ||
|
81 | def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1): | |
|
82 | """Install a Javascript extension for the notebook | |
|
83 | ||
|
84 | Stages files and/or directories into IPYTHONDIR/nbextensions. | |
|
85 | By default, this compares modification time, and only stages files that need updating. | |
|
86 | If `overwrite` is specified, matching files are purged before proceeding. | |
|
87 | ||
|
88 | Parameters | |
|
89 | ---------- | |
|
90 | ||
|
91 | files : list(paths or URLs) | |
|
92 | One or more paths or URLs to existing files directories to install. | |
|
93 | These will be installed with their base name, so '/path/to/foo' | |
|
94 | will install to 'nbextensions/foo'. | |
|
95 | Archives (zip or tarballs) will be extracted into the nbextensions directory. | |
|
96 | overwrite : bool [default: False] | |
|
97 | If True, always install the files, regardless of what may already be installed. | |
|
98 | symlink : bool [default: False] | |
|
99 | If True, create a symlink in nbextensions, rather than copying files. | |
|
100 | Not allowed with URLs or archives. | |
|
101 | ipython_dir : str [optional] | |
|
102 | The path to an IPython directory, if the default value is not desired. | |
|
103 | get_ipython_dir() is used by default. | |
|
104 | verbose : int [default: 1] | |
|
105 | Set verbosity level. The default is 1, where file actions are printed. | |
|
106 | set verbose=2 for more output, or verbose=0 for silence. | |
|
107 | """ | |
|
108 | ||
|
109 | ipython_dir = ipython_dir or get_ipython_dir() | |
|
110 | nbext = pjoin(ipython_dir, u'nbextensions') | |
|
111 | # make sure nbextensions dir exists | |
|
112 | if not os.path.exists(nbext): | |
|
113 | os.makedirs(nbext) | |
|
114 | ||
|
115 | if isinstance(files, string_types): | |
|
116 | # one file given, turn it into a list | |
|
117 | files = [files] | |
|
118 | ||
|
119 | for path in map(cast_unicode_py2, files): | |
|
120 | ||
|
121 | if path.startswith(('https://', 'http://')): | |
|
122 | if symlink: | |
|
123 | raise ValueError("Cannot symlink from URLs") | |
|
124 | # Given a URL, download it | |
|
125 | with TemporaryDirectory() as td: | |
|
126 | filename = urlparse(path).path.split('/')[-1] | |
|
127 | local_path = os.path.join(td, filename) | |
|
128 | if verbose >= 1: | |
|
129 | print("downloading %s to %s" % (path, local_path)) | |
|
130 | urlretrieve(path, local_path) | |
|
131 | # now install from the local copy | |
|
132 | install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose) | |
|
133 | continue | |
|
134 | ||
|
135 | # handle archives | |
|
136 | archive = None | |
|
137 | if path.endswith('.zip'): | |
|
138 | archive = zipfile.ZipFile(path) | |
|
139 | elif _safe_is_tarfile(path): | |
|
140 | archive = tarfile.open(path) | |
|
141 | ||
|
142 | if archive: | |
|
143 | if symlink: | |
|
144 | raise ValueError("Cannot symlink from archives") | |
|
145 | if verbose >= 1: | |
|
146 | print("extracting %s to %s" % (path, nbext)) | |
|
147 | archive.extractall(nbext) | |
|
148 | archive.close() | |
|
149 | continue | |
|
150 | ||
|
151 | dest = pjoin(nbext, basename(path)) | |
|
152 | if overwrite and os.path.exists(dest): | |
|
153 | if verbose >= 1: | |
|
154 | print("removing %s" % dest) | |
|
155 | if os.path.isdir(dest): | |
|
156 | shutil.rmtree(dest) | |
|
157 | else: | |
|
158 | os.remove(dest) | |
|
159 | ||
|
160 | if symlink: | |
|
161 | path = os.path.abspath(path) | |
|
162 | if not os.path.exists(dest): | |
|
163 | if verbose >= 1: | |
|
164 | print("symlink %s -> %s" % (dest, path)) | |
|
165 | os.symlink(path, dest) | |
|
166 | continue | |
|
167 | ||
|
168 | if os.path.isdir(path): | |
|
169 | strip_prefix_len = len(path) - len(basename(path)) | |
|
170 | for parent, dirs, files in os.walk(path): | |
|
171 | dest_dir = pjoin(nbext, parent[strip_prefix_len:]) | |
|
172 | if not os.path.exists(dest_dir): | |
|
173 | if verbose >= 2: | |
|
174 | print("making directory %s" % dest_dir) | |
|
175 | os.makedirs(dest_dir) | |
|
176 | for file in files: | |
|
177 | src = pjoin(parent, file) | |
|
178 | # print("%r, %r" % (dest_dir, file)) | |
|
179 | dest = pjoin(dest_dir, file) | |
|
180 | _maybe_copy(src, dest, verbose) | |
|
181 | else: | |
|
182 | src = path | |
|
183 | _maybe_copy(src, dest, verbose) | |
|
184 | ||
|
185 | #---------------------------------------------------------------------- | |
|
186 | # install nbextension app | |
|
187 | #---------------------------------------------------------------------- | |
|
188 | ||
|
189 | from IPython.utils.traitlets import Bool, Enum | |
|
190 | from IPython.core.application import BaseIPythonApplication | |
|
191 | ||
|
192 | flags = { | |
|
193 | "overwrite" : ({ | |
|
194 | "NBExtensionApp" : { | |
|
195 | "overwrite" : True, | |
|
196 | }}, "Force overwrite of existing files" | |
|
197 | ), | |
|
198 | "debug" : ({ | |
|
199 | "NBExtensionApp" : { | |
|
200 | "verbose" : 2, | |
|
201 | }}, "Extra output" | |
|
202 | ), | |
|
203 | "quiet" : ({ | |
|
204 | "NBExtensionApp" : { | |
|
205 | "verbose" : 0, | |
|
206 | }}, "Minimal output" | |
|
207 | ), | |
|
208 | "symlink" : ({ | |
|
209 | "NBExtensionApp" : { | |
|
210 | "symlink" : True, | |
|
211 | }}, "Create symlinks instead of copying files" | |
|
212 | ), | |
|
213 | } | |
|
214 | flags['s'] = flags['symlink'] | |
|
215 | ||
|
216 | aliases = { | |
|
217 | "ipython-dir" : "NBExtensionApp.ipython_dir" | |
|
218 | } | |
|
219 | ||
|
220 | class NBExtensionApp(BaseIPythonApplication): | |
|
221 | """Entry point for installing notebook extensions""" | |
|
222 | ||
|
223 | description = """Install IPython notebook extensions | |
|
224 | ||
|
225 | Usage | |
|
226 | ||
|
227 | ipython install-nbextension file [more files, folders, archives or urls] | |
|
228 | ||
|
229 | This copies files and/or folders into the IPython nbextensions directory. | |
|
230 | If a URL is given, it will be downloaded. | |
|
231 | If an archive is given, it will be extracted into nbextensions. | |
|
232 | If the requested files are already up to date, no action is taken | |
|
233 | unless --overwrite is specified. | |
|
234 | """ | |
|
235 | ||
|
236 | examples = """ | |
|
237 | ipython install-nbextension /path/to/d3.js /path/to/myextension | |
|
238 | """ | |
|
239 | aliases = aliases | |
|
240 | flags = flags | |
|
241 | ||
|
242 | overwrite = Bool(False, config=True, help="Force overwrite of existing files") | |
|
243 | symlink = Bool(False, config=True, help="Create symlinks instead of copying files") | |
|
244 | verbose = Enum((0,1,2), default_value=1, config=True, | |
|
245 | help="Verbosity level" | |
|
246 | ) | |
|
247 | ||
|
248 | def install_extensions(self): | |
|
249 | install_nbextension(self.extra_args, | |
|
250 | overwrite=self.overwrite, | |
|
251 | symlink=self.symlink, | |
|
252 | verbose=self.verbose, | |
|
253 | ipython_dir=self.ipython_dir, | |
|
254 | ) | |
|
255 | ||
|
256 | def start(self): | |
|
257 | if not self.extra_args: | |
|
258 | nbext = pjoin(self.ipython_dir, u'nbextensions') | |
|
259 | print("Notebook extensions in %s:" % nbext) | |
|
260 | for ext in os.listdir(nbext): | |
|
261 | print(u" %s" % ext) | |
|
262 | else: | |
|
263 | self.install_extensions() | |
|
264 | ||
|
265 | ||
|
266 | if __name__ == '__main__': | |
|
267 | NBExtensionApp.launch_instance() | |
|
268 | No newline at end of file |
@@ -0,0 +1,272 b'' | |||
|
1 | # coding: utf-8 | |
|
2 | """Test installation of notebook extensions""" | |
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (C) 2014 The IPython Development Team | |
|
5 | # | |
|
6 | # Distributed under the terms of the BSD License. The full license is in | |
|
7 | # the file COPYING, distributed as part of this software. | |
|
8 | #----------------------------------------------------------------------------- | |
|
9 | ||
|
10 | #----------------------------------------------------------------------------- | |
|
11 | # Imports | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | ||
|
14 | import glob | |
|
15 | import os | |
|
16 | import re | |
|
17 | import tarfile | |
|
18 | import zipfile | |
|
19 | from io import BytesIO | |
|
20 | from os.path import basename, join as pjoin | |
|
21 | from unittest import TestCase | |
|
22 | ||
|
23 | import IPython.testing.tools as tt | |
|
24 | import IPython.testing.decorators as dec | |
|
25 | from IPython.utils import py3compat | |
|
26 | from IPython.utils.tempdir import TemporaryDirectory | |
|
27 | from IPython.html import nbextensions | |
|
28 | from IPython.html.nbextensions import install_nbextension, check_nbextension | |
|
29 | ||
|
30 | #----------------------------------------------------------------------------- | |
|
31 | # Test functions | |
|
32 | #----------------------------------------------------------------------------- | |
|
33 | ||
|
34 | def touch(file, mtime=None): | |
|
35 | """ensure a file exists, and set its modification time | |
|
36 | ||
|
37 | returns the modification time of the file | |
|
38 | """ | |
|
39 | open(file, 'a').close() | |
|
40 | # set explicit mtime | |
|
41 | if mtime: | |
|
42 | atime = os.stat(file).st_atime | |
|
43 | os.utime(file, (atime, mtime)) | |
|
44 | return os.stat(file).st_mtime | |
|
45 | ||
|
46 | ||
|
47 | class TestInstallNBExtension(TestCase): | |
|
48 | ||
|
49 | def tempdir(self): | |
|
50 | td = TemporaryDirectory() | |
|
51 | self.tempdirs.append(td) | |
|
52 | return py3compat.cast_unicode(td.name) | |
|
53 | ||
|
54 | def setUp(self): | |
|
55 | self.tempdirs = [] | |
|
56 | src = self.src = self.tempdir() | |
|
57 | self.files = files = [ | |
|
58 | pjoin(u'Ζile'), | |
|
59 | pjoin(u'βir', u'Ζile1'), | |
|
60 | pjoin(u'βir', u'βir2', u'Ζile2'), | |
|
61 | ] | |
|
62 | for file in files: | |
|
63 | fullpath = os.path.join(self.src, file) | |
|
64 | parent = os.path.dirname(fullpath) | |
|
65 | if not os.path.exists(parent): | |
|
66 | os.makedirs(parent) | |
|
67 | touch(fullpath) | |
|
68 | ||
|
69 | self.ipdir = self.tempdir() | |
|
70 | self.save_get_ipython_dir = nbextensions.get_ipython_dir | |
|
71 | nbextensions.get_ipython_dir = lambda : self.ipdir | |
|
72 | ||
|
73 | def tearDown(self): | |
|
74 | for td in self.tempdirs: | |
|
75 | td.cleanup() | |
|
76 | nbextensions.get_ipython_dir = self.save_get_ipython_dir | |
|
77 | ||
|
78 | def assert_path_exists(self, path): | |
|
79 | if not os.path.exists(path): | |
|
80 | do_exist = os.listdir(os.path.dirname(path)) | |
|
81 | self.fail(u"%s should exist (found %s)" % (path, do_exist)) | |
|
82 | ||
|
83 | def assert_not_path_exists(self, path): | |
|
84 | if os.path.exists(path): | |
|
85 | self.fail(u"%s should not exist" % path) | |
|
86 | ||
|
87 | def assert_installed(self, relative_path, ipdir=None): | |
|
88 | self.assert_path_exists( | |
|
89 | pjoin(ipdir or self.ipdir, u'nbextensions', relative_path) | |
|
90 | ) | |
|
91 | ||
|
92 | def assert_not_installed(self, relative_path, ipdir=None): | |
|
93 | self.assert_not_path_exists( | |
|
94 | pjoin(ipdir or self.ipdir, u'nbextensions', relative_path) | |
|
95 | ) | |
|
96 | ||
|
97 | def test_create_ipython_dir(self): | |
|
98 | """install_nbextension when ipython_dir doesn't exist""" | |
|
99 | with TemporaryDirectory() as td: | |
|
100 | ipdir = pjoin(td, u'ipython') | |
|
101 | install_nbextension(self.src, ipython_dir=ipdir) | |
|
102 | self.assert_path_exists(ipdir) | |
|
103 | for file in self.files: | |
|
104 | self.assert_installed( | |
|
105 | pjoin(basename(self.src), file), | |
|
106 | ipdir | |
|
107 | ) | |
|
108 | ||
|
109 | def test_create_nbextensions(self): | |
|
110 | with TemporaryDirectory() as ipdir: | |
|
111 | install_nbextension(self.src, ipython_dir=ipdir) | |
|
112 | self.assert_installed( | |
|
113 | pjoin(basename(self.src), u'Ζile'), | |
|
114 | ipdir | |
|
115 | ) | |
|
116 | ||
|
117 | def test_single_file(self): | |
|
118 | file = self.files[0] | |
|
119 | install_nbextension(pjoin(self.src, file)) | |
|
120 | self.assert_installed(file) | |
|
121 | ||
|
122 | def test_single_dir(self): | |
|
123 | d = u'βir' | |
|
124 | install_nbextension(pjoin(self.src, d)) | |
|
125 | self.assert_installed(self.files[-1]) | |
|
126 | ||
|
127 | def test_install_nbextension(self): | |
|
128 | install_nbextension(glob.glob(pjoin(self.src, '*'))) | |
|
129 | for file in self.files: | |
|
130 | self.assert_installed(file) | |
|
131 | ||
|
132 | def test_overwrite_file(self): | |
|
133 | with TemporaryDirectory() as d: | |
|
134 | fname = u'Ζ.js' | |
|
135 | src = pjoin(d, fname) | |
|
136 | with open(src, 'w') as f: | |
|
137 | f.write('first') | |
|
138 | mtime = touch(src) | |
|
139 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
140 | install_nbextension(src) | |
|
141 | with open(src, 'w') as f: | |
|
142 | f.write('overwrite') | |
|
143 | mtime = touch(src, mtime - 100) | |
|
144 | install_nbextension(src, overwrite=True) | |
|
145 | with open(dest) as f: | |
|
146 | self.assertEqual(f.read(), 'overwrite') | |
|
147 | ||
|
148 | def test_overwrite_dir(self): | |
|
149 | with TemporaryDirectory() as src: | |
|
150 | # src = py3compat.cast_unicode_py2(src) | |
|
151 | base = basename(src) | |
|
152 | fname = u'Ζ.js' | |
|
153 | touch(pjoin(src, fname)) | |
|
154 | install_nbextension(src) | |
|
155 | self.assert_installed(pjoin(base, fname)) | |
|
156 | os.remove(pjoin(src, fname)) | |
|
157 | fname2 = u'β.js' | |
|
158 | touch(pjoin(src, fname2)) | |
|
159 | install_nbextension(src, overwrite=True) | |
|
160 | self.assert_installed(pjoin(base, fname2)) | |
|
161 | self.assert_not_installed(pjoin(base, fname)) | |
|
162 | ||
|
163 | def test_update_file(self): | |
|
164 | with TemporaryDirectory() as d: | |
|
165 | fname = u'Ζ.js' | |
|
166 | src = pjoin(d, fname) | |
|
167 | with open(src, 'w') as f: | |
|
168 | f.write('first') | |
|
169 | mtime = touch(src) | |
|
170 | install_nbextension(src) | |
|
171 | self.assert_installed(fname) | |
|
172 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
173 | old_mtime = os.stat(dest).st_mtime | |
|
174 | with open(src, 'w') as f: | |
|
175 | f.write('overwrite') | |
|
176 | touch(src, mtime + 10) | |
|
177 | install_nbextension(src) | |
|
178 | with open(dest) as f: | |
|
179 | self.assertEqual(f.read(), 'overwrite') | |
|
180 | ||
|
181 | def test_skip_old_file(self): | |
|
182 | with TemporaryDirectory() as d: | |
|
183 | fname = u'Ζ.js' | |
|
184 | src = pjoin(d, fname) | |
|
185 | mtime = touch(src) | |
|
186 | install_nbextension(src) | |
|
187 | self.assert_installed(fname) | |
|
188 | dest = pjoin(self.ipdir, u'nbextensions', fname) | |
|
189 | old_mtime = os.stat(dest).st_mtime | |
|
190 | ||
|
191 | mtime = touch(src, mtime - 100) | |
|
192 | install_nbextension(src) | |
|
193 | new_mtime = os.stat(dest).st_mtime | |
|
194 | self.assertEqual(new_mtime, old_mtime) | |
|
195 | ||
|
196 | def test_quiet(self): | |
|
197 | with tt.AssertNotPrints(re.compile(r'.+')): | |
|
198 | install_nbextension(self.src, verbose=0) | |
|
199 | ||
|
200 | def test_install_zip(self): | |
|
201 | path = pjoin(self.src, "myjsext.zip") | |
|
202 | with zipfile.ZipFile(path, 'w') as f: | |
|
203 | f.writestr("a.js", b"b();") | |
|
204 | f.writestr("foo/a.js", b"foo();") | |
|
205 | install_nbextension(path) | |
|
206 | self.assert_installed("a.js") | |
|
207 | self.assert_installed(pjoin("foo", "a.js")) | |
|
208 | ||
|
209 | def test_install_tar(self): | |
|
210 | def _add_file(f, fname, buf): | |
|
211 | info = tarfile.TarInfo(fname) | |
|
212 | info.size = len(buf) | |
|
213 | f.addfile(info, BytesIO(buf)) | |
|
214 | ||
|
215 | for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")): | |
|
216 | path = pjoin(self.src, "myjsext" + ext) | |
|
217 | with tarfile.open(path, 'w') as f: | |
|
218 | _add_file(f, "b%i.js" % i, b"b();") | |
|
219 | _add_file(f, "foo/b%i.js" % i, b"foo();") | |
|
220 | install_nbextension(path) | |
|
221 | self.assert_installed("b%i.js" % i) | |
|
222 | self.assert_installed(pjoin("foo", "b%i.js" % i)) | |
|
223 | ||
|
224 | def test_install_url(self): | |
|
225 | def fake_urlretrieve(url, dest): | |
|
226 | touch(dest) | |
|
227 | save_urlretrieve = nbextensions.urlretrieve | |
|
228 | nbextensions.urlretrieve = fake_urlretrieve | |
|
229 | try: | |
|
230 | install_nbextension("http://example.com/path/to/foo.js") | |
|
231 | self.assert_installed("foo.js") | |
|
232 | install_nbextension("https://example.com/path/to/another/bar.js") | |
|
233 | self.assert_installed("bar.js") | |
|
234 | finally: | |
|
235 | nbextensions.urlretrieve = save_urlretrieve | |
|
236 | ||
|
237 | def test_check_nbextension(self): | |
|
238 | with TemporaryDirectory() as d: | |
|
239 | f = u'Ζ.js' | |
|
240 | src = pjoin(d, f) | |
|
241 | touch(src) | |
|
242 | install_nbextension(src) | |
|
243 | ||
|
244 | assert check_nbextension(f, self.ipdir) | |
|
245 | assert check_nbextension([f], self.ipdir) | |
|
246 | assert not check_nbextension([f, pjoin('dne', f)], self.ipdir) | |
|
247 | ||
|
248 | @dec.skip_win32 | |
|
249 | def test_install_symlink(self): | |
|
250 | with TemporaryDirectory() as d: | |
|
251 | f = u'Ζ.js' | |
|
252 | src = pjoin(d, f) | |
|
253 | touch(src) | |
|
254 | install_nbextension(src, symlink=True) | |
|
255 | dest = pjoin(self.ipdir, u'nbextensions', f) | |
|
256 | assert os.path.islink(dest) | |
|
257 | link = os.readlink(dest) | |
|
258 | self.assertEqual(link, src) | |
|
259 | ||
|
260 | def test_install_symlink_bad(self): | |
|
261 | with self.assertRaises(ValueError): | |
|
262 | install_nbextension("http://example.com/foo.js", symlink=True) | |
|
263 | ||
|
264 | with TemporaryDirectory() as d: | |
|
265 | zf = u'Ζ.zip' | |
|
266 | zsrc = pjoin(d, zf) | |
|
267 | with zipfile.ZipFile(zsrc, 'w') as z: | |
|
268 | z.writestr("a.js", b"b();") | |
|
269 | ||
|
270 | with self.assertRaises(ValueError): | |
|
271 | install_nbextension(zsrc, symlink=True) | |
|
272 |
@@ -5,3 +5,5 b' import os' | |||
|
5 | 5 | DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") |
|
6 | 6 | |
|
7 | 7 | del os |
|
8 | ||
|
9 | from .nbextensions import install_nbextension No newline at end of file |
@@ -12,6 +12,34 b" IPython.namespace('IPython.utils');" | |||
|
12 | 12 | |
|
13 | 13 | IPython.utils = (function (IPython) { |
|
14 | 14 | "use strict"; |
|
15 | ||
|
16 | IPython.load_extensions = function () { | |
|
17 | // load one or more IPython notebook extensions with requirejs | |
|
18 | ||
|
19 | var extensions = []; | |
|
20 | var extension_names = arguments; | |
|
21 | for (var i = 0; i < extension_names.length; i++) { | |
|
22 | extensions.push("nbextensions/" + arguments[i]); | |
|
23 | } | |
|
24 | ||
|
25 | require(extensions, | |
|
26 | function () { | |
|
27 | for (var i = 0; i < arguments.length; i++) { | |
|
28 | var ext = arguments[i]; | |
|
29 | var ext_name = extension_names[i]; | |
|
30 | // success callback | |
|
31 | console.log("Loaded extension: " + ext_name); | |
|
32 | if (ext && ext.load_ipython_extension !== undefined) { | |
|
33 | ext.load_ipython_extension(); | |
|
34 | } | |
|
35 | } | |
|
36 | }, | |
|
37 | function (err) { | |
|
38 | // failure callback | |
|
39 | console.log("Failed to load extension(s):", err.requireModules, err); | |
|
40 | } | |
|
41 | ); | |
|
42 | }; | |
|
15 | 43 | |
|
16 | 44 | //============================================================================ |
|
17 | 45 | // Cross-browser RegEx Split |
@@ -224,7 +224,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||
|
224 | 224 | StoreMagics, |
|
225 | 225 | ] |
|
226 | 226 | |
|
227 |
subcommands = |
|
|
227 | subcommands = dict( | |
|
228 | 228 | qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', |
|
229 | 229 | """Launch the IPython Qt Console.""" |
|
230 | 230 | ), |
@@ -252,7 +252,11 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||
|
252 | 252 | trust=('IPython.nbformat.sign.TrustNotebookApp', |
|
253 | 253 | "Sign notebooks to trust their potentially unsafe contents at load." |
|
254 | 254 | ), |
|
255 |
) |
|
|
255 | ) | |
|
256 | subcommands['install-nbextension'] = ( | |
|
257 | "IPython.html.nbextensions.NBExtensionApp", | |
|
258 | "Install IPython notebook extension files" | |
|
259 | ) | |
|
256 | 260 | |
|
257 | 261 | # *do* autocreate requested profile, but don't create the config file. |
|
258 | 262 | auto_create=Bool(True) |
@@ -327,6 +327,8 b' else:' | |||
|
327 | 327 | s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING) |
|
328 | 328 | super(MyStringIO, self).write(s) |
|
329 | 329 | |
|
330 | _re_type = type(re.compile(r'')) | |
|
331 | ||
|
330 | 332 | notprinted_msg = """Did not find {0!r} in printed output (on {1}): |
|
331 | 333 | ------- |
|
332 | 334 | {2!s} |
@@ -347,7 +349,7 b' class AssertPrints(object):' | |||
|
347 | 349 | """ |
|
348 | 350 | def __init__(self, s, channel='stdout', suppress=True): |
|
349 | 351 | self.s = s |
|
350 | if isinstance(self.s, py3compat.string_types): | |
|
352 | if isinstance(self.s, (py3compat.string_types, _re_type)): | |
|
351 | 353 | self.s = [self.s] |
|
352 | 354 | self.channel = channel |
|
353 | 355 | self.suppress = suppress |
@@ -366,7 +368,10 b' class AssertPrints(object):' | |||
|
366 | 368 | setattr(sys, self.channel, self.orig_stream) |
|
367 | 369 | printed = self.buffer.getvalue() |
|
368 | 370 | for s in self.s: |
|
369 | assert s in printed, notprinted_msg.format(s, self.channel, printed) | |
|
371 | if isinstance(s, _re_type): | |
|
372 | assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed) | |
|
373 | else: | |
|
374 | assert s in printed, notprinted_msg.format(s, self.channel, printed) | |
|
370 | 375 | return False |
|
371 | 376 | |
|
372 | 377 | printed_msg = """Found {0!r} in printed output (on {1}): |
@@ -387,7 +392,10 b' class AssertNotPrints(AssertPrints):' | |||
|
387 | 392 | setattr(sys, self.channel, self.orig_stream) |
|
388 | 393 | printed = self.buffer.getvalue() |
|
389 | 394 | for s in self.s: |
|
390 | assert s not in printed, printed_msg.format(s, self.channel, printed) | |
|
395 | if isinstance(s, _re_type): | |
|
396 | assert not s.search(printed), printed_msg.format(s.pattern, self.channel, printed) | |
|
397 | else: | |
|
398 | assert s not in printed, printed_msg.format(s, self.channel, printed) | |
|
391 | 399 | return False |
|
392 | 400 | |
|
393 | 401 | @contextmanager |
General Comments 0
You need to be logged in to leave comments.
Login now