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