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