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 | DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") |
|
5 | DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") | |
6 |
|
6 | |||
7 | del os |
|
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 | IPython.utils = (function (IPython) { |
|
13 | IPython.utils = (function (IPython) { | |
14 | "use strict"; |
|
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 | // Cross-browser RegEx Split |
|
45 | // Cross-browser RegEx Split |
@@ -224,7 +224,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||||
224 | StoreMagics, |
|
224 | StoreMagics, | |
225 | ] |
|
225 | ] | |
226 |
|
226 | |||
227 |
subcommands = |
|
227 | subcommands = dict( | |
228 | qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', |
|
228 | qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', | |
229 | """Launch the IPython Qt Console.""" |
|
229 | """Launch the IPython Qt Console.""" | |
230 | ), |
|
230 | ), | |
@@ -252,7 +252,11 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||||
252 | trust=('IPython.nbformat.sign.TrustNotebookApp', |
|
252 | trust=('IPython.nbformat.sign.TrustNotebookApp', | |
253 | "Sign notebooks to trust their potentially unsafe contents at load." |
|
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 | # *do* autocreate requested profile, but don't create the config file. |
|
261 | # *do* autocreate requested profile, but don't create the config file. | |
258 | auto_create=Bool(True) |
|
262 | auto_create=Bool(True) |
@@ -327,6 +327,8 b' else:' | |||||
327 | s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING) |
|
327 | s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING) | |
328 | super(MyStringIO, self).write(s) |
|
328 | super(MyStringIO, self).write(s) | |
329 |
|
329 | |||
|
330 | _re_type = type(re.compile(r'')) | |||
|
331 | ||||
330 | notprinted_msg = """Did not find {0!r} in printed output (on {1}): |
|
332 | notprinted_msg = """Did not find {0!r} in printed output (on {1}): | |
331 | ------- |
|
333 | ------- | |
332 | {2!s} |
|
334 | {2!s} | |
@@ -347,7 +349,7 b' class AssertPrints(object):' | |||||
347 | """ |
|
349 | """ | |
348 | def __init__(self, s, channel='stdout', suppress=True): |
|
350 | def __init__(self, s, channel='stdout', suppress=True): | |
349 | self.s = s |
|
351 | self.s = s | |
350 | if isinstance(self.s, py3compat.string_types): |
|
352 | if isinstance(self.s, (py3compat.string_types, _re_type)): | |
351 | self.s = [self.s] |
|
353 | self.s = [self.s] | |
352 | self.channel = channel |
|
354 | self.channel = channel | |
353 | self.suppress = suppress |
|
355 | self.suppress = suppress | |
@@ -366,7 +368,10 b' class AssertPrints(object):' | |||||
366 | setattr(sys, self.channel, self.orig_stream) |
|
368 | setattr(sys, self.channel, self.orig_stream) | |
367 | printed = self.buffer.getvalue() |
|
369 | printed = self.buffer.getvalue() | |
368 | for s in self.s: |
|
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 | return False |
|
375 | return False | |
371 |
|
376 | |||
372 | printed_msg = """Found {0!r} in printed output (on {1}): |
|
377 | printed_msg = """Found {0!r} in printed output (on {1}): | |
@@ -387,7 +392,10 b' class AssertNotPrints(AssertPrints):' | |||||
387 | setattr(sys, self.channel, self.orig_stream) |
|
392 | setattr(sys, self.channel, self.orig_stream) | |
388 | printed = self.buffer.getvalue() |
|
393 | printed = self.buffer.getvalue() | |
389 | for s in self.s: |
|
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 | return False |
|
399 | return False | |
392 |
|
400 | |||
393 | @contextmanager |
|
401 | @contextmanager |
General Comments 0
You need to be logged in to leave comments.
Login now