##// END OF EJS Templates
Merge pull request #7609 from jasongrout/install-single-nbextension...
Thomas Kluyver -
r20244:a346be78 merge
parent child Browse files
Show More
@@ -1,358 +1,350 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Utilities for installing Javascript extensions for the notebook"""
2 """Utilities for installing Javascript extensions for the notebook"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import os
9 import os
10 import shutil
10 import shutil
11 import sys
11 import sys
12 import tarfile
12 import tarfile
13 import zipfile
13 import zipfile
14 import uuid
14 import uuid
15 from os.path import basename, join as pjoin
15 from os.path import basename, join as pjoin
16
16
17 # Deferred imports
17 # Deferred imports
18 try:
18 try:
19 from urllib.parse import urlparse # Py3
19 from urllib.parse import urlparse # Py3
20 from urllib.request import urlretrieve
20 from urllib.request import urlretrieve
21 except ImportError:
21 except ImportError:
22 from urlparse import urlparse
22 from urlparse import urlparse
23 from urllib import urlretrieve
23 from urllib import urlretrieve
24
24
25 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
25 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
26 from IPython.utils.py3compat import string_types, cast_unicode_py2
26 from IPython.utils.py3compat import string_types, cast_unicode_py2
27 from IPython.utils.tempdir import TemporaryDirectory
27 from IPython.utils.tempdir import TemporaryDirectory
28
28
29 class ArgumentConflict(ValueError):
29 class ArgumentConflict(ValueError):
30 pass
30 pass
31
31
32 # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely)
32 # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely)
33 SYSTEM_NBEXTENSIONS_DIRS = []
33 SYSTEM_NBEXTENSIONS_DIRS = []
34
34
35 if os.name == 'nt':
35 if os.name == 'nt':
36 programdata = os.environ.get('PROGRAMDATA', None)
36 programdata = os.environ.get('PROGRAMDATA', None)
37 if programdata: # PROGRAMDATA is not defined by default on XP.
37 if programdata: # PROGRAMDATA is not defined by default on XP.
38 SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')]
38 SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')]
39 prefixes = []
39 prefixes = []
40 else:
40 else:
41 prefixes = [os.path.sep + pjoin('usr', 'local'), os.path.sep + 'usr']
41 prefixes = [os.path.sep + pjoin('usr', 'local'), os.path.sep + 'usr']
42
42
43 # add sys.prefix at the front
43 # add sys.prefix at the front
44 if sys.prefix not in prefixes:
44 if sys.prefix not in prefixes:
45 prefixes.insert(0, sys.prefix)
45 prefixes.insert(0, sys.prefix)
46
46
47 for prefix in prefixes:
47 for prefix in prefixes:
48 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
48 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
49 if nbext not in SYSTEM_NBEXTENSIONS_DIRS:
49 if nbext not in SYSTEM_NBEXTENSIONS_DIRS:
50 SYSTEM_NBEXTENSIONS_DIRS.append(nbext)
50 SYSTEM_NBEXTENSIONS_DIRS.append(nbext)
51
51
52 if os.name == 'nt':
52 if os.name == 'nt':
53 # PROGRAMDATA
53 # PROGRAMDATA
54 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1]
54 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1]
55 else:
55 else:
56 # /usr/local
56 # /usr/local
57 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2]
57 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2]
58
58
59
59
60 def _should_copy(src, dest, verbose=1):
60 def _should_copy(src, dest, verbose=1):
61 """should a file be copied?"""
61 """should a file be copied?"""
62 if not os.path.exists(dest):
62 if not os.path.exists(dest):
63 return True
63 return True
64 if os.stat(src).st_mtime - os.stat(dest).st_mtime > 1e-6:
64 if os.stat(src).st_mtime - os.stat(dest).st_mtime > 1e-6:
65 # we add a fudge factor to work around a bug in python 2.x
65 # we add a fudge factor to work around a bug in python 2.x
66 # that was fixed in python 3.x: http://bugs.python.org/issue12904
66 # that was fixed in python 3.x: http://bugs.python.org/issue12904
67 if verbose >= 2:
67 if verbose >= 2:
68 print("%s is out of date" % dest)
68 print("%s is out of date" % dest)
69 return True
69 return True
70 if verbose >= 2:
70 if verbose >= 2:
71 print("%s is up to date" % dest)
71 print("%s is up to date" % dest)
72 return False
72 return False
73
73
74
74
75 def _maybe_copy(src, dest, verbose=1):
75 def _maybe_copy(src, dest, verbose=1):
76 """copy a file if it needs updating"""
76 """copy a file if it needs updating"""
77 if _should_copy(src, dest, verbose):
77 if _should_copy(src, dest, verbose):
78 if verbose >= 1:
78 if verbose >= 1:
79 print("copying %s -> %s" % (src, dest))
79 print("copying %s -> %s" % (src, dest))
80 shutil.copy2(src, dest)
80 shutil.copy2(src, dest)
81
81
82 def _safe_is_tarfile(path):
82 def _safe_is_tarfile(path):
83 """safe version of is_tarfile, return False on IOError"""
83 """safe version of is_tarfile, return False on IOError"""
84 try:
84 try:
85 return tarfile.is_tarfile(path)
85 return tarfile.is_tarfile(path)
86 except IOError:
86 except IOError:
87 return False
87 return False
88
88
89
89
90 def _get_nbext_dir(nbextensions_dir=None, user=False, prefix=None):
90 def _get_nbext_dir(nbextensions_dir=None, user=False, prefix=None):
91 """Return the nbextension directory specified"""
91 """Return the nbextension directory specified"""
92 if sum(map(bool, [user, prefix, nbextensions_dir])) > 1:
92 if sum(map(bool, [user, prefix, nbextensions_dir])) > 1:
93 raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.")
93 raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.")
94 if user:
94 if user:
95 nbext = pjoin(get_ipython_dir(), u'nbextensions')
95 nbext = pjoin(get_ipython_dir(), u'nbextensions')
96 else:
96 else:
97 if prefix:
97 if prefix:
98 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
98 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
99 elif nbextensions_dir:
99 elif nbextensions_dir:
100 nbext = nbextensions_dir
100 nbext = nbextensions_dir
101 else:
101 else:
102 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
102 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
103 return nbext
103 return nbext
104
104
105
105
106 def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None):
106 def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None):
107 """Check whether nbextension files have been installed
107 """Check whether nbextension files have been installed
108
108
109 Returns True if all files are found, False if any are missing.
109 Returns True if all files are found, False if any are missing.
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113
113
114 files : list(paths)
114 files : list(paths)
115 a list of relative paths within nbextensions.
115 a list of relative paths within nbextensions.
116 user : bool [default: False]
116 user : bool [default: False]
117 Whether to check the user's .ipython/nbextensions directory.
117 Whether to check the user's .ipython/nbextensions directory.
118 Otherwise check a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
118 Otherwise check a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
119 prefix : str [optional]
119 prefix : str [optional]
120 Specify install prefix, if it should differ from default (e.g. /usr/local).
120 Specify install prefix, if it should differ from default (e.g. /usr/local).
121 Will check prefix/share/jupyter/nbextensions
121 Will check prefix/share/jupyter/nbextensions
122 nbextensions_dir : str [optional]
122 nbextensions_dir : str [optional]
123 Specify absolute path of nbextensions directory explicitly.
123 Specify absolute path of nbextensions directory explicitly.
124 """
124 """
125 nbext = _get_nbext_dir(nbextensions_dir, user, prefix)
125 nbext = _get_nbext_dir(nbextensions_dir, user, prefix)
126 # make sure nbextensions dir exists
126 # make sure nbextensions dir exists
127 if not os.path.exists(nbext):
127 if not os.path.exists(nbext):
128 return False
128 return False
129
129
130 if isinstance(files, string_types):
130 if isinstance(files, string_types):
131 # one file given, turn it into a list
131 # one file given, turn it into a list
132 files = [files]
132 files = [files]
133
133
134 return all(os.path.exists(pjoin(nbext, f)) for f in files)
134 return all(os.path.exists(pjoin(nbext, f)) for f in files)
135
135
136
136
137 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1):
137 def install_nbextension(path, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, destination=None, verbose=1):
138 """Install a Javascript extension for the notebook
138 """Install a Javascript extension for the notebook
139
139
140 Stages files and/or directories into the nbextensions directory.
140 Stages files and/or directories into the nbextensions directory.
141 By default, this compares modification time, and only stages files that need updating.
141 By default, this compares modification time, and only stages files that need updating.
142 If `overwrite` is specified, matching files are purged before proceeding.
142 If `overwrite` is specified, matching files are purged before proceeding.
143
143
144 Parameters
144 Parameters
145 ----------
145 ----------
146
146
147 files : list(paths or URLs) or dict(install_name: path or URL)
147 path : path to file, directory, zip or tarball archive, or URL to install
148 One or more paths or URLs to existing files directories to install.
148 By default, the file will be installed with its base name, so '/path/to/foo'
149 If given as a list, these will be installed with their base name, so '/path/to/foo'
149 will install to 'nbextensions/foo'. See the destination argument below to change this.
150 will install to 'nbextensions/foo'. If given as a dict, such as {'bar': '/path/to/foo'},
151 then '/path/to/foo' will install to 'nbextensions/bar'.
152 Archives (zip or tarballs) will be extracted into the nbextensions directory.
150 Archives (zip or tarballs) will be extracted into the nbextensions directory.
153 overwrite : bool [default: False]
151 overwrite : bool [default: False]
154 If True, always install the files, regardless of what may already be installed.
152 If True, always install the files, regardless of what may already be installed.
155 symlink : bool [default: False]
153 symlink : bool [default: False]
156 If True, create a symlink in nbextensions, rather than copying files.
154 If True, create a symlink in nbextensions, rather than copying files.
157 Not allowed with URLs or archives. Windows support for symlinks requires
155 Not allowed with URLs or archives. Windows support for symlinks requires
158 Vista or above, Python 3, and a permission bit which only admin users
156 Vista or above, Python 3, and a permission bit which only admin users
159 have by default, so don't rely on it.
157 have by default, so don't rely on it.
160 user : bool [default: False]
158 user : bool [default: False]
161 Whether to install to the user's .ipython/nbextensions directory.
159 Whether to install to the user's .ipython/nbextensions directory.
162 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
160 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
163 prefix : str [optional]
161 prefix : str [optional]
164 Specify install prefix, if it should differ from default (e.g. /usr/local).
162 Specify install prefix, if it should differ from default (e.g. /usr/local).
165 Will install to prefix/share/jupyter/nbextensions
163 Will install to prefix/share/jupyter/nbextensions
166 nbextensions_dir : str [optional]
164 nbextensions_dir : str [optional]
167 Specify absolute path of nbextensions directory explicitly.
165 Specify absolute path of nbextensions directory explicitly.
166 destination : str [optional]
167 name the nbextension is installed to. For example, if destination is 'foo', then
168 the source file will be installed to 'nbextensions/foo', regardless of the source name.
169 This cannot be specified if an archive is given as the source.
168 verbose : int [default: 1]
170 verbose : int [default: 1]
169 Set verbosity level. The default is 1, where file actions are printed.
171 Set verbosity level. The default is 1, where file actions are printed.
170 set verbose=2 for more output, or verbose=0 for silence.
172 set verbose=2 for more output, or verbose=0 for silence.
171 """
173 """
172 nbext = _get_nbext_dir(nbextensions_dir, user, prefix)
174 nbext = _get_nbext_dir(nbextensions_dir, user, prefix)
173 # make sure nbextensions dir exists
175 # make sure nbextensions dir exists
174 ensure_dir_exists(nbext)
176 ensure_dir_exists(nbext)
175
177
176 if isinstance(files, string_types):
178 if isinstance(path, (list, tuple)):
177 # one file given, turn it into a list
179 raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions")
178 files = [files]
179 if isinstance(files, (list,tuple)):
180 # list given, turn into dict
181 _files = {}
182 for path in map(cast_unicode_py2, files):
183 if path.startswith(('https://', 'http://')):
184 destination = urlparse(path).path.split('/')[-1]
185 elif path.endswith('.zip') or _safe_is_tarfile(path):
186 destination = str(uuid.uuid4()) # ignored for archives
187 else:
188 destination = basename(path)
189 _files[destination] = path
190 files = _files
191
180
192 for dest_basename,path in (map(cast_unicode_py2, item) for item in files.items()):
181 path = cast_unicode_py2(path)
193
182
194 if path.startswith(('https://', 'http://')):
183 if path.startswith(('https://', 'http://')):
195 if symlink:
184 if symlink:
196 raise ValueError("Cannot symlink from URLs")
185 raise ValueError("Cannot symlink from URLs")
197 # Given a URL, download it
186 # Given a URL, download it
198 with TemporaryDirectory() as td:
187 with TemporaryDirectory() as td:
199 filename = urlparse(path).path.split('/')[-1]
188 filename = urlparse(path).path.split('/')[-1]
200 local_path = os.path.join(td, filename)
189 local_path = os.path.join(td, filename)
201 if verbose >= 1:
190 if verbose >= 1:
202 print("downloading %s to %s" % (path, local_path))
191 print("downloading %s to %s" % (path, local_path))
203 urlretrieve(path, local_path)
192 urlretrieve(path, local_path)
204 # now install from the local copy
193 # now install from the local copy
205 install_nbextension({dest_basename: local_path}, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose)
194 install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, destination=destination, verbose=verbose)
206 continue
195 elif path.endswith('.zip') or _safe_is_tarfile(path):
207
196 if symlink:
208 # handle archives
197 raise ValueError("Cannot symlink from archives")
209 archive = None
198 if destination:
199 raise ValueError("Cannot give destination for archives")
200 if verbose >= 1:
201 print("extracting %s to %s" % (path, nbext))
202
210 if path.endswith('.zip'):
203 if path.endswith('.zip'):
211 archive = zipfile.ZipFile(path)
204 archive = zipfile.ZipFile(path)
212 elif _safe_is_tarfile(path):
205 elif _safe_is_tarfile(path):
213 archive = tarfile.open(path)
206 archive = tarfile.open(path)
214
207 archive.extractall(nbext)
215 if archive:
208 archive.close()
216 if symlink:
209 else:
217 raise ValueError("Cannot symlink from archives")
210 if not destination:
218 if verbose >= 1:
211 destination = basename(path)
219 print("extracting %s to %s" % (path, nbext))
212 destination = cast_unicode_py2(destination)
220 archive.extractall(nbext)
213 full_dest = pjoin(nbext, destination)
221 archive.close()
214 if overwrite and os.path.exists(full_dest):
222 continue
223
224 dest = pjoin(nbext, dest_basename)
225 if overwrite and os.path.exists(dest):
226 if verbose >= 1:
215 if verbose >= 1:
227 print("removing %s" % dest)
216 print("removing %s" % full_dest)
228 if os.path.isdir(dest) and not os.path.islink(dest):
217 if os.path.isdir(full_dest) and not os.path.islink(full_dest):
229 shutil.rmtree(dest)
218 shutil.rmtree(full_dest)
230 else:
219 else:
231 os.remove(dest)
220 os.remove(full_dest)
232
221
233 if symlink:
222 if symlink:
234 path = os.path.abspath(path)
223 path = os.path.abspath(path)
235 if not os.path.exists(dest):
224 if not os.path.exists(full_dest):
236 if verbose >= 1:
225 if verbose >= 1:
237 print("symlink %s -> %s" % (dest, path))
226 print("symlink %s -> %s" % (full_dest, path))
238 os.symlink(path, dest)
227 os.symlink(path, full_dest)
239 continue
228 elif os.path.isdir(path):
240
241 if os.path.isdir(path):
242 path = pjoin(os.path.abspath(path), '') # end in path separator
229 path = pjoin(os.path.abspath(path), '') # end in path separator
243 for parent, dirs, files in os.walk(path):
230 for parent, dirs, files in os.walk(path):
244 dest_dir = pjoin(dest, parent[len(path):])
231 dest_dir = pjoin(full_dest, parent[len(path):])
245 if not os.path.exists(dest_dir):
232 if not os.path.exists(dest_dir):
246 if verbose >= 2:
233 if verbose >= 2:
247 print("making directory %s" % dest_dir)
234 print("making directory %s" % dest_dir)
248 os.makedirs(dest_dir)
235 os.makedirs(dest_dir)
249 for file in files:
236 for file in files:
250 src = pjoin(parent, file)
237 src = pjoin(parent, file)
251 # print("%r, %r" % (dest_dir, file))
238 # print("%r, %r" % (dest_dir, file))
252 dest_file = pjoin(dest_dir, file)
239 dest_file = pjoin(dest_dir, file)
253 _maybe_copy(src, dest_file, verbose)
240 _maybe_copy(src, dest_file, verbose)
254 else:
241 else:
255 src = path
242 src = path
256 _maybe_copy(src, dest, verbose)
243 _maybe_copy(src, full_dest, verbose)
257
244
258 #----------------------------------------------------------------------
245 #----------------------------------------------------------------------
259 # install nbextension app
246 # install nbextension app
260 #----------------------------------------------------------------------
247 #----------------------------------------------------------------------
261
248
262 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
249 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
263 from IPython.core.application import BaseIPythonApplication
250 from IPython.core.application import BaseIPythonApplication
264
251
265 flags = {
252 flags = {
266 "overwrite" : ({
253 "overwrite" : ({
267 "NBExtensionApp" : {
254 "NBExtensionApp" : {
268 "overwrite" : True,
255 "overwrite" : True,
269 }}, "Force overwrite of existing files"
256 }}, "Force overwrite of existing files"
270 ),
257 ),
271 "debug" : ({
258 "debug" : ({
272 "NBExtensionApp" : {
259 "NBExtensionApp" : {
273 "verbose" : 2,
260 "verbose" : 2,
274 }}, "Extra output"
261 }}, "Extra output"
275 ),
262 ),
276 "quiet" : ({
263 "quiet" : ({
277 "NBExtensionApp" : {
264 "NBExtensionApp" : {
278 "verbose" : 0,
265 "verbose" : 0,
279 }}, "Minimal output"
266 }}, "Minimal output"
280 ),
267 ),
281 "symlink" : ({
268 "symlink" : ({
282 "NBExtensionApp" : {
269 "NBExtensionApp" : {
283 "symlink" : True,
270 "symlink" : True,
284 }}, "Create symlinks instead of copying files"
271 }}, "Create symlink instead of copying files"
285 ),
272 ),
286 "user" : ({
273 "user" : ({
287 "NBExtensionApp" : {
274 "NBExtensionApp" : {
288 "user" : True,
275 "user" : True,
289 }}, "Install to the user's IPython directory"
276 }}, "Install to the user's IPython directory"
290 ),
277 ),
291 }
278 }
292 flags['s'] = flags['symlink']
279 flags['s'] = flags['symlink']
293
280
294 aliases = {
281 aliases = {
295 "ipython-dir" : "NBExtensionApp.ipython_dir",
282 "ipython-dir" : "NBExtensionApp.ipython_dir",
296 "prefix" : "NBExtensionApp.prefix",
283 "prefix" : "NBExtensionApp.prefix",
297 "nbextensions" : "NBExtensionApp.nbextensions_dir",
284 "nbextensions" : "NBExtensionApp.nbextensions_dir",
285 "destination" : "NBExtensionApp.destination",
298 }
286 }
299
287
300 class NBExtensionApp(BaseIPythonApplication):
288 class NBExtensionApp(BaseIPythonApplication):
301 """Entry point for installing notebook extensions"""
289 """Entry point for installing notebook extensions"""
302
290
303 description = """Install IPython notebook extensions
291 description = """Install IPython notebook extensions
304
292
305 Usage
293 Usage
306
294
307 ipython install-nbextension file [more files, folders, archives or urls]
295 ipython install-nbextension path/url
308
296
309 This copies files and/or folders into the IPython nbextensions directory.
297 This copies a file or a folder into the IPython nbextensions directory.
310 If a URL is given, it will be downloaded.
298 If a URL is given, it will be downloaded.
311 If an archive is given, it will be extracted into nbextensions.
299 If an archive is given, it will be extracted into nbextensions.
312 If the requested files are already up to date, no action is taken
300 If the requested files are already up to date, no action is taken
313 unless --overwrite is specified.
301 unless --overwrite is specified.
314 """
302 """
315
303
316 examples = """
304 examples = """
317 ipython install-nbextension /path/to/d3.js /path/to/myextension
305 ipython install-nbextension /path/to/myextension
318 """
306 """
319 aliases = aliases
307 aliases = aliases
320 flags = flags
308 flags = flags
321
309
322 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
310 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
323 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
311 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
324 user = Bool(False, config=True, help="Whether to do a user install")
312 user = Bool(False, config=True, help="Whether to do a user install")
325 prefix = Unicode('', config=True, help="Installation prefix")
313 prefix = Unicode('', config=True, help="Installation prefix")
326 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
314 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
315 destination = Unicode('', config=True, help="Destination for the copy or symlink")
327 verbose = Enum((0,1,2), default_value=1, config=True,
316 verbose = Enum((0,1,2), default_value=1, config=True,
328 help="Verbosity level"
317 help="Verbosity level"
329 )
318 )
330
319
331 def install_extensions(self):
320 def install_extensions(self):
332 install_nbextension(self.extra_args,
321 if len(self.extra_args)>1:
322 raise ValueError("only one nbextension allowed at a time. Call multiple times to install multiple extensions.")
323 install_nbextension(self.extra_args[0],
333 overwrite=self.overwrite,
324 overwrite=self.overwrite,
334 symlink=self.symlink,
325 symlink=self.symlink,
335 verbose=self.verbose,
326 verbose=self.verbose,
336 user=self.user,
327 user=self.user,
337 prefix=self.prefix,
328 prefix=self.prefix,
329 destination=self.destination,
338 nbextensions_dir=self.nbextensions_dir,
330 nbextensions_dir=self.nbextensions_dir,
339 )
331 )
340
332
341 def start(self):
333 def start(self):
342 if not self.extra_args:
334 if not self.extra_args:
343 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
335 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
344 if os.path.exists(nbext):
336 if os.path.exists(nbext):
345 print("Notebook extensions in %s:" % nbext)
337 print("Notebook extensions in %s:" % nbext)
346 for ext in os.listdir(nbext):
338 for ext in os.listdir(nbext):
347 print(u" %s" % ext)
339 print(u" %s" % ext)
348 else:
340 else:
349 try:
341 try:
350 self.install_extensions()
342 self.install_extensions()
351 except ArgumentConflict as e:
343 except ArgumentConflict as e:
352 print(str(e), file=sys.stderr)
344 print(str(e), file=sys.stderr)
353 self.exit(1)
345 self.exit(1)
354
346
355
347
356 if __name__ == '__main__':
348 if __name__ == '__main__':
357 NBExtensionApp.launch_instance()
349 NBExtensionApp.launch_instance()
358
350
@@ -1,293 +1,316 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test installation of notebook extensions"""
2 """Test installation of notebook extensions"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import glob
7 import glob
8 import os
8 import os
9 import re
9 import re
10 import tarfile
10 import tarfile
11 import zipfile
11 import zipfile
12 from io import BytesIO
12 from io import BytesIO
13 from os.path import basename, join as pjoin
13 from os.path import basename, join as pjoin
14 from unittest import TestCase
14 from unittest import TestCase
15
15
16 import IPython.testing.tools as tt
16 import IPython.testing.tools as tt
17 import IPython.testing.decorators as dec
17 import IPython.testing.decorators as dec
18 from IPython.utils import py3compat
18 from IPython.utils import py3compat
19 from IPython.utils.tempdir import TemporaryDirectory
19 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.html import nbextensions
20 from IPython.html import nbextensions
21 from IPython.html.nbextensions import install_nbextension, check_nbextension
21 from IPython.html.nbextensions import install_nbextension, check_nbextension
22
22
23
23
24 def touch(file, mtime=None):
24 def touch(file, mtime=None):
25 """ensure a file exists, and set its modification time
25 """ensure a file exists, and set its modification time
26
26
27 returns the modification time of the file
27 returns the modification time of the file
28 """
28 """
29 open(file, 'a').close()
29 open(file, 'a').close()
30 # set explicit mtime
30 # set explicit mtime
31 if mtime:
31 if mtime:
32 atime = os.stat(file).st_atime
32 atime = os.stat(file).st_atime
33 os.utime(file, (atime, mtime))
33 os.utime(file, (atime, mtime))
34 return os.stat(file).st_mtime
34 return os.stat(file).st_mtime
35
35
36 class TestInstallNBExtension(TestCase):
36 class TestInstallNBExtension(TestCase):
37
37
38 def tempdir(self):
38 def tempdir(self):
39 td = TemporaryDirectory()
39 td = TemporaryDirectory()
40 self.tempdirs.append(td)
40 self.tempdirs.append(td)
41 return py3compat.cast_unicode(td.name)
41 return py3compat.cast_unicode(td.name)
42
42
43 def setUp(self):
43 def setUp(self):
44 self.tempdirs = []
44 self.tempdirs = []
45 src = self.src = self.tempdir()
45 src = self.src = self.tempdir()
46 self.files = files = [
46 self.files = files = [
47 pjoin(u'ƒile'),
47 pjoin(u'ƒile'),
48 pjoin(u'∂ir', u'ƒile1'),
48 pjoin(u'∂ir', u'ƒile1'),
49 pjoin(u'∂ir', u'∂ir2', u'ƒile2'),
49 pjoin(u'∂ir', u'∂ir2', u'ƒile2'),
50 ]
50 ]
51 for file in files:
51 for file in files:
52 fullpath = os.path.join(self.src, file)
52 fullpath = os.path.join(self.src, file)
53 parent = os.path.dirname(fullpath)
53 parent = os.path.dirname(fullpath)
54 if not os.path.exists(parent):
54 if not os.path.exists(parent):
55 os.makedirs(parent)
55 os.makedirs(parent)
56 touch(fullpath)
56 touch(fullpath)
57
57
58 self.ipdir = self.tempdir()
58 self.ipdir = self.tempdir()
59 self.save_get_ipython_dir = nbextensions.get_ipython_dir
59 self.save_get_ipython_dir = nbextensions.get_ipython_dir
60 nbextensions.get_ipython_dir = lambda : self.ipdir
60 nbextensions.get_ipython_dir = lambda : self.ipdir
61 self.save_system_dir = nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR
61 self.save_system_dir = nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR
62 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = self.tempdir()
62 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = self.tempdir()
63
63
64 def tearDown(self):
64 def tearDown(self):
65 nbextensions.get_ipython_dir = self.save_get_ipython_dir
65 nbextensions.get_ipython_dir = self.save_get_ipython_dir
66 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.save_system_dir
66 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.save_system_dir
67 for td in self.tempdirs:
67 for td in self.tempdirs:
68 td.cleanup()
68 td.cleanup()
69
69
70 def assert_dir_exists(self, path):
70 def assert_dir_exists(self, path):
71 if not os.path.exists(path):
71 if not os.path.exists(path):
72 do_exist = os.listdir(os.path.dirname(path))
72 do_exist = os.listdir(os.path.dirname(path))
73 self.fail(u"%s should exist (found %s)" % (path, do_exist))
73 self.fail(u"%s should exist (found %s)" % (path, do_exist))
74
74
75 def assert_not_dir_exists(self, path):
75 def assert_not_dir_exists(self, path):
76 if os.path.exists(path):
76 if os.path.exists(path):
77 self.fail(u"%s should not exist" % path)
77 self.fail(u"%s should not exist" % path)
78
78
79 def assert_installed(self, relative_path, user=False):
79 def assert_installed(self, relative_path, user=False):
80 if user:
80 if user:
81 nbext = pjoin(self.ipdir, u'nbextensions')
81 nbext = pjoin(self.ipdir, u'nbextensions')
82 else:
82 else:
83 nbext = self.system_nbext
83 nbext = self.system_nbext
84 self.assert_dir_exists(
84 self.assert_dir_exists(
85 pjoin(nbext, relative_path)
85 pjoin(nbext, relative_path)
86 )
86 )
87
87
88 def assert_not_installed(self, relative_path, user=False):
88 def assert_not_installed(self, relative_path, user=False):
89 if user:
89 if user:
90 nbext = pjoin(self.ipdir, u'nbextensions')
90 nbext = pjoin(self.ipdir, u'nbextensions')
91 else:
91 else:
92 nbext = self.system_nbext
92 nbext = self.system_nbext
93 self.assert_not_dir_exists(
93 self.assert_not_dir_exists(
94 pjoin(nbext, relative_path)
94 pjoin(nbext, relative_path)
95 )
95 )
96
96
97 def test_create_ipython_dir(self):
97 def test_create_ipython_dir(self):
98 """install_nbextension when ipython_dir doesn't exist"""
98 """install_nbextension when ipython_dir doesn't exist"""
99 with TemporaryDirectory() as td:
99 with TemporaryDirectory() as td:
100 self.ipdir = ipdir = pjoin(td, u'ipython')
100 self.ipdir = ipdir = pjoin(td, u'ipython')
101 install_nbextension(self.src, user=True)
101 install_nbextension(self.src, user=True)
102 self.assert_dir_exists(ipdir)
102 self.assert_dir_exists(ipdir)
103 for file in self.files:
103 for file in self.files:
104 self.assert_installed(
104 self.assert_installed(
105 pjoin(basename(self.src), file),
105 pjoin(basename(self.src), file),
106 user=bool(ipdir)
106 user=bool(ipdir)
107 )
107 )
108
108
109 def test_create_nbextensions_user(self):
109 def test_create_nbextensions_user(self):
110 with TemporaryDirectory() as td:
110 with TemporaryDirectory() as td:
111 self.ipdir = ipdir = pjoin(td, u'ipython')
111 self.ipdir = ipdir = pjoin(td, u'ipython')
112 install_nbextension(self.src, user=True)
112 install_nbextension(self.src, user=True)
113 self.assert_installed(
113 self.assert_installed(
114 pjoin(basename(self.src), u'ƒile'),
114 pjoin(basename(self.src), u'ƒile'),
115 user=True
115 user=True
116 )
116 )
117
117
118 def test_create_nbextensions_system(self):
118 def test_create_nbextensions_system(self):
119 with TemporaryDirectory() as td:
119 with TemporaryDirectory() as td:
120 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = pjoin(td, u'nbextensions')
120 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = pjoin(td, u'nbextensions')
121 install_nbextension(self.src, user=False)
121 install_nbextension(self.src, user=False)
122 self.assert_installed(
122 self.assert_installed(
123 pjoin(basename(self.src), u'ƒile'),
123 pjoin(basename(self.src), u'ƒile'),
124 user=False
124 user=False
125 )
125 )
126
126
127 def test_single_file(self):
127 def test_single_file(self):
128 file = self.files[0]
128 file = self.files[0]
129 install_nbextension(pjoin(self.src, file))
129 install_nbextension(pjoin(self.src, file))
130 self.assert_installed(file)
130 self.assert_installed(file)
131
131
132 def test_single_dir(self):
132 def test_single_dir(self):
133 d = u'∂ir'
133 d = u'∂ir'
134 install_nbextension(pjoin(self.src, d))
134 install_nbextension(pjoin(self.src, d))
135 self.assert_installed(self.files[-1])
135 self.assert_installed(self.files[-1])
136 install_nbextension({'test': pjoin(self.src, d)})
136
137 self.assert_installed(pjoin('test', u'∂ir2', u'ƒile2'))
137
138 def test_destination_file(self):
139 file = self.files[0]
140 install_nbextension(pjoin(self.src, file), destination = u'ƒiledest')
141 self.assert_installed(u'ƒiledest')
142
143 def test_destination_dir(self):
144 d = u'∂ir'
145 install_nbextension(pjoin(self.src, d), destination = u'ƒiledest2')
146 self.assert_installed(pjoin(u'ƒiledest2', u'∂ir2', u'ƒile2'))
138
147
139 def test_install_nbextension(self):
148 def test_install_nbextension(self):
140 install_nbextension(glob.glob(pjoin(self.src, '*')))
149 with self.assertRaises(TypeError):
141 for file in self.files:
150 install_nbextension(glob.glob(pjoin(self.src, '*')))
142 self.assert_installed(file)
143
151
144 def test_overwrite_file(self):
152 def test_overwrite_file(self):
145 with TemporaryDirectory() as d:
153 with TemporaryDirectory() as d:
146 fname = u'ƒ.js'
154 fname = u'ƒ.js'
147 src = pjoin(d, fname)
155 src = pjoin(d, fname)
148 with open(src, 'w') as f:
156 with open(src, 'w') as f:
149 f.write('first')
157 f.write('first')
150 mtime = touch(src)
158 mtime = touch(src)
151 dest = pjoin(self.system_nbext, fname)
159 dest = pjoin(self.system_nbext, fname)
152 install_nbextension(src)
160 install_nbextension(src)
153 with open(src, 'w') as f:
161 with open(src, 'w') as f:
154 f.write('overwrite')
162 f.write('overwrite')
155 mtime = touch(src, mtime - 100)
163 mtime = touch(src, mtime - 100)
156 install_nbextension(src, overwrite=True)
164 install_nbextension(src, overwrite=True)
157 with open(dest) as f:
165 with open(dest) as f:
158 self.assertEqual(f.read(), 'overwrite')
166 self.assertEqual(f.read(), 'overwrite')
159
167
160 def test_overwrite_dir(self):
168 def test_overwrite_dir(self):
161 with TemporaryDirectory() as src:
169 with TemporaryDirectory() as src:
162 base = basename(src)
170 base = basename(src)
163 fname = u'ƒ.js'
171 fname = u'ƒ.js'
164 touch(pjoin(src, fname))
172 touch(pjoin(src, fname))
165 install_nbextension(src)
173 install_nbextension(src)
166 self.assert_installed(pjoin(base, fname))
174 self.assert_installed(pjoin(base, fname))
167 os.remove(pjoin(src, fname))
175 os.remove(pjoin(src, fname))
168 fname2 = u'∂.js'
176 fname2 = u'∂.js'
169 touch(pjoin(src, fname2))
177 touch(pjoin(src, fname2))
170 install_nbextension(src, overwrite=True)
178 install_nbextension(src, overwrite=True)
171 self.assert_installed(pjoin(base, fname2))
179 self.assert_installed(pjoin(base, fname2))
172 self.assert_not_installed(pjoin(base, fname))
180 self.assert_not_installed(pjoin(base, fname))
173
181
174 def test_update_file(self):
182 def test_update_file(self):
175 with TemporaryDirectory() as d:
183 with TemporaryDirectory() as d:
176 fname = u'ƒ.js'
184 fname = u'ƒ.js'
177 src = pjoin(d, fname)
185 src = pjoin(d, fname)
178 with open(src, 'w') as f:
186 with open(src, 'w') as f:
179 f.write('first')
187 f.write('first')
180 mtime = touch(src)
188 mtime = touch(src)
181 install_nbextension(src)
189 install_nbextension(src)
182 self.assert_installed(fname)
190 self.assert_installed(fname)
183 dest = pjoin(self.system_nbext, fname)
191 dest = pjoin(self.system_nbext, fname)
184 old_mtime = os.stat(dest).st_mtime
192 old_mtime = os.stat(dest).st_mtime
185 with open(src, 'w') as f:
193 with open(src, 'w') as f:
186 f.write('overwrite')
194 f.write('overwrite')
187 touch(src, mtime + 10)
195 touch(src, mtime + 10)
188 install_nbextension(src)
196 install_nbextension(src)
189 with open(dest) as f:
197 with open(dest) as f:
190 self.assertEqual(f.read(), 'overwrite')
198 self.assertEqual(f.read(), 'overwrite')
191
199
192 def test_skip_old_file(self):
200 def test_skip_old_file(self):
193 with TemporaryDirectory() as d:
201 with TemporaryDirectory() as d:
194 fname = u'ƒ.js'
202 fname = u'ƒ.js'
195 src = pjoin(d, fname)
203 src = pjoin(d, fname)
196 mtime = touch(src)
204 mtime = touch(src)
197 install_nbextension(src)
205 install_nbextension(src)
198 self.assert_installed(fname)
206 self.assert_installed(fname)
199 dest = pjoin(self.system_nbext, fname)
207 dest = pjoin(self.system_nbext, fname)
200 old_mtime = os.stat(dest).st_mtime
208 old_mtime = os.stat(dest).st_mtime
201
209
202 mtime = touch(src, mtime - 100)
210 mtime = touch(src, mtime - 100)
203 install_nbextension(src)
211 install_nbextension(src)
204 new_mtime = os.stat(dest).st_mtime
212 new_mtime = os.stat(dest).st_mtime
205 self.assertEqual(new_mtime, old_mtime)
213 self.assertEqual(new_mtime, old_mtime)
206
214
207 def test_quiet(self):
215 def test_quiet(self):
208 with tt.AssertNotPrints(re.compile(r'.+')):
216 with tt.AssertNotPrints(re.compile(r'.+')):
209 install_nbextension(self.src, verbose=0)
217 install_nbextension(self.src, verbose=0)
210
218
211 def test_install_zip(self):
219 def test_install_zip(self):
212 path = pjoin(self.src, "myjsext.zip")
220 path = pjoin(self.src, "myjsext.zip")
213 with zipfile.ZipFile(path, 'w') as f:
221 with zipfile.ZipFile(path, 'w') as f:
214 f.writestr("a.js", b"b();")
222 f.writestr("a.js", b"b();")
215 f.writestr("foo/a.js", b"foo();")
223 f.writestr("foo/a.js", b"foo();")
216 install_nbextension(path)
224 install_nbextension(path)
217 self.assert_installed("a.js")
225 self.assert_installed("a.js")
218 self.assert_installed(pjoin("foo", "a.js"))
226 self.assert_installed(pjoin("foo", "a.js"))
219
227
220 def test_install_tar(self):
228 def test_install_tar(self):
221 def _add_file(f, fname, buf):
229 def _add_file(f, fname, buf):
222 info = tarfile.TarInfo(fname)
230 info = tarfile.TarInfo(fname)
223 info.size = len(buf)
231 info.size = len(buf)
224 f.addfile(info, BytesIO(buf))
232 f.addfile(info, BytesIO(buf))
225
233
226 for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
234 for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
227 path = pjoin(self.src, "myjsext" + ext)
235 path = pjoin(self.src, "myjsext" + ext)
228 with tarfile.open(path, 'w') as f:
236 with tarfile.open(path, 'w') as f:
229 _add_file(f, "b%i.js" % i, b"b();")
237 _add_file(f, "b%i.js" % i, b"b();")
230 _add_file(f, "foo/b%i.js" % i, b"foo();")
238 _add_file(f, "foo/b%i.js" % i, b"foo();")
231 install_nbextension(path)
239 install_nbextension(path)
232 self.assert_installed("b%i.js" % i)
240 self.assert_installed("b%i.js" % i)
233 self.assert_installed(pjoin("foo", "b%i.js" % i))
241 self.assert_installed(pjoin("foo", "b%i.js" % i))
234
242
235 def test_install_url(self):
243 def test_install_url(self):
236 def fake_urlretrieve(url, dest):
244 def fake_urlretrieve(url, dest):
237 touch(dest)
245 touch(dest)
238 save_urlretrieve = nbextensions.urlretrieve
246 save_urlretrieve = nbextensions.urlretrieve
239 nbextensions.urlretrieve = fake_urlretrieve
247 nbextensions.urlretrieve = fake_urlretrieve
240 try:
248 try:
241 install_nbextension("http://example.com/path/to/foo.js")
249 install_nbextension("http://example.com/path/to/foo.js")
242 self.assert_installed("foo.js")
250 self.assert_installed("foo.js")
243 install_nbextension("https://example.com/path/to/another/bar.js")
251 install_nbextension("https://example.com/path/to/another/bar.js")
244 self.assert_installed("bar.js")
252 self.assert_installed("bar.js")
245 install_nbextension({'foobar.js': "https://example.com/path/to/another/bar.js"})
253 install_nbextension("https://example.com/path/to/another/bar.js",
254 destination = 'foobar.js')
246 self.assert_installed("foobar.js")
255 self.assert_installed("foobar.js")
247 finally:
256 finally:
248 nbextensions.urlretrieve = save_urlretrieve
257 nbextensions.urlretrieve = save_urlretrieve
249
258
250 def test_check_nbextension(self):
259 def test_check_nbextension(self):
251 with TemporaryDirectory() as d:
260 with TemporaryDirectory() as d:
252 f = u'ƒ.js'
261 f = u'ƒ.js'
253 src = pjoin(d, f)
262 src = pjoin(d, f)
254 touch(src)
263 touch(src)
255 install_nbextension(src, user=True)
264 install_nbextension(src, user=True)
256
265
257 assert check_nbextension(f, user=True)
266 assert check_nbextension(f, user=True)
258 assert check_nbextension([f], user=True)
267 assert check_nbextension([f], user=True)
259 assert not check_nbextension([f, pjoin('dne', f)], user=True)
268 assert not check_nbextension([f, pjoin('dne', f)], user=True)
260
269
261 @dec.skip_win32
270 @dec.skip_win32
262 def test_install_symlink(self):
271 def test_install_symlink(self):
263 with TemporaryDirectory() as d:
272 with TemporaryDirectory() as d:
264 f = u'ƒ.js'
273 f = u'ƒ.js'
265 src = pjoin(d, f)
274 src = pjoin(d, f)
266 touch(src)
275 touch(src)
267 install_nbextension(src, symlink=True)
276 install_nbextension(src, symlink=True)
268 dest = pjoin(self.system_nbext, f)
277 dest = pjoin(self.system_nbext, f)
269 assert os.path.islink(dest)
278 assert os.path.islink(dest)
270 link = os.readlink(dest)
279 link = os.readlink(dest)
271 self.assertEqual(link, src)
280 self.assertEqual(link, src)
272
281
282 @dec.skip_win32
283 def test_install_symlink_destination(self):
284 with TemporaryDirectory() as d:
285 f = u'ƒ.js'
286 flink = u'ƒlink.js'
287 src = pjoin(d, f)
288 touch(src)
289 install_nbextension(src, symlink=True, destination=flink)
290 dest = pjoin(self.system_nbext, flink)
291 assert os.path.islink(dest)
292 link = os.readlink(dest)
293 self.assertEqual(link, src)
294
273 def test_install_symlink_bad(self):
295 def test_install_symlink_bad(self):
274 with self.assertRaises(ValueError):
296 with self.assertRaises(ValueError):
275 install_nbextension("http://example.com/foo.js", symlink=True)
297 install_nbextension("http://example.com/foo.js", symlink=True)
276
298
277 with TemporaryDirectory() as d:
299 with TemporaryDirectory() as d:
278 zf = u'ƒ.zip'
300 zf = u'ƒ.zip'
279 zsrc = pjoin(d, zf)
301 zsrc = pjoin(d, zf)
280 with zipfile.ZipFile(zsrc, 'w') as z:
302 with zipfile.ZipFile(zsrc, 'w') as z:
281 z.writestr("a.js", b"b();")
303 z.writestr("a.js", b"b();")
282
304
283 with self.assertRaises(ValueError):
305 with self.assertRaises(ValueError):
284 install_nbextension(zsrc, symlink=True)
306 install_nbextension(zsrc, symlink=True)
285
307
286 def test_install_different_name(self):
308 def test_install_destination_bad(self):
287 with TemporaryDirectory() as d:
309 with TemporaryDirectory() as d:
288 f = u'ƒ.js'
310 zf = u'ƒ.zip'
289 src = pjoin(d, f)
311 zsrc = pjoin(d, zf)
290 dest_f = u'ƒile.js'
312 with zipfile.ZipFile(zsrc, 'w') as z:
291 touch(src)
313 z.writestr("a.js", b"b();")
292 install_nbextension({dest_f: src})
314
293 self.assert_installed(dest_f)
315 with self.assertRaises(ValueError):
316 install_nbextension(zsrc, destination='foo')
General Comments 0
You need to be logged in to leave comments. Login now