##// END OF EJS Templates
Fix the path we use when walking to include the path separator...
Jason Grout -
Show More
@@ -1,341 +1,342 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(dest).st_mtime < os.stat(src).st_mtime:
64 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
65 if verbose >= 2:
65 if verbose >= 2:
66 print("%s is out of date" % dest)
66 print("%s is out of date" % dest)
67 return True
67 return True
68 if verbose >= 2:
68 if verbose >= 2:
69 print("%s is up to date" % dest)
69 print("%s is up to date" % dest)
70 return False
70 return False
71
71
72
72
73 def _maybe_copy(src, dest, verbose=1):
73 def _maybe_copy(src, dest, verbose=1):
74 """copy a file if it needs updating"""
74 """copy a file if it needs updating"""
75 if _should_copy(src, dest, verbose):
75 if _should_copy(src, dest, verbose):
76 if verbose >= 1:
76 if verbose >= 1:
77 print("copying %s -> %s" % (src, dest))
77 print("copying %s -> %s" % (src, dest))
78 shutil.copy2(src, dest)
78 shutil.copy2(src, dest)
79
79
80
80
81 def _safe_is_tarfile(path):
81 def _safe_is_tarfile(path):
82 """safe version of is_tarfile, return False on IOError"""
82 """safe version of is_tarfile, return False on IOError"""
83 try:
83 try:
84 return tarfile.is_tarfile(path)
84 return tarfile.is_tarfile(path)
85 except IOError:
85 except IOError:
86 return False
86 return False
87
87
88
88
89 def check_nbextension(files, nbextensions_dir=None):
89 def check_nbextension(files, nbextensions_dir=None):
90 """Check whether nbextension files have been installed
90 """Check whether nbextension files have been installed
91
91
92 files should be a list of relative paths within nbextensions.
92 files should be a list of relative paths within nbextensions.
93
93
94 Returns True if all files are found, False if any are missing.
94 Returns True if all files are found, False if any are missing.
95 """
95 """
96 if nbextensions_dir:
96 if nbextensions_dir:
97 nbext = nbextensions_dir
97 nbext = nbextensions_dir
98 else:
98 else:
99 nbext = pjoin(get_ipython_dir(), u'nbextensions')
99 nbext = pjoin(get_ipython_dir(), u'nbextensions')
100 # make sure nbextensions dir exists
100 # make sure nbextensions dir exists
101 if not os.path.exists(nbext):
101 if not os.path.exists(nbext):
102 return False
102 return False
103
103
104 if isinstance(files, string_types):
104 if isinstance(files, string_types):
105 # one file given, turn it into a list
105 # one file given, turn it into a list
106 files = [files]
106 files = [files]
107
107
108 return all(os.path.exists(pjoin(nbext, f)) for f in files)
108 return all(os.path.exists(pjoin(nbext, f)) for f in files)
109
109
110
110
111 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1):
111 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1):
112 """Install a Javascript extension for the notebook
112 """Install a Javascript extension for the notebook
113
113
114 Stages files and/or directories into the nbextensions directory.
114 Stages files and/or directories into the nbextensions directory.
115 By default, this compares modification time, and only stages files that need updating.
115 By default, this compares modification time, and only stages files that need updating.
116 If `overwrite` is specified, matching files are purged before proceeding.
116 If `overwrite` is specified, matching files are purged before proceeding.
117
117
118 Parameters
118 Parameters
119 ----------
119 ----------
120
120
121 files : list(paths or URLs) or dict(install_name: path or URL)
121 files : list(paths or URLs) or dict(install_name: path or URL)
122 One or more paths or URLs to existing files directories to install.
122 One or more paths or URLs to existing files directories to install.
123 If given as a list, these will be installed with their base name, so '/path/to/foo'
123 If given as a list, these will be installed with their base name, so '/path/to/foo'
124 will install to 'nbextensions/foo'. If given as a dict, such as {'bar': '/path/to/foo'},
124 will install to 'nbextensions/foo'. If given as a dict, such as {'bar': '/path/to/foo'},
125 then '/path/to/foo' will install to 'nbextensions/bar'.
125 then '/path/to/foo' will install to 'nbextensions/bar'.
126 Archives (zip or tarballs) will be extracted into the nbextensions directory.
126 Archives (zip or tarballs) will be extracted into the nbextensions directory.
127 overwrite : bool [default: False]
127 overwrite : bool [default: False]
128 If True, always install the files, regardless of what may already be installed.
128 If True, always install the files, regardless of what may already be installed.
129 symlink : bool [default: False]
129 symlink : bool [default: False]
130 If True, create a symlink in nbextensions, rather than copying files.
130 If True, create a symlink in nbextensions, rather than copying files.
131 Not allowed with URLs or archives. Windows support for symlinks requires
131 Not allowed with URLs or archives. Windows support for symlinks requires
132 Vista or above, Python 3, and a permission bit which only admin users
132 Vista or above, Python 3, and a permission bit which only admin users
133 have by default, so don't rely on it.
133 have by default, so don't rely on it.
134 user : bool [default: False]
134 user : bool [default: False]
135 Whether to install to the user's .ipython/nbextensions directory.
135 Whether to install to the user's .ipython/nbextensions directory.
136 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
136 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
137 prefix : str [optional]
137 prefix : str [optional]
138 Specify install prefix, if it should differ from default (e.g. /usr/local).
138 Specify install prefix, if it should differ from default (e.g. /usr/local).
139 Will install to prefix/share/jupyter/nbextensions
139 Will install to prefix/share/jupyter/nbextensions
140 nbextensions_dir : str [optional]
140 nbextensions_dir : str [optional]
141 Specify absolute path of nbextensions directory explicitly.
141 Specify absolute path of nbextensions directory explicitly.
142 verbose : int [default: 1]
142 verbose : int [default: 1]
143 Set verbosity level. The default is 1, where file actions are printed.
143 Set verbosity level. The default is 1, where file actions are printed.
144 set verbose=2 for more output, or verbose=0 for silence.
144 set verbose=2 for more output, or verbose=0 for silence.
145 """
145 """
146 if sum(map(bool, [user, prefix, nbextensions_dir])) > 1:
146 if sum(map(bool, [user, prefix, nbextensions_dir])) > 1:
147 raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.")
147 raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.")
148 if user:
148 if user:
149 nbext = pjoin(get_ipython_dir(), u'nbextensions')
149 nbext = pjoin(get_ipython_dir(), u'nbextensions')
150 else:
150 else:
151 if prefix:
151 if prefix:
152 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
152 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
153 elif nbextensions_dir:
153 elif nbextensions_dir:
154 nbext = nbextensions_dir
154 nbext = nbextensions_dir
155 else:
155 else:
156 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
156 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
157 # make sure nbextensions dir exists
157 # make sure nbextensions dir exists
158 ensure_dir_exists(nbext)
158 ensure_dir_exists(nbext)
159
159
160 if isinstance(files, string_types):
160 if isinstance(files, string_types):
161 # one file given, turn it into a list
161 # one file given, turn it into a list
162 files = [files]
162 files = [files]
163 if isinstance(files, (list,tuple)):
163 if isinstance(files, (list,tuple)):
164 # list given, turn into dict
164 # list given, turn into dict
165 _files = {}
165 _files = {}
166 for path in map(cast_unicode_py2, files):
166 for path in map(cast_unicode_py2, files):
167 if path.startswith(('https://', 'http://')):
167 if path.startswith(('https://', 'http://')):
168 destination = urlparse(path).path.split('/')[-1]
168 destination = urlparse(path).path.split('/')[-1]
169 elif path.endswith('.zip') or _safe_is_tarfile(path):
169 elif path.endswith('.zip') or _safe_is_tarfile(path):
170 destination = str(uuid.uuid4()) # ignored for archives
170 destination = str(uuid.uuid4()) # ignored for archives
171 else:
171 else:
172 destination = basename(path)
172 destination = basename(path)
173 _files[destination] = path
173 _files[destination] = path
174 files = _files
174 files = _files
175
175
176 for dest_basename,path in (map(cast_unicode_py2, item) for item in files.items()):
176 for dest_basename,path in (map(cast_unicode_py2, item) for item in files.items()):
177
177
178 if path.startswith(('https://', 'http://')):
178 if path.startswith(('https://', 'http://')):
179 if symlink:
179 if symlink:
180 raise ValueError("Cannot symlink from URLs")
180 raise ValueError("Cannot symlink from URLs")
181 # Given a URL, download it
181 # Given a URL, download it
182 with TemporaryDirectory() as td:
182 with TemporaryDirectory() as td:
183 filename = urlparse(path).path.split('/')[-1]
183 filename = urlparse(path).path.split('/')[-1]
184 local_path = os.path.join(td, filename)
184 local_path = os.path.join(td, filename)
185 if verbose >= 1:
185 if verbose >= 1:
186 print("downloading %s to %s" % (path, local_path))
186 print("downloading %s to %s" % (path, local_path))
187 urlretrieve(path, local_path)
187 urlretrieve(path, local_path)
188 # now install from the local copy
188 # now install from the local copy
189 install_nbextension({dest_basename: local_path}, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose)
189 install_nbextension({dest_basename: local_path}, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose)
190 continue
190 continue
191
191
192 # handle archives
192 # handle archives
193 archive = None
193 archive = None
194 if path.endswith('.zip'):
194 if path.endswith('.zip'):
195 archive = zipfile.ZipFile(path)
195 archive = zipfile.ZipFile(path)
196 elif _safe_is_tarfile(path):
196 elif _safe_is_tarfile(path):
197 archive = tarfile.open(path)
197 archive = tarfile.open(path)
198
198
199 if archive:
199 if archive:
200 if symlink:
200 if symlink:
201 raise ValueError("Cannot symlink from archives")
201 raise ValueError("Cannot symlink from archives")
202 if verbose >= 1:
202 if verbose >= 1:
203 print("extracting %s to %s" % (path, nbext))
203 print("extracting %s to %s" % (path, nbext))
204 archive.extractall(nbext)
204 archive.extractall(nbext)
205 archive.close()
205 archive.close()
206 continue
206 continue
207
207
208 dest = pjoin(nbext, dest_basename)
208 dest = pjoin(nbext, dest_basename)
209 if overwrite and os.path.exists(dest):
209 if overwrite and os.path.exists(dest):
210 if verbose >= 1:
210 if verbose >= 1:
211 print("removing %s" % dest)
211 print("removing %s" % dest)
212 if os.path.isdir(dest) and not os.path.islink(dest):
212 if os.path.isdir(dest) and not os.path.islink(dest):
213 shutil.rmtree(dest)
213 shutil.rmtree(dest)
214 else:
214 else:
215 os.remove(dest)
215 os.remove(dest)
216
216
217 if symlink:
217 if symlink:
218 path = os.path.abspath(path)
218 path = os.path.abspath(path)
219 if not os.path.exists(dest):
219 if not os.path.exists(dest):
220 if verbose >= 1:
220 if verbose >= 1:
221 print("symlink %s -> %s" % (dest, path))
221 print("symlink %s -> %s" % (dest, path))
222 os.symlink(path, dest)
222 os.symlink(path, dest)
223 continue
223 continue
224
224
225 if os.path.isdir(path):
225 if os.path.isdir(path):
226 path = pjoin(os.path.abspath(path), '') # end in path separator
226 for parent, dirs, files in os.walk(path):
227 for parent, dirs, files in os.walk(path):
227 dest_dir = pjoin(dest, parent[len(path):])
228 dest_dir = pjoin(dest, parent[len(path):])
228 if not os.path.exists(dest_dir):
229 if not os.path.exists(dest_dir):
229 if verbose >= 2:
230 if verbose >= 2:
230 print("making directory %s" % dest_dir)
231 print("making directory %s" % dest_dir)
231 os.makedirs(dest_dir)
232 os.makedirs(dest_dir)
232 for file in files:
233 for file in files:
233 src = pjoin(parent, file)
234 src = pjoin(parent, file)
234 # print("%r, %r" % (dest_dir, file))
235 # print("%r, %r" % (dest_dir, file))
235 dest_file = pjoin(dest_dir, file)
236 dest_file = pjoin(dest_dir, file)
236 _maybe_copy(src, dest_file, verbose)
237 _maybe_copy(src, dest_file, verbose)
237 else:
238 else:
238 src = path
239 src = path
239 _maybe_copy(src, dest, verbose)
240 _maybe_copy(src, dest, verbose)
240
241
241 #----------------------------------------------------------------------
242 #----------------------------------------------------------------------
242 # install nbextension app
243 # install nbextension app
243 #----------------------------------------------------------------------
244 #----------------------------------------------------------------------
244
245
245 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
246 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
246 from IPython.core.application import BaseIPythonApplication
247 from IPython.core.application import BaseIPythonApplication
247
248
248 flags = {
249 flags = {
249 "overwrite" : ({
250 "overwrite" : ({
250 "NBExtensionApp" : {
251 "NBExtensionApp" : {
251 "overwrite" : True,
252 "overwrite" : True,
252 }}, "Force overwrite of existing files"
253 }}, "Force overwrite of existing files"
253 ),
254 ),
254 "debug" : ({
255 "debug" : ({
255 "NBExtensionApp" : {
256 "NBExtensionApp" : {
256 "verbose" : 2,
257 "verbose" : 2,
257 }}, "Extra output"
258 }}, "Extra output"
258 ),
259 ),
259 "quiet" : ({
260 "quiet" : ({
260 "NBExtensionApp" : {
261 "NBExtensionApp" : {
261 "verbose" : 0,
262 "verbose" : 0,
262 }}, "Minimal output"
263 }}, "Minimal output"
263 ),
264 ),
264 "symlink" : ({
265 "symlink" : ({
265 "NBExtensionApp" : {
266 "NBExtensionApp" : {
266 "symlink" : True,
267 "symlink" : True,
267 }}, "Create symlinks instead of copying files"
268 }}, "Create symlinks instead of copying files"
268 ),
269 ),
269 "user" : ({
270 "user" : ({
270 "NBExtensionApp" : {
271 "NBExtensionApp" : {
271 "user" : True,
272 "user" : True,
272 }}, "Install to the user's IPython directory"
273 }}, "Install to the user's IPython directory"
273 ),
274 ),
274 }
275 }
275 flags['s'] = flags['symlink']
276 flags['s'] = flags['symlink']
276
277
277 aliases = {
278 aliases = {
278 "ipython-dir" : "NBExtensionApp.ipython_dir",
279 "ipython-dir" : "NBExtensionApp.ipython_dir",
279 "prefix" : "NBExtensionApp.prefix",
280 "prefix" : "NBExtensionApp.prefix",
280 "nbextensions" : "NBExtensionApp.nbextensions_dir",
281 "nbextensions" : "NBExtensionApp.nbextensions_dir",
281 }
282 }
282
283
283 class NBExtensionApp(BaseIPythonApplication):
284 class NBExtensionApp(BaseIPythonApplication):
284 """Entry point for installing notebook extensions"""
285 """Entry point for installing notebook extensions"""
285
286
286 description = """Install IPython notebook extensions
287 description = """Install IPython notebook extensions
287
288
288 Usage
289 Usage
289
290
290 ipython install-nbextension file [more files, folders, archives or urls]
291 ipython install-nbextension file [more files, folders, archives or urls]
291
292
292 This copies files and/or folders into the IPython nbextensions directory.
293 This copies files and/or folders into the IPython nbextensions directory.
293 If a URL is given, it will be downloaded.
294 If a URL is given, it will be downloaded.
294 If an archive is given, it will be extracted into nbextensions.
295 If an archive is given, it will be extracted into nbextensions.
295 If the requested files are already up to date, no action is taken
296 If the requested files are already up to date, no action is taken
296 unless --overwrite is specified.
297 unless --overwrite is specified.
297 """
298 """
298
299
299 examples = """
300 examples = """
300 ipython install-nbextension /path/to/d3.js /path/to/myextension
301 ipython install-nbextension /path/to/d3.js /path/to/myextension
301 """
302 """
302 aliases = aliases
303 aliases = aliases
303 flags = flags
304 flags = flags
304
305
305 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
306 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
306 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
307 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
307 user = Bool(False, config=True, help="Whether to do a user install")
308 user = Bool(False, config=True, help="Whether to do a user install")
308 prefix = Unicode('', config=True, help="Installation prefix")
309 prefix = Unicode('', config=True, help="Installation prefix")
309 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
310 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
310 verbose = Enum((0,1,2), default_value=1, config=True,
311 verbose = Enum((0,1,2), default_value=1, config=True,
311 help="Verbosity level"
312 help="Verbosity level"
312 )
313 )
313
314
314 def install_extensions(self):
315 def install_extensions(self):
315 install_nbextension(self.extra_args,
316 install_nbextension(self.extra_args,
316 overwrite=self.overwrite,
317 overwrite=self.overwrite,
317 symlink=self.symlink,
318 symlink=self.symlink,
318 verbose=self.verbose,
319 verbose=self.verbose,
319 user=self.user,
320 user=self.user,
320 prefix=self.prefix,
321 prefix=self.prefix,
321 nbextensions_dir=self.nbextensions_dir,
322 nbextensions_dir=self.nbextensions_dir,
322 )
323 )
323
324
324 def start(self):
325 def start(self):
325 if not self.extra_args:
326 if not self.extra_args:
326 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
327 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
327 if os.path.exists(nbext):
328 if os.path.exists(nbext):
328 print("Notebook extensions in %s:" % nbext)
329 print("Notebook extensions in %s:" % nbext)
329 for ext in os.listdir(nbext):
330 for ext in os.listdir(nbext):
330 print(u" %s" % ext)
331 print(u" %s" % ext)
331 else:
332 else:
332 try:
333 try:
333 self.install_extensions()
334 self.install_extensions()
334 except ArgumentConflict as e:
335 except ArgumentConflict as e:
335 print(str(e), file=sys.stderr)
336 print(str(e), file=sys.stderr)
336 self.exit(1)
337 self.exit(1)
337
338
338
339
339 if __name__ == '__main__':
340 if __name__ == '__main__':
340 NBExtensionApp.launch_instance()
341 NBExtensionApp.launch_instance()
341 No newline at end of file
342
General Comments 0
You need to be logged in to leave comments. Login now