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