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