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