##// END OF EJS Templates
Use os.remove instead of shutil.rmtree if we try to remove a symbolic link...
Jason Grout -
Show More
@@ -1,265 +1,265 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Utilities for installing Javascript extensions for the notebook"""
2 """Utilities for installing Javascript extensions for the notebook"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import os
9 import os
10 import shutil
10 import shutil
11 import tarfile
11 import tarfile
12 import zipfile
12 import zipfile
13 from os.path import basename, join as pjoin
13 from os.path import basename, join as pjoin
14
14
15 # Deferred imports
15 # Deferred imports
16 try:
16 try:
17 from urllib.parse import urlparse # Py3
17 from urllib.parse import urlparse # Py3
18 from urllib.request import urlretrieve
18 from urllib.request import urlretrieve
19 except ImportError:
19 except ImportError:
20 from urlparse import urlparse
20 from urlparse import urlparse
21 from urllib import urlretrieve
21 from urllib import urlretrieve
22
22
23 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
23 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
24 from IPython.utils.py3compat import string_types, cast_unicode_py2
24 from IPython.utils.py3compat import string_types, cast_unicode_py2
25 from IPython.utils.tempdir import TemporaryDirectory
25 from IPython.utils.tempdir import TemporaryDirectory
26
26
27
27
28 def _should_copy(src, dest, verbose=1):
28 def _should_copy(src, dest, verbose=1):
29 """should a file be copied?"""
29 """should a file be copied?"""
30 if not os.path.exists(dest):
30 if not os.path.exists(dest):
31 return True
31 return True
32 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
32 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
33 if verbose >= 2:
33 if verbose >= 2:
34 print("%s is out of date" % dest)
34 print("%s is out of date" % dest)
35 return True
35 return True
36 if verbose >= 2:
36 if verbose >= 2:
37 print("%s is up to date" % dest)
37 print("%s is up to date" % dest)
38 return False
38 return False
39
39
40
40
41 def _maybe_copy(src, dest, verbose=1):
41 def _maybe_copy(src, dest, verbose=1):
42 """copy a file if it needs updating"""
42 """copy a file if it needs updating"""
43 if _should_copy(src, dest, verbose):
43 if _should_copy(src, dest, verbose):
44 if verbose >= 1:
44 if verbose >= 1:
45 print("copying %s -> %s" % (src, dest))
45 print("copying %s -> %s" % (src, dest))
46 shutil.copy2(src, dest)
46 shutil.copy2(src, dest)
47
47
48
48
49 def _safe_is_tarfile(path):
49 def _safe_is_tarfile(path):
50 """safe version of is_tarfile, return False on IOError"""
50 """safe version of is_tarfile, return False on IOError"""
51 try:
51 try:
52 return tarfile.is_tarfile(path)
52 return tarfile.is_tarfile(path)
53 except IOError:
53 except IOError:
54 return False
54 return False
55
55
56
56
57 def check_nbextension(files, ipython_dir=None):
57 def check_nbextension(files, ipython_dir=None):
58 """Check whether nbextension files have been installed
58 """Check whether nbextension files have been installed
59
59
60 files should be a list of relative paths within nbextensions.
60 files should be a list of relative paths within nbextensions.
61
61
62 Returns True if all files are found, False if any are missing.
62 Returns True if all files are found, False if any are missing.
63 """
63 """
64 ipython_dir = ipython_dir or get_ipython_dir()
64 ipython_dir = ipython_dir or get_ipython_dir()
65 nbext = pjoin(ipython_dir, u'nbextensions')
65 nbext = pjoin(ipython_dir, u'nbextensions')
66 # make sure nbextensions dir exists
66 # make sure nbextensions dir exists
67 if not os.path.exists(nbext):
67 if not os.path.exists(nbext):
68 return False
68 return False
69
69
70 if isinstance(files, string_types):
70 if isinstance(files, string_types):
71 # one file given, turn it into a list
71 # one file given, turn it into a list
72 files = [files]
72 files = [files]
73
73
74 return all(os.path.exists(pjoin(nbext, f)) for f in files)
74 return all(os.path.exists(pjoin(nbext, f)) for f in files)
75
75
76
76
77 def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
77 def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
78 """Install a Javascript extension for the notebook
78 """Install a Javascript extension for the notebook
79
79
80 Stages files and/or directories into IPYTHONDIR/nbextensions.
80 Stages files and/or directories into IPYTHONDIR/nbextensions.
81 By default, this compares modification time, and only stages files that need updating.
81 By default, this compares modification time, and only stages files that need updating.
82 If `overwrite` is specified, matching files are purged before proceeding.
82 If `overwrite` is specified, matching files are purged before proceeding.
83
83
84 Parameters
84 Parameters
85 ----------
85 ----------
86
86
87 files : list(paths or URLs)
87 files : list(paths or URLs)
88 One or more paths or URLs to existing files directories to install.
88 One or more paths or URLs to existing files directories to install.
89 These will be installed with their base name, so '/path/to/foo'
89 These will be installed with their base name, so '/path/to/foo'
90 will install to 'nbextensions/foo'.
90 will install to 'nbextensions/foo'.
91 Archives (zip or tarballs) will be extracted into the nbextensions directory.
91 Archives (zip or tarballs) will be extracted into the nbextensions directory.
92 overwrite : bool [default: False]
92 overwrite : bool [default: False]
93 If True, always install the files, regardless of what may already be installed.
93 If True, always install the files, regardless of what may already be installed.
94 symlink : bool [default: False]
94 symlink : bool [default: False]
95 If True, create a symlink in nbextensions, rather than copying files.
95 If True, create a symlink in nbextensions, rather than copying files.
96 Not allowed with URLs or archives. Windows support for symlinks requires
96 Not allowed with URLs or archives. Windows support for symlinks requires
97 Vista or above, Python 3, and a permission bit which only admin users
97 Vista or above, Python 3, and a permission bit which only admin users
98 have by default, so don't rely on it.
98 have by default, so don't rely on it.
99 ipython_dir : str [optional]
99 ipython_dir : str [optional]
100 The path to an IPython directory, if the default value is not desired.
100 The path to an IPython directory, if the default value is not desired.
101 get_ipython_dir() is used by default.
101 get_ipython_dir() is used by default.
102 verbose : int [default: 1]
102 verbose : int [default: 1]
103 Set verbosity level. The default is 1, where file actions are printed.
103 Set verbosity level. The default is 1, where file actions are printed.
104 set verbose=2 for more output, or verbose=0 for silence.
104 set verbose=2 for more output, or verbose=0 for silence.
105 """
105 """
106
106
107 ipython_dir = ipython_dir or get_ipython_dir()
107 ipython_dir = ipython_dir or get_ipython_dir()
108 nbext = pjoin(ipython_dir, u'nbextensions')
108 nbext = pjoin(ipython_dir, u'nbextensions')
109 # make sure nbextensions dir exists
109 # make sure nbextensions dir exists
110 ensure_dir_exists(nbext)
110 ensure_dir_exists(nbext)
111
111
112 if isinstance(files, string_types):
112 if isinstance(files, string_types):
113 # one file given, turn it into a list
113 # one file given, turn it into a list
114 files = [files]
114 files = [files]
115
115
116 for path in map(cast_unicode_py2, files):
116 for path in map(cast_unicode_py2, files):
117
117
118 if path.startswith(('https://', 'http://')):
118 if path.startswith(('https://', 'http://')):
119 if symlink:
119 if symlink:
120 raise ValueError("Cannot symlink from URLs")
120 raise ValueError("Cannot symlink from URLs")
121 # Given a URL, download it
121 # Given a URL, download it
122 with TemporaryDirectory() as td:
122 with TemporaryDirectory() as td:
123 filename = urlparse(path).path.split('/')[-1]
123 filename = urlparse(path).path.split('/')[-1]
124 local_path = os.path.join(td, filename)
124 local_path = os.path.join(td, filename)
125 if verbose >= 1:
125 if verbose >= 1:
126 print("downloading %s to %s" % (path, local_path))
126 print("downloading %s to %s" % (path, local_path))
127 urlretrieve(path, local_path)
127 urlretrieve(path, local_path)
128 # now install from the local copy
128 # now install from the local copy
129 install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
129 install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
130 continue
130 continue
131
131
132 # handle archives
132 # handle archives
133 archive = None
133 archive = None
134 if path.endswith('.zip'):
134 if path.endswith('.zip'):
135 archive = zipfile.ZipFile(path)
135 archive = zipfile.ZipFile(path)
136 elif _safe_is_tarfile(path):
136 elif _safe_is_tarfile(path):
137 archive = tarfile.open(path)
137 archive = tarfile.open(path)
138
138
139 if archive:
139 if archive:
140 if symlink:
140 if symlink:
141 raise ValueError("Cannot symlink from archives")
141 raise ValueError("Cannot symlink from archives")
142 if verbose >= 1:
142 if verbose >= 1:
143 print("extracting %s to %s" % (path, nbext))
143 print("extracting %s to %s" % (path, nbext))
144 archive.extractall(nbext)
144 archive.extractall(nbext)
145 archive.close()
145 archive.close()
146 continue
146 continue
147
147
148 dest = pjoin(nbext, basename(path))
148 dest = pjoin(nbext, basename(path))
149 if overwrite and os.path.exists(dest):
149 if overwrite and os.path.exists(dest):
150 if verbose >= 1:
150 if verbose >= 1:
151 print("removing %s" % dest)
151 print("removing %s" % dest)
152 if os.path.isdir(dest):
152 if os.path.isdir(dest) and not os.path.islink(dest):
153 shutil.rmtree(dest)
153 shutil.rmtree(dest)
154 else:
154 else:
155 os.remove(dest)
155 os.remove(dest)
156
156
157 if symlink:
157 if symlink:
158 path = os.path.abspath(path)
158 path = os.path.abspath(path)
159 if not os.path.exists(dest):
159 if not os.path.exists(dest):
160 if verbose >= 1:
160 if verbose >= 1:
161 print("symlink %s -> %s" % (dest, path))
161 print("symlink %s -> %s" % (dest, path))
162 os.symlink(path, dest)
162 os.symlink(path, dest)
163 continue
163 continue
164
164
165 if os.path.isdir(path):
165 if os.path.isdir(path):
166 strip_prefix_len = len(path) - len(basename(path))
166 strip_prefix_len = len(path) - len(basename(path))
167 for parent, dirs, files in os.walk(path):
167 for parent, dirs, files in os.walk(path):
168 dest_dir = pjoin(nbext, parent[strip_prefix_len:])
168 dest_dir = pjoin(nbext, parent[strip_prefix_len:])
169 if not os.path.exists(dest_dir):
169 if not os.path.exists(dest_dir):
170 if verbose >= 2:
170 if verbose >= 2:
171 print("making directory %s" % dest_dir)
171 print("making directory %s" % dest_dir)
172 os.makedirs(dest_dir)
172 os.makedirs(dest_dir)
173 for file in files:
173 for file in files:
174 src = pjoin(parent, file)
174 src = pjoin(parent, file)
175 # print("%r, %r" % (dest_dir, file))
175 # print("%r, %r" % (dest_dir, file))
176 dest = pjoin(dest_dir, file)
176 dest = pjoin(dest_dir, file)
177 _maybe_copy(src, dest, verbose)
177 _maybe_copy(src, dest, verbose)
178 else:
178 else:
179 src = path
179 src = path
180 _maybe_copy(src, dest, verbose)
180 _maybe_copy(src, dest, verbose)
181
181
182 #----------------------------------------------------------------------
182 #----------------------------------------------------------------------
183 # install nbextension app
183 # install nbextension app
184 #----------------------------------------------------------------------
184 #----------------------------------------------------------------------
185
185
186 from IPython.utils.traitlets import Bool, Enum
186 from IPython.utils.traitlets import Bool, Enum
187 from IPython.core.application import BaseIPythonApplication
187 from IPython.core.application import BaseIPythonApplication
188
188
189 flags = {
189 flags = {
190 "overwrite" : ({
190 "overwrite" : ({
191 "NBExtensionApp" : {
191 "NBExtensionApp" : {
192 "overwrite" : True,
192 "overwrite" : True,
193 }}, "Force overwrite of existing files"
193 }}, "Force overwrite of existing files"
194 ),
194 ),
195 "debug" : ({
195 "debug" : ({
196 "NBExtensionApp" : {
196 "NBExtensionApp" : {
197 "verbose" : 2,
197 "verbose" : 2,
198 }}, "Extra output"
198 }}, "Extra output"
199 ),
199 ),
200 "quiet" : ({
200 "quiet" : ({
201 "NBExtensionApp" : {
201 "NBExtensionApp" : {
202 "verbose" : 0,
202 "verbose" : 0,
203 }}, "Minimal output"
203 }}, "Minimal output"
204 ),
204 ),
205 "symlink" : ({
205 "symlink" : ({
206 "NBExtensionApp" : {
206 "NBExtensionApp" : {
207 "symlink" : True,
207 "symlink" : True,
208 }}, "Create symlinks instead of copying files"
208 }}, "Create symlinks instead of copying files"
209 ),
209 ),
210 }
210 }
211 flags['s'] = flags['symlink']
211 flags['s'] = flags['symlink']
212
212
213 aliases = {
213 aliases = {
214 "ipython-dir" : "NBExtensionApp.ipython_dir"
214 "ipython-dir" : "NBExtensionApp.ipython_dir"
215 }
215 }
216
216
217 class NBExtensionApp(BaseIPythonApplication):
217 class NBExtensionApp(BaseIPythonApplication):
218 """Entry point for installing notebook extensions"""
218 """Entry point for installing notebook extensions"""
219
219
220 description = """Install IPython notebook extensions
220 description = """Install IPython notebook extensions
221
221
222 Usage
222 Usage
223
223
224 ipython install-nbextension file [more files, folders, archives or urls]
224 ipython install-nbextension file [more files, folders, archives or urls]
225
225
226 This copies files and/or folders into the IPython nbextensions directory.
226 This copies files and/or folders into the IPython nbextensions directory.
227 If a URL is given, it will be downloaded.
227 If a URL is given, it will be downloaded.
228 If an archive is given, it will be extracted into nbextensions.
228 If an archive is given, it will be extracted into nbextensions.
229 If the requested files are already up to date, no action is taken
229 If the requested files are already up to date, no action is taken
230 unless --overwrite is specified.
230 unless --overwrite is specified.
231 """
231 """
232
232
233 examples = """
233 examples = """
234 ipython install-nbextension /path/to/d3.js /path/to/myextension
234 ipython install-nbextension /path/to/d3.js /path/to/myextension
235 """
235 """
236 aliases = aliases
236 aliases = aliases
237 flags = flags
237 flags = flags
238
238
239 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
239 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
240 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
240 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
241 verbose = Enum((0,1,2), default_value=1, config=True,
241 verbose = Enum((0,1,2), default_value=1, config=True,
242 help="Verbosity level"
242 help="Verbosity level"
243 )
243 )
244
244
245 def install_extensions(self):
245 def install_extensions(self):
246 install_nbextension(self.extra_args,
246 install_nbextension(self.extra_args,
247 overwrite=self.overwrite,
247 overwrite=self.overwrite,
248 symlink=self.symlink,
248 symlink=self.symlink,
249 verbose=self.verbose,
249 verbose=self.verbose,
250 ipython_dir=self.ipython_dir,
250 ipython_dir=self.ipython_dir,
251 )
251 )
252
252
253 def start(self):
253 def start(self):
254 if not self.extra_args:
254 if not self.extra_args:
255 nbext = pjoin(self.ipython_dir, u'nbextensions')
255 nbext = pjoin(self.ipython_dir, u'nbextensions')
256 print("Notebook extensions in %s:" % nbext)
256 print("Notebook extensions in %s:" % nbext)
257 for ext in os.listdir(nbext):
257 for ext in os.listdir(nbext):
258 print(u" %s" % ext)
258 print(u" %s" % ext)
259 else:
259 else:
260 self.install_extensions()
260 self.install_extensions()
261
261
262
262
263 if __name__ == '__main__':
263 if __name__ == '__main__':
264 NBExtensionApp.launch_instance()
264 NBExtensionApp.launch_instance()
265 No newline at end of file
265
General Comments 0
You need to be logged in to leave comments. Login now