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