diff --git a/IPython/html/nbextensions.py b/IPython/html/nbextensions.py
index d254b76..df4f040 100644
--- a/IPython/html/nbextensions.py
+++ b/IPython/html/nbextensions.py
@@ -8,6 +8,7 @@ from __future__ import print_function
import os
import shutil
+import sys
import tarfile
import zipfile
from os.path import basename, join as pjoin
@@ -25,6 +26,34 @@ from IPython.utils.py3compat import string_types, cast_unicode_py2
from IPython.utils.tempdir import TemporaryDirectory
+# 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:
+ prefixes = ['/usr/local', '/usr']
+
+# add sys.prefix at the front
+if sys.prefix not in prefixes:
+ prefixes.insert(0, sys.prefix)
+
+for prefix in prefixes:
+ nbext = os.path.join(prefix, 'share', 'jupyter', 'nbextensions')
+ 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]
+
+
def _should_copy(src, dest, verbose=1):
"""should a file be copied?"""
if not os.path.exists(dest):
@@ -54,15 +83,17 @@ def _safe_is_tarfile(path):
return False
-def check_nbextension(files, ipython_dir=None):
+def check_nbextension(files, nbextensions=None):
"""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.
"""
- ipython_dir = ipython_dir or get_ipython_dir()
- nbext = pjoin(ipython_dir, u'nbextensions')
+ if nbextensions:
+ nbext = nbextensions
+ else:
+ nbext = pjoin(get_ipython_dir(), u'nbextensions')
# make sure nbextensions dir exists
if not os.path.exists(nbext):
return False
@@ -74,7 +105,7 @@ def check_nbextension(files, ipython_dir=None):
return all(os.path.exists(pjoin(nbext, f)) for f in files)
-def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
+def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions=None, verbose=1):
"""Install a Javascript extension for the notebook
Stages files and/or directories into IPYTHONDIR/nbextensions.
@@ -96,16 +127,29 @@ def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,
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.
- ipython_dir : str [optional]
- The path to an IPython directory, if the default value is not desired.
- get_ipython_dir() is used by default.
+ 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
+ nbextensions : str [optional]
+ Specify absolute path of nbextensions directory explicitly.
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.
"""
-
- ipython_dir = ipython_dir or get_ipython_dir()
- nbext = pjoin(ipython_dir, u'nbextensions')
+ if sum(map(bool, [user, prefix, nbextensions])) > 1:
+ raise ValueError("Cannot specify more than one of user, prefix, or nbextensions.")
+ if user:
+ nbext = pjoin(get_ipython_dir(), u'nbextensions')
+ else:
+ if prefix:
+ nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
+ elif nbextensions:
+ nbext = nbextensions
+ else:
+ nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
# make sure nbextensions dir exists
ensure_dir_exists(nbext)
@@ -126,7 +170,7 @@ def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,
print("downloading %s to %s" % (path, local_path))
urlretrieve(path, local_path)
# now install from the local copy
- install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
+ install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions=nbext, verbose=verbose)
continue
# handle archives
@@ -183,7 +227,7 @@ def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,
# install nbextension app
#----------------------------------------------------------------------
-from IPython.utils.traitlets import Bool, Enum
+from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
from IPython.core.application import BaseIPythonApplication
flags = {
@@ -207,11 +251,18 @@ flags = {
"symlink" : True,
}}, "Create symlinks instead of copying files"
),
+ "user" : ({
+ "NBExtensionApp" : {
+ "user" : True,
+ }}, "Install to the user's IPython directory"
+ ),
}
flags['s'] = flags['symlink']
aliases = {
- "ipython-dir" : "NBExtensionApp.ipython_dir"
+ "ipython-dir" : "NBExtensionApp.ipython_dir",
+ "prefix" : "NBExtensionApp.prefix",
+ "nbextensions" : "NBExtensionApp.nbextensions",
}
class NBExtensionApp(BaseIPythonApplication):
@@ -238,25 +289,36 @@ class NBExtensionApp(BaseIPythonApplication):
overwrite = Bool(False, config=True, help="Force overwrite of existing files")
symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
+ user = Bool(False, config=True, help="Whether to do a user install")
+ prefix = Unicode('', config=True, help="Installation prefix")
+ nbextensions = Unicode('', config=True, help="Full path to nbextensions (probably use prefix or user)")
verbose = Enum((0,1,2), default_value=1, config=True,
help="Verbosity level"
)
+ def check_install():
+ if sum(map(bool, [user, prefix, nbextensions])) > 1:
+ raise TraitError("Cannot specify more than one of user, prefix, or nbextensions.")
+
def install_extensions(self):
install_nbextension(self.extra_args,
overwrite=self.overwrite,
symlink=self.symlink,
verbose=self.verbose,
- ipython_dir=self.ipython_dir,
+ user=self.user,
+ prefix=self.prefix,
+ nbextensions=self.nbextensions,
)
def start(self):
if not self.extra_args:
- nbext = pjoin(self.ipython_dir, u'nbextensions')
- print("Notebook extensions in %s:" % nbext)
- for ext in os.listdir(nbext):
- print(u" %s" % ext)
+ 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)
else:
+ self.check_install()
self.install_extensions()
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index 71883e6..38bbc20 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -90,6 +90,7 @@ from IPython.utils import py3compat
from IPython.utils.path import filefind, get_ipython_dir
from IPython.utils.sysinfo import get_sys_info
+from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
from .utils import url_path_join
#-----------------------------------------------------------------------------
@@ -566,11 +567,14 @@ class NotebookApp(BaseIPythonApplication):
"""return extra paths + the default locations"""
return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
- nbextensions_path = List(Unicode, config=True,
- help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
+ extra_nbextensions_path = List(Unicode, config=True,
+ help="""extra paths to look for Javascript notebook extensions"""
)
- def _nbextensions_path_default(self):
- return [os.path.join(get_ipython_dir(), 'nbextensions')]
+
+ @property
+ def nbextensions_path(self):
+ """The path to look for Javascript notebook extensions"""
+ return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
websocket_url = Unicode("", config=True,
help="""The base URL for websockets,
diff --git a/IPython/html/tests/test_nbextensions.py b/IPython/html/tests/test_nbextensions.py
index f400c8a..1f0351f 100644
--- a/IPython/html/tests/test_nbextensions.py
+++ b/IPython/html/tests/test_nbextensions.py
@@ -1,15 +1,8 @@
# coding: utf-8
"""Test installation of notebook extensions"""
-#-----------------------------------------------------------------------------
-# Copyright (C) 2014 The IPython Development Team
-#
-# Distributed under the terms of the BSD License. The full license is in
-# the file COPYING, distributed as part of this software.
-#-----------------------------------------------------------------------------
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
import glob
import os
@@ -27,9 +20,6 @@ from IPython.utils.tempdir import TemporaryDirectory
from IPython.html import nbextensions
from IPython.html.nbextensions import install_nbextension, check_nbextension
-#-----------------------------------------------------------------------------
-# Test functions
-#-----------------------------------------------------------------------------
def touch(file, mtime=None):
"""ensure a file exists, and set its modification time
@@ -43,7 +33,6 @@ def touch(file, mtime=None):
os.utime(file, (atime, mtime))
return os.stat(file).st_mtime
-
class TestInstallNBExtension(TestCase):
def tempdir(self):
@@ -69,12 +58,15 @@ class TestInstallNBExtension(TestCase):
self.ipdir = self.tempdir()
self.save_get_ipython_dir = nbextensions.get_ipython_dir
nbextensions.get_ipython_dir = lambda : self.ipdir
+ self.save_system_dir = nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR
+ nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = self.tempdir()
def tearDown(self):
+ nbextensions.get_ipython_dir = self.save_get_ipython_dir
+ nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.save_system_dir
for td in self.tempdirs:
td.cleanup()
- nbextensions.get_ipython_dir = self.save_get_ipython_dir
-
+
def assert_dir_exists(self, path):
if not os.path.exists(path):
do_exist = os.listdir(os.path.dirname(path))
@@ -84,21 +76,29 @@ class TestInstallNBExtension(TestCase):
if os.path.exists(path):
self.fail(u"%s should not exist" % path)
- def assert_installed(self, relative_path, ipdir=None):
+ def assert_installed(self, relative_path, user=False):
+ if user:
+ nbext = pjoin(self.ipdir, u'nbextensions')
+ else:
+ nbext = self.system_nbext
self.assert_dir_exists(
- pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
+ pjoin(nbext, relative_path)
)
- def assert_not_installed(self, relative_path, ipdir=None):
+ def assert_not_installed(self, relative_path, user=False):
+ if user:
+ nbext = pjoin(self.ipdir, u'nbextensions')
+ else:
+ nbext = self.system_nbext
self.assert_not_dir_exists(
- pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
+ pjoin(nbext, relative_path)
)
def test_create_ipython_dir(self):
"""install_nbextension when ipython_dir doesn't exist"""
with TemporaryDirectory() as td:
- ipdir = pjoin(td, u'ipython')
- install_nbextension(self.src, ipython_dir=ipdir)
+ self.ipdir = ipdir = pjoin(td, u'ipython')
+ install_nbextension(self.src, user=True)
self.assert_dir_exists(ipdir)
for file in self.files:
self.assert_installed(
@@ -106,12 +106,22 @@ class TestInstallNBExtension(TestCase):
ipdir
)
- def test_create_nbextensions(self):
- with TemporaryDirectory() as ipdir:
- install_nbextension(self.src, ipython_dir=ipdir)
+ def test_create_nbextensions_user(self):
+ with TemporaryDirectory() as td:
+ self.ipdir = ipdir = pjoin(td, u'ipython')
+ install_nbextension(self.src, user=True)
+ self.assert_installed(
+ pjoin(basename(self.src), u'ƒile'),
+ user=True
+ )
+
+ def test_create_nbextensions_system(self):
+ with TemporaryDirectory() as td:
+ nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = pjoin(td, u'nbextensions')
+ install_nbextension(self.src, user=False)
self.assert_installed(
pjoin(basename(self.src), u'ƒile'),
- ipdir
+ user=False
)
def test_single_file(self):
@@ -136,7 +146,7 @@ class TestInstallNBExtension(TestCase):
with open(src, 'w') as f:
f.write('first')
mtime = touch(src)
- dest = pjoin(self.ipdir, u'nbextensions', fname)
+ dest = pjoin(self.system_nbext, fname)
install_nbextension(src)
with open(src, 'w') as f:
f.write('overwrite')
@@ -147,7 +157,6 @@ class TestInstallNBExtension(TestCase):
def test_overwrite_dir(self):
with TemporaryDirectory() as src:
- # src = py3compat.cast_unicode_py2(src)
base = basename(src)
fname = u'ƒ.js'
touch(pjoin(src, fname))
@@ -169,7 +178,7 @@ class TestInstallNBExtension(TestCase):
mtime = touch(src)
install_nbextension(src)
self.assert_installed(fname)
- dest = pjoin(self.ipdir, u'nbextensions', fname)
+ dest = pjoin(self.system_nbext, fname)
old_mtime = os.stat(dest).st_mtime
with open(src, 'w') as f:
f.write('overwrite')
@@ -185,7 +194,7 @@ class TestInstallNBExtension(TestCase):
mtime = touch(src)
install_nbextension(src)
self.assert_installed(fname)
- dest = pjoin(self.ipdir, u'nbextensions', fname)
+ dest = pjoin(self.system_nbext, fname)
old_mtime = os.stat(dest).st_mtime
mtime = touch(src, mtime - 100)
@@ -239,11 +248,12 @@ class TestInstallNBExtension(TestCase):
f = u'ƒ.js'
src = pjoin(d, f)
touch(src)
- install_nbextension(src)
+ install_nbextension(src, user=True)
- assert check_nbextension(f, self.ipdir)
- assert check_nbextension([f], self.ipdir)
- assert not check_nbextension([f, pjoin('dne', f)], self.ipdir)
+ nbext = pjoin(self.ipdir, u'nbextensions')
+ assert check_nbextension(f, nbext)
+ assert check_nbextension([f], nbext)
+ assert not check_nbextension([f, pjoin('dne', f)], nbext)
@dec.skip_win32
def test_install_symlink(self):
@@ -252,7 +262,7 @@ class TestInstallNBExtension(TestCase):
src = pjoin(d, f)
touch(src)
install_nbextension(src, symlink=True)
- dest = pjoin(self.ipdir, u'nbextensions', f)
+ dest = pjoin(self.system_nbext, f)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, src)