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