nbextensions.py
327 lines
| 11.3 KiB
| text/x-python
|
PythonLexer
MinRK
|
r15220 | # coding: utf-8 | |
"""Utilities for installing Javascript extensions for the notebook""" | |||
MinRK
|
r16486 | # Copyright (c) IPython Development Team. | |
# Distributed under the terms of the Modified BSD License. | |||
MinRK
|
r15220 | ||
from __future__ import print_function | |||
import os | |||
import shutil | |||
Min RK
|
r19854 | import sys | |
MinRK
|
r15223 | import tarfile | |
import zipfile | |||
MinRK
|
r15220 | from os.path import basename, join as pjoin | |
MinRK
|
r15223 | # Deferred imports | |
try: | |||
from urllib.parse import urlparse # Py3 | |||
from urllib.request import urlretrieve | |||
except ImportError: | |||
from urlparse import urlparse | |||
from urllib import urlretrieve | |||
MinRK
|
r16486 | from IPython.utils.path import get_ipython_dir, ensure_dir_exists | |
MinRK
|
r15220 | from IPython.utils.py3compat import string_types, cast_unicode_py2 | |
MinRK
|
r15223 | from IPython.utils.tempdir import TemporaryDirectory | |
MinRK
|
r15220 | ||
Min RK
|
r19856 | class ArgumentConflict(ValueError): | |
pass | |||
MinRK
|
r15220 | ||
Min RK
|
r19854 | # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely) | |
SYSTEM_NBEXTENSIONS_DIRS = [] | |||
if os.name == 'nt': | |||
programdata = os.environ.get('PROGRAMDATA', None) | |||
if programdata: # PROGRAMDATA is not defined by default on XP. | |||
SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')] | |||
prefixes = [] | |||
else: | |||
Min RK
|
r19857 | prefixes = [os.path.sep + pjoin('usr', 'local'), os.path.sep + 'usr'] | |
Min RK
|
r19854 | ||
# add sys.prefix at the front | |||
if sys.prefix not in prefixes: | |||
prefixes.insert(0, sys.prefix) | |||
for prefix in prefixes: | |||
Min RK
|
r19857 | nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions') | |
Min RK
|
r19854 | if nbext not in SYSTEM_NBEXTENSIONS_DIRS: | |
SYSTEM_NBEXTENSIONS_DIRS.append(nbext) | |||
if os.name == 'nt': | |||
# PROGRAMDATA | |||
SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1] | |||
else: | |||
# /usr/local | |||
SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2] | |||
MinRK
|
r15220 | def _should_copy(src, dest, verbose=1): | |
"""should a file be copied?""" | |||
if not os.path.exists(dest): | |||
return True | |||
if os.stat(dest).st_mtime < os.stat(src).st_mtime: | |||
if verbose >= 2: | |||
print("%s is out of date" % dest) | |||
return True | |||
if verbose >= 2: | |||
print("%s is up to date" % dest) | |||
return False | |||
def _maybe_copy(src, dest, verbose=1): | |||
"""copy a file if it needs updating""" | |||
if _should_copy(src, dest, verbose): | |||
if verbose >= 1: | |||
print("copying %s -> %s" % (src, dest)) | |||
shutil.copy2(src, dest) | |||
MinRK
|
r15223 | def _safe_is_tarfile(path): | |
"""safe version of is_tarfile, return False on IOError""" | |||
try: | |||
return tarfile.is_tarfile(path) | |||
except IOError: | |||
return False | |||
Min RK
|
r19855 | def check_nbextension(files, nbextensions_dir=None): | |
MinRK
|
r15227 | """Check whether nbextension files have been installed | |
files should be a list of relative paths within nbextensions. | |||
Returns True if all files are found, False if any are missing. | |||
""" | |||
Min RK
|
r19855 | if nbextensions_dir: | |
nbext = nbextensions_dir | |||
Min RK
|
r19854 | else: | |
nbext = pjoin(get_ipython_dir(), u'nbextensions') | |||
MinRK
|
r15227 | # make sure nbextensions dir exists | |
if not os.path.exists(nbext): | |||
return False | |||
if isinstance(files, string_types): | |||
# one file given, turn it into a list | |||
files = [files] | |||
MinRK
|
r15340 | return all(os.path.exists(pjoin(nbext, f)) for f in files) | |
MinRK
|
r15227 | ||
Min RK
|
r19855 | def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1): | |
MinRK
|
r15220 | """Install a Javascript extension for the notebook | |
Min RK
|
r19855 | Stages files and/or directories into the nbextensions directory. | |
MinRK
|
r15223 | By default, this compares modification time, and only stages files that need updating. | |
MinRK
|
r15220 | If `overwrite` is specified, matching files are purged before proceeding. | |
Parameters | |||
---------- | |||
MinRK
|
r15223 | files : list(paths or URLs) | |
One or more paths or URLs to existing files directories to install. | |||
MinRK
|
r15220 | These will be installed with their base name, so '/path/to/foo' | |
will install to 'nbextensions/foo'. | |||
MinRK
|
r15223 | Archives (zip or tarballs) will be extracted into the nbextensions directory. | |
MinRK
|
r15220 | overwrite : bool [default: False] | |
If True, always install the files, regardless of what may already be installed. | |||
MinRK
|
r15226 | symlink : bool [default: False] | |
If True, create a symlink in nbextensions, rather than copying files. | |||
Thomas Kluyver
|
r18725 | Not allowed with URLs or archives. Windows support for symlinks requires | |
Vista or above, Python 3, and a permission bit which only admin users | |||
have by default, so don't rely on it. | |||
Min RK
|
r19854 | user : bool [default: False] | |
Whether to install to the user's .ipython/nbextensions directory. | |||
Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions). | |||
prefix : str [optional] | |||
Specify install prefix, if it should differ from default (e.g. /usr/local). | |||
Will install to prefix/share/jupyter/nbextensions | |||
Min RK
|
r19855 | nbextensions_dir : str [optional] | |
Min RK
|
r19854 | Specify absolute path of nbextensions directory explicitly. | |
MinRK
|
r15220 | verbose : int [default: 1] | |
Set verbosity level. The default is 1, where file actions are printed. | |||
set verbose=2 for more output, or verbose=0 for silence. | |||
""" | |||
Min RK
|
r19855 | if sum(map(bool, [user, prefix, nbextensions_dir])) > 1: | |
Min RK
|
r19856 | raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.") | |
Min RK
|
r19854 | if user: | |
nbext = pjoin(get_ipython_dir(), u'nbextensions') | |||
else: | |||
if prefix: | |||
nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions') | |||
Min RK
|
r19855 | elif nbextensions_dir: | |
nbext = nbextensions_dir | |||
Min RK
|
r19854 | else: | |
nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR | |||
MinRK
|
r15220 | # make sure nbextensions dir exists | |
MinRK
|
r16486 | ensure_dir_exists(nbext) | |
MinRK
|
r15220 | ||
if isinstance(files, string_types): | |||
# one file given, turn it into a list | |||
files = [files] | |||
for path in map(cast_unicode_py2, files): | |||
MinRK
|
r15223 | ||
if path.startswith(('https://', 'http://')): | |||
MinRK
|
r15226 | if symlink: | |
raise ValueError("Cannot symlink from URLs") | |||
MinRK
|
r15223 | # Given a URL, download it | |
with TemporaryDirectory() as td: | |||
filename = urlparse(path).path.split('/')[-1] | |||
local_path = os.path.join(td, filename) | |||
if verbose >= 1: | |||
print("downloading %s to %s" % (path, local_path)) | |||
urlretrieve(path, local_path) | |||
# now install from the local copy | |||
Min RK
|
r19855 | install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose) | |
MinRK
|
r15223 | continue | |
# handle archives | |||
archive = None | |||
if path.endswith('.zip'): | |||
archive = zipfile.ZipFile(path) | |||
elif _safe_is_tarfile(path): | |||
archive = tarfile.open(path) | |||
if archive: | |||
MinRK
|
r15226 | if symlink: | |
MinRK
|
r15339 | raise ValueError("Cannot symlink from archives") | |
MinRK
|
r15223 | if verbose >= 1: | |
print("extracting %s to %s" % (path, nbext)) | |||
archive.extractall(nbext) | |||
archive.close() | |||
continue | |||
MinRK
|
r15220 | dest = pjoin(nbext, basename(path)) | |
if overwrite and os.path.exists(dest): | |||
if verbose >= 1: | |||
print("removing %s" % dest) | |||
Jason Grout
|
r18978 | if os.path.isdir(dest) and not os.path.islink(dest): | |
MinRK
|
r15220 | shutil.rmtree(dest) | |
else: | |||
os.remove(dest) | |||
MinRK
|
r15226 | ||
if symlink: | |||
path = os.path.abspath(path) | |||
if not os.path.exists(dest): | |||
if verbose >= 1: | |||
print("symlink %s -> %s" % (dest, path)) | |||
os.symlink(path, dest) | |||
continue | |||
MinRK
|
r15220 | ||
if os.path.isdir(path): | |||
strip_prefix_len = len(path) - len(basename(path)) | |||
for parent, dirs, files in os.walk(path): | |||
dest_dir = pjoin(nbext, parent[strip_prefix_len:]) | |||
if not os.path.exists(dest_dir): | |||
if verbose >= 2: | |||
print("making directory %s" % dest_dir) | |||
os.makedirs(dest_dir) | |||
for file in files: | |||
src = pjoin(parent, file) | |||
# print("%r, %r" % (dest_dir, file)) | |||
dest = pjoin(dest_dir, file) | |||
_maybe_copy(src, dest, verbose) | |||
else: | |||
src = path | |||
_maybe_copy(src, dest, verbose) | |||
MinRK
|
r15221 | ||
#---------------------------------------------------------------------- | |||
# install nbextension app | |||
#---------------------------------------------------------------------- | |||
Min RK
|
r19854 | from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError | |
MinRK
|
r15221 | from IPython.core.application import BaseIPythonApplication | |
flags = { | |||
"overwrite" : ({ | |||
"NBExtensionApp" : { | |||
"overwrite" : True, | |||
}}, "Force overwrite of existing files" | |||
), | |||
"debug" : ({ | |||
"NBExtensionApp" : { | |||
"verbose" : 2, | |||
}}, "Extra output" | |||
), | |||
"quiet" : ({ | |||
"NBExtensionApp" : { | |||
"verbose" : 0, | |||
}}, "Minimal output" | |||
), | |||
MinRK
|
r15226 | "symlink" : ({ | |
"NBExtensionApp" : { | |||
"symlink" : True, | |||
}}, "Create symlinks instead of copying files" | |||
), | |||
Min RK
|
r19854 | "user" : ({ | |
"NBExtensionApp" : { | |||
"user" : True, | |||
}}, "Install to the user's IPython directory" | |||
), | |||
MinRK
|
r15221 | } | |
MinRK
|
r15226 | flags['s'] = flags['symlink'] | |
MinRK
|
r15221 | aliases = { | |
Min RK
|
r19854 | "ipython-dir" : "NBExtensionApp.ipython_dir", | |
"prefix" : "NBExtensionApp.prefix", | |||
Min RK
|
r19855 | "nbextensions" : "NBExtensionApp.nbextensions_dir", | |
MinRK
|
r15221 | } | |
class NBExtensionApp(BaseIPythonApplication): | |||
"""Entry point for installing notebook extensions""" | |||
description = """Install IPython notebook extensions | |||
Usage | |||
MinRK
|
r15223 | ipython install-nbextension file [more files, folders, archives or urls] | |
MinRK
|
r15221 | ||
This copies files and/or folders into the IPython nbextensions directory. | |||
MinRK
|
r15223 | If a URL is given, it will be downloaded. | |
If an archive is given, it will be extracted into nbextensions. | |||
MinRK
|
r15221 | If the requested files are already up to date, no action is taken | |
unless --overwrite is specified. | |||
""" | |||
examples = """ | |||
ipython install-nbextension /path/to/d3.js /path/to/myextension | |||
""" | |||
aliases = aliases | |||
flags = flags | |||
overwrite = Bool(False, config=True, help="Force overwrite of existing files") | |||
MinRK
|
r15226 | symlink = Bool(False, config=True, help="Create symlinks instead of copying files") | |
Min RK
|
r19854 | user = Bool(False, config=True, help="Whether to do a user install") | |
prefix = Unicode('', config=True, help="Installation prefix") | |||
Min RK
|
r19855 | nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)") | |
MinRK
|
r15221 | verbose = Enum((0,1,2), default_value=1, config=True, | |
help="Verbosity level" | |||
) | |||
def install_extensions(self): | |||
install_nbextension(self.extra_args, | |||
overwrite=self.overwrite, | |||
MinRK
|
r15226 | symlink=self.symlink, | |
MinRK
|
r15221 | verbose=self.verbose, | |
Min RK
|
r19854 | user=self.user, | |
prefix=self.prefix, | |||
Min RK
|
r19855 | nbextensions_dir=self.nbextensions_dir, | |
MinRK
|
r15221 | ) | |
def start(self): | |||
if not self.extra_args: | |||
Min RK
|
r19854 | for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS: | |
if os.path.exists(nbext): | |||
print("Notebook extensions in %s:" % nbext) | |||
for ext in os.listdir(nbext): | |||
print(u" %s" % ext) | |||
MinRK
|
r15221 | else: | |
Min RK
|
r19856 | try: | |
self.install_extensions() | |||
except ArgumentConflict as e: | |||
print(str(e), file=sys.stderr) | |||
self.exit(1) | |||
MinRK
|
r15221 | ||
if __name__ == '__main__': | |||
NBExtensionApp.launch_instance() | |||