Sfoglia il codice sorgente

Initial labextensions file

Steven Silvester 8 anni fa
parent
commit
e58866b149
1 ha cambiato i file con 1087 aggiunte e 0 eliminazioni
  1. 1087 0
      jupyterlab/labextensions.py

+ 1087 - 0
jupyterlab/labextensions.py

@@ -0,0 +1,1087 @@
+# coding: utf-8
+"""Utilities for installing Javascript extensions for the notebook"""
+
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from __future__ import print_function
+
+import glob
+import json
+import os
+import shutil
+import sys
+import tarfile
+import zipfile
+from os.path import basename, join as pjoin, normpath
+
+try:
+    from urllib.parse import urlparse  # Py3
+    from urllib.request import urlretrieve
+except ImportError:
+    from urlparse import urlparse
+    from urllib import urlretrieve
+
+from jupyter_core.paths import (
+    jupyter_data_dir, jupyter_config_dir, jupyter_config_path,
+    SYSTEM_JUPYTER_PATH, ENV_JUPYTER_PATH, ENV_CONFIG_PATH, SYSTEM_CONFIG_PATH
+)
+from ipython_genutils.path import ensure_dir_exists
+from ipython_genutils.py3compat import string_types, cast_unicode_py2
+from ipython_genutils.tempdir import TemporaryDirectory
+from ._version import __version__
+
+from traitlets.config.manager import BaseJSONConfigManager
+from traitlets.utils.importstring import import_item
+
+from tornado.log import LogFormatter
+
+# Constants for pretty print extension listing function.
+# Window doesn't support coloring in the commandline
+GREEN_ENABLED = '\033[32m enabled \033[0m' if os.name != 'nt' else 'enabled '
+RED_DISABLED = '\033[31mdisabled\033[0m' if os.name != 'nt' else 'disabled'
+
+GREEN_OK = '\033[32mOK\033[0m' if os.name != 'nt' else 'ok'
+RED_X = '\033[31m X\033[0m' if os.name != 'nt' else ' X'
+
+#------------------------------------------------------------------------------
+# Public API
+#------------------------------------------------------------------------------
+
+
+class ArgumentConflict(ValueError):
+    pass
+
+
+def check_labextension(files, user=False, prefix=None, labextensions_dir=None, sys_prefix=False):
+    """Check whether labextension files have been installed
+    
+    Returns True if all files are found, False if any are missing.
+
+    Parameters
+    ----------
+    files : list(paths)
+        a list of relative paths within labextensions.
+    user : bool [default: False]
+        Whether to check the user's .jupyter/labextensions directory.
+        Otherwise check a system-wide install (e.g. /usr/local/share/jupyter/labextensions).
+    prefix : str [optional]
+        Specify install prefix, if it should differ from default (e.g. /usr/local).
+        Will check prefix/share/jupyter/labextensions
+    labextensions_dir : str [optional]
+        Specify absolute path of labextensions directory explicitly.
+    sys_prefix : bool [default: False]
+        Install into the sys.prefix, i.e. environment
+    """
+    labext = _get_labextension_dir(user=user, sys_prefix=sys_prefix, prefix=prefix, labextensions_dir=labextensions_dir)
+    # make sure labextensions dir exists
+    if not os.path.exists(labext):
+        return False
+    
+    if isinstance(files, string_types):
+        # one file given, turn it into a list
+        files = [files]
+    
+    return all(os.path.exists(pjoin(labext, f)) for f in files)
+
+
+def install_labextension(path, name, overwrite=False, symlink=False,
+                        user=False, prefix=None, labextensions_dir=None,
+                        logger=None, sys_prefix=False
+                        ):
+    """Install a Javascript extension for the notebook
+    
+    Stages files and/or directories into the labextensions directory.
+    By default, this compares modification time, and only stages files that need updating.
+    If `overwrite` is specified, matching files are purged before proceeding.
+    
+    Parameters
+    ----------
+    path : path to file, directory, zip or tarball archive, or URL to install
+        Archives (zip or tarballs) will be extracted into the labextensions directory.
+    name : str [optional]
+        name the labextension is installed to.  For example, if name is 'foo', then
+        the source file will be installed to 'labextensions/foo'.
+    overwrite : bool [default: False]
+        If True, always install the files, regardless of what may already be installed.
+    symlink : bool [default: False]
+        If True, create a symlink in labextensions, rather than copying files.
+        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.
+    user : bool [default: False]
+        Whether to install to the user's labextensions directory.
+        Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/labextensions).
+    prefix : str [optional]
+        Specify install prefix, if it should differ from default (e.g. /usr/local).
+        Will install to ``<prefix>/share/jupyter/labextensions``
+    labextensions_dir : str [optional]
+        Specify absolute path of labextensions directory explicitly.
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+
+    # the actual path to which we eventually installed
+    full_dest = None
+
+    labext = _get_labextension_dir(user=user, sys_prefix=sys_prefix, prefix=prefix, labextensions_dir=labextensions_dir)
+    # make sure labextensions dir exists
+    ensure_dir_exists(labext)
+    
+    # forcing symlink parameter to False if os.symlink does not exist (e.g., on Windows machines running python 2)
+    if not hasattr(os, 'symlink'):
+        symlink = False
+    
+    if isinstance(path, (list, tuple)):
+        raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions")
+    
+    path = cast_unicode_py2(path)
+
+    if path.startswith(('https://', 'http://')):
+        raise NotImplementedError('Urls are not yet supported for labextensions')
+    elif path.endswith('.zip') or _safe_is_tarfile(path):
+        raise NotImplementedError('Archive files are not yet supported for labextensions')
+    else:
+        destination = cast_unicode_py2(name)
+        full_dest = normpath(pjoin(labext, destination))
+        if overwrite and os.path.lexists(full_dest):
+            if logger:
+                logger.info("Removing: %s" % full_dest)
+            if os.path.isdir(full_dest) and not os.path.islink(full_dest):
+                shutil.rmtree(full_dest)
+            else:
+                os.remove(full_dest)
+
+        if symlink:
+            path = os.path.abspath(path)
+            if not os.path.exists(full_dest):
+                if logger:
+                    logger.info("Symlinking: %s -> %s" % (full_dest, path))
+                os.symlink(path, full_dest)
+        elif os.path.isdir(path):
+            path = pjoin(os.path.abspath(path), '') # end in path separator
+            for parent, dirs, files in os.walk(path):
+                dest_dir = pjoin(full_dest, parent[len(path):])
+                if not os.path.exists(dest_dir):
+                    if logger:
+                        logger.info("Making directory: %s" % dest_dir)
+                    os.makedirs(dest_dir)
+                for file in files:
+                    src = pjoin(parent, file)
+                    dest_file = pjoin(dest_dir, file)
+                    _maybe_copy(src, dest_file, logger=logger)
+        else:
+            src = path
+            _maybe_copy(src, full_dest, logger=logger)
+
+    return full_dest
+
+
+def install_labextension_python(module, overwrite=False, symlink=False,
+                        user=False, sys_prefix=False, prefix=None, labextensions_dir=None, logger=None):
+    """Install an labextension bundled in a Python package.
+
+    Returns a list of installed/updated directories.
+
+    See install_labextension for parameter information."""
+    m, labexts = _get_labextension_metadata(module)
+    base_path = os.path.split(m.__file__)[0]
+
+    full_dests = []
+
+    for labext in labexts:
+        src = os.path.join(base_path, labext['src'])
+        name = labext['name']
+
+        if logger:
+            logger.info("Installing %s -> %s" % (src, name))
+        full_dest = install_labextension(
+            src, name, overwrite=overwrite, symlink=symlink,
+            user=user, sys_prefix=sys_prefix, prefix=prefix, labextensions_dir=labextensions_dir,
+            logger=logger
+            )
+        validate_labextension_folder(full_dest, logger)
+        full_dests.append(full_dest)
+
+    return full_dests
+
+
+def uninstall_labextension(name, user=False, sys_prefix=False, prefix=None, 
+                          labextensions_dir=None, logger=None):
+    """Uninstall a Javascript extension of the notebook
+    
+    Removes staged files and/or directories in the labextensions directory and 
+    removes the extension from the frontend config.
+    
+    Parameters
+    ----------
+    name: str
+        The name of the labextension.
+    user : bool [default: False]
+        Whether to install to the user's labextensions directory.
+        Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/labextensions).
+    prefix : str [optional]
+        Specify install prefix, if it should differ from default (e.g. /usr/local).
+        Will install to ``<prefix>/share/jupyter/labextensions``
+    labextensions_dir : str [optional]
+        Specify absolute path of labextensions directory explicitly.
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    labext = _get_labextension_dir(user=user, sys_prefix=sys_prefix, prefix=prefix, labextensions_dir=labextensions_dir)
+    dest = cast_unicode_py2(name)
+    full_dest = pjoin(labext, dest)
+    if os.path.lexists(full_dest):
+        if logger:
+            logger.info("Removing: %s" % full_dest)
+        if os.path.isdir(full_dest) and not os.path.islink(full_dest):
+            shutil.rmtree(full_dest)
+        else:
+            os.remove(full_dest)
+    disable_labextension(name)
+
+
+def uninstall_labextension_python(module,
+                        user=False, sys_prefix=False, prefix=None, labextensions_dir=None,
+                        logger=None):
+    """Uninstall an labextension bundled in a Python package.
+    
+    See parameters of `install_labextension_python`
+    """
+    m, labexts = _get_labextension_metadata(module)
+    for labext in labexts:
+        name = labext['name']
+        if logger:
+            logger.info("Uninstalling {} {}".format(name))
+        uninstall_labextension(name, user=user, sys_prefix=sys_prefix, 
+            prefix=prefix, labextensions_dir=labextensions_dir, logger=logger)
+
+
+def _set_labextension_state(name, state,
+                           user=True, sys_prefix=False, logger=None):
+    """Set whether the JupyterLab frontend should use the named labextension
+
+    Returns True if the final state is the one requested.
+
+    Parameters
+    name : string
+        The name of the extension.
+    state : bool
+        The state in which to leave the extension
+    user : bool [default: True]
+        Whether to update the user's .jupyter/labextensions directory
+    sys_prefix : bool [default: False]
+        Whether to update the sys.prefix, i.e. environment. Will override
+        `user`.
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    user = False if sys_prefix else user
+    config_dir = os.path.join(
+        _get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig')
+    cm = BaseJSONConfigManager(config_dir=config_dir)
+    if logger:
+        logger.info("{} extension {}...".format(
+            "Enabling" if state else "Disabling",
+            name
+        ))
+    cfg = cm.get("jupyter_notebook_config")
+    lab_extensions = (
+        cfg.setdefault("LabApp", {})
+        .setdefault("labextensions", {})
+    )
+
+    old_enabled = lab_extensions.get(name, None)
+    new_enabled = state if state is not None else not old_enabled
+
+    if logger:
+        if new_enabled:
+            logger.info(u"Enabling: %s" % (name))
+        else:
+            logger.info(u"Disabling: %s" % (name))
+
+    lab_extensions[name] = new_enabled
+
+    if logger:
+        logger.info(u"- Writing config: {}".format(config_dir))
+
+    cm.update("jupyter_notebook_config", cfg)
+
+    if new_enabled:
+        validate_labextension(name, logger=logger)
+
+    return old_enabled == state
+
+
+def _set_labextension_state_python(state, module, user, sys_prefix,
+                                  logger=None):
+    """Enable or disable some labextensions stored in a Python package
+
+    Returns a list of whether the state was achieved (i.e. changed, or was
+    already right)
+
+    Parameters
+    ----------
+
+    state : Bool
+        Whether the extensions should be enabled
+    module : str
+        Importable Python module exposing the
+        magic-named `_jupyter_labextension_paths` function
+    user : bool
+        Whether to enable in the user's labextensions directory.
+    sys_prefix : bool
+        Enable/disable in the sys.prefix, i.e. environment
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    m, labexts = _get_labextension_metadata(module)
+    return [_set_labextension_state(name=labext["name"],
+                                   state=state,
+                                   user=user, sys_prefix=sys_prefix,
+                                   logger=logger)
+            for labext in labexts]
+
+
+def enable_labextension(name, user=True, sys_prefix=False,
+                       logger=None):
+    """Enable a named labextension
+
+    Returns True if the final state is the one requested.
+
+    Parameters
+    ----------
+    name : string
+        The name of the extension.
+    user : bool [default: True]
+        Whether to enable in the user's labextensions directory.
+    sys_prefix : bool [default: False]
+        Whether to enable in the sys.prefix, i.e. environment. Will override
+        `user`
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    return _set_labextension_state(name=name,
+                                  state=True,
+                                  user=user, sys_prefix=sys_prefix,
+                                  logger=logger)
+
+
+def disable_labextension(name, user=True, sys_prefix=False,
+                        logger=None):
+    """Disable a named labextension
+    
+    Returns True if the final state is the one requested.
+
+    Parameters
+    ----------
+    name : string
+        The name of the extension.
+    user : bool [default: True]
+        Whether to enable in the user's labextensions directory.
+    sys_prefix : bool [default: False]
+        Whether to enable in the sys.prefix, i.e. environment. Will override
+        `user`.
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    return _set_labextension_state(name=name,
+                                  state=False,
+                                  user=user, sys_prefix=sys_prefix,
+                                  logger=logger)
+
+
+def enable_labextension_python(module, user=True, sys_prefix=False,
+                              logger=None):
+    """Enable some labextensions associated with a Python module.
+
+    Returns a list of whether the state was achieved (i.e. changed, or was
+    already right)
+
+    Parameters
+    ----------
+
+    module : str
+        Importable Python module exposing the
+        magic-named `_jupyter_labextension_paths` function
+    user : bool [default: True]
+        Whether to enable in the user's labextensions directory.
+    sys_prefix : bool [default: False]
+        Whether to enable in the sys.prefix, i.e. environment. Will override
+        `user`
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    return _set_labextension_state_python(True, module, user, sys_prefix,
+                                         logger=logger)
+
+
+def disable_labextension_python(module, user=True, sys_prefix=False,
+                               logger=None):
+    """Disable some labextensions associated with a Python module.
+
+    Returns True if the final state is the one requested.
+
+    Parameters
+    ----------
+    module : str
+        Importable Python module exposing the
+        magic-named `_jupyter_labextension_paths` function
+    user : bool [default: True]
+        Whether to enable in the user's labextensions directory.
+    sys_prefix : bool [default: False]
+        Whether to enable in the sys.prefix, i.e. environment
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    return _set_labextension_state_python(False, module, user, sys_prefix,
+                                         logger=logger)
+
+
+def validate_labextension(name, logger=None):
+    """Validate a named labextension.
+
+    Looks across all of the labextension directories.
+
+    Returns a list of warnings.
+
+    Parameters
+    ----------
+    name : str
+        The name of the extension.
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    for exts in _labextension_dirs():
+        full_dest = os.path.join(exts, name)
+        if os.path.exists(full_dest):
+            return validate_labextension_folder(full_dest)
+
+
+def validate_labextension_folder(name, full_dest, logger=None):
+    """Assess the health of an installed labextension
+
+    Returns a list of warnings.
+
+    Parameters
+    ----------
+    full_dest : str
+        The on-disk location of the installed labextension: this should end
+        with `labextensions/<name>`
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    if logger:
+        logger.info("    - Validating...")
+
+    infos = []
+    warnings = []
+
+    hasFiles = True
+    hasEntry = False
+    manifest_files = glob.glob(os.path.join(full_dest, '*.manifest'))
+    if not manifest_files:
+        hasFiles = False
+    for file in manifest_files:
+        with open(file) as fid:
+            manifest = json.load(fid)
+        # Make sure there is at least one entry module and that the entry
+        # module and listed files are present.
+        if ('entry' in manifest and 'modules' in manifest):
+            if (manifest['entry'] in manifest['modules']):
+                hasEntry = True
+        files = manifest.get('files', [])
+        if not files:
+            hasFiles = False
+        for fname in files:
+            path = os.path.join(full_dest, fname)
+            if not os.path.exists(path):
+                hasFiles = False
+    entry_msg = u"     {} has {} entry point?"
+    name = os.path.basename(full_dest)
+    if hasEntry:
+        (entry_msg.format(GREEN_OK, name))
+    else:
+        warnings.append(entry_msg.format(RED_X, name))
+
+    files_msg = u"     {} has necessary files?"
+    if hasFiles:
+        infos.append(files_msg.format(GREEN_OK, name))
+    else:
+        warnings.append(files_msg.format(RED_X, name))
+
+    post_mortem = u"      {} {} {}"
+    if logger:
+        if warnings:
+            [logger.info(info) for info in infos]
+            [logger.warn(warning) for warning in warnings]
+        else:
+            logger.info(post_mortem.format(name, "", GREEN_OK))
+
+    return warnings
+
+
+#----------------------------------------------------------------------
+# Applications
+#----------------------------------------------------------------------
+
+from traitlets import Bool, Unicode, Any
+from jupyter_core.application import JupyterApp
+
+_base_flags = {}
+_base_flags.update(JupyterApp.flags)
+_base_flags.pop("y", None)
+_base_flags.pop("generate-config", None)
+_base_flags.update({
+    "user" : ({
+        "BaseNBExtensionApp" : {
+            "user" : True,
+        }}, "Apply the operation only for the given user"
+    ),
+    "system" : ({
+        "BaseNBExtensionApp" : {
+            "user" : False,
+            "sys_prefix": False,
+        }}, "Apply the operation system-wide"
+    ),
+    "sys-prefix" : ({
+        "BaseNBExtensionApp" : {
+            "sys_prefix" : True,
+        }}, "Use sys.prefix as the prefix for installing labextensions (for environments, packaging)"
+    ),
+    "py" : ({
+        "BaseNBExtensionApp" : {
+            "python" : True,
+        }}, "Install from a Python package"
+    )
+})
+_base_flags['python'] = _base_flags['py']
+
+class BaseNBExtensionApp(JupyterApp):
+    """Base labextension installer app"""
+    _log_formatter_cls = LogFormatter
+    flags = _base_flags
+    version = __version__
+    
+    user = Bool(False, config=True, help="Whether to do a user install")
+    sys_prefix = Bool(False, config=True, help="Use the sys.prefix as the prefix")
+    python = Bool(False, config=True, help="Install from a Python package")
+
+    def _log_format_default(self):
+        """A default format for messages"""
+        return "%(message)s"
+
+
+flags = {}
+flags.update(_base_flags)
+flags.update({
+    "overwrite" : ({
+        "InstallNBExtensionApp" : {
+            "overwrite" : True,
+        }}, "Force overwrite of existing files"
+    ),
+    "symlink" : ({
+        "InstallNBExtensionApp" : {
+            "symlink" : True,
+        }}, "Create symlink instead of copying files"
+    ),
+})
+
+flags['s'] = flags['symlink']
+
+aliases = {
+    "prefix" : "InstallNBExtensionApp.prefix",
+    "labextensions" : "InstallNBExtensionApp.labextensions_dir",
+    "destination" : "InstallNBExtensionApp.destination",
+}
+
+class InstallNBExtensionApp(BaseNBExtensionApp):
+    """Entry point for installing notebook extensions"""
+    description = """Install Jupyter notebook extensions
+    
+    Usage
+    
+        jupyter labextension install path|url [--user|--sys-prefix]
+    
+    This copies a file or a folder into the Jupyter labextensions directory.
+    If a URL is given, it will be downloaded.
+    If an archive is given, it will be extracted into labextensions.
+    If the requested files are already up to date, no action is taken
+    unless --overwrite is specified.
+    """
+    
+    examples = """
+    jupyter labextension install /path/to/myextension
+    """
+    aliases = aliases
+    flags = flags
+    
+    overwrite = Bool(False, config=True, help="Force overwrite of existing files")
+    symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
+
+    prefix = Unicode('', config=True, help="Installation prefix")
+    labextensions_dir = Unicode('', config=True,
+           help="Full path to labextensions dir (probably use prefix or user)")
+    destination = Unicode('', config=True, help="Destination for the copy or symlink")
+
+    def _config_file_name_default(self):
+        """The default config file name."""
+        return 'jupyter_notebook_config'
+    
+    def install_extensions(self):
+        """Perform the installation of labextension(s)"""
+        if len(self.extra_args)>1:
+            raise ValueError("Only one labextension allowed at a time. "
+                         "Call multiple times to install multiple extensions.")
+
+        if self.python:
+            install = install_labextension_python
+            kwargs = {}
+        else:
+            install = install_labextension
+            kwargs = {'destination': self.destination}
+        
+        full_dests = install(self.extra_args[0],
+                             overwrite=self.overwrite,
+                             symlink=self.symlink,
+                             user=self.user,
+                             sys_prefix=self.sys_prefix,
+                             prefix=self.prefix,
+                             labextensions_dir=self.labextensions_dir,
+                             logger=self.log,
+                             **kwargs
+                            )
+
+        if full_dests:
+            self.log.info(
+                u"\nTo initialize this labextension in the browser every time"
+                " JupyterLab loads:\n\n"
+                "      jupyter labextension enable {}{}{}{}\n".format(
+                    self.extra_args[0] if self.python else "<the entry point>",
+                    " --user" if self.user else "",
+                    " --py" if self.python else "",
+                    " --sys-prefix" if self.sys_prefix else ""
+                )
+            )
+
+    def start(self):
+        """Perform the App's function as configured"""
+        if not self.extra_args:
+            sys.exit('Please specify an labextension to install')
+        else:
+            try:
+                self.install_extensions()
+            except ArgumentConflict as e:
+                sys.exit(str(e))
+
+
+class UninstallNBExtensionApp(BaseNBExtensionApp):
+    """Entry point for uninstalling notebook extensions"""
+    version = __version__
+    description = """Uninstall Jupyter notebook extensions
+    
+    Usage
+    
+        jupyter labextension uninstall path/url path/url/entrypoint
+        jupyter labextension uninstall --py pythonPackageName
+    
+    This uninstalls an labextension.
+    """
+    
+    examples = """
+    jupyter labextension uninstall dest/dir dest/dir/extensionjs
+    jupyter labextension uninstall --py extensionPyPackage
+    """
+    
+    aliases = {
+        "prefix" : "UninstallNBExtensionApp.prefix",
+        "labextensions" : "UninstallNBExtensionApp.labextensions_dir",
+        "require": "UninstallNBExtensionApp.require",
+    }
+    
+    prefix = Unicode('', config=True, help="Installation prefix")
+    labextensions_dir = Unicode('', config=True, help="Full path to labextensions dir (probably use prefix or user)")
+    require = Unicode('', config=True, help="require.js module to load.")
+    
+    def _config_file_name_default(self):
+        """The default config file name."""
+        return 'jupyter_notebook_config'
+    
+    def uninstall_extensions(self):
+        """Uninstall some labextensions"""
+        kwargs = {
+            'user': self.user,
+            'sys_prefix': self.sys_prefix,
+            'prefix': self.prefix,
+            'labextensions_dir': self.labextensions_dir,
+            'logger': self.log
+        }
+        
+        arg_count = 1
+        if len(self.extra_args) > arg_count:
+            raise ValueError("only one labextension allowed at a time.  Call multiple times to uninstall multiple extensions.")
+        if len(self.extra_args) < arg_count:
+            raise ValueError("not enough arguments")
+        
+        if self.python:
+            uninstall_labextension_python(self.extra_args[0], **kwargs)
+        else:
+            if self.require:
+                kwargs['require'] = self.require
+            uninstall_labextension(self.extra_args[0], **kwargs)
+    
+    def start(self):
+        if not self.extra_args:
+            sys.exit('Please specify an labextension to uninstall')
+        else:
+            try:
+                self.uninstall_extensions()
+            except ArgumentConflict as e:
+                sys.exit(str(e))
+
+
+class ToggleNBExtensionApp(BaseNBExtensionApp):
+    """A base class for apps that enable/disable extensions"""
+    name = "jupyter labextension enable/disable"
+    version = __version__
+    description = "Enable/disable an labextension in configuration."
+
+    user = Bool(True, config=True, help="Apply the configuration only for the current user (default)")
+    
+    _toggle_value = None
+
+    def _config_file_name_default(self):
+        """The default config file name."""
+        return 'jupyter_notebook_config'
+    
+    def toggle_labextension_python(self, module):
+        """Toggle some extensions in an importable Python module.
+
+        Returns a list of booleans indicating whether the state was changed as
+        requested.
+
+        Parameters
+        ----------
+        module : str
+            Importable Python module exposing the
+            magic-named `_jupyter_labextension_paths` function
+        """
+        toggle = (enable_labextension_python if self._toggle_value
+                  else disable_labextension_python)
+        return toggle(module,
+                      user=self.user,
+                      sys_prefix=self.sys_prefix,
+                      logger=self.log)
+
+    def toggle_labextension(self, name):
+        """Toggle some a named labextension by require-able AMD module.
+
+        Returns whether the state was changed as requested.
+
+        Parameters
+        ----------
+        require : str
+            require.js path used to load the labextension
+        """
+        toggle = (enable_labextension if self._toggle_value
+                  else disable_labextension)
+        return toggle(name,
+                      user=self.user, sys_prefix=self.sys_prefix,
+                      logger=self.log)
+        
+    def start(self):
+        if not self.extra_args:
+            sys.exit('Please specify an labextension/package to enable or disable')
+        elif len(self.extra_args) > 1:
+            sys.exit('Please specify one labextension/package at a time')
+        if self.python:
+            self.toggle_labextension_python(self.extra_args[0])
+        else:
+            self.toggle_labextension(self.extra_args[0])
+
+
+class EnableNBExtensionApp(ToggleNBExtensionApp):
+    """An App that enables labextensions"""
+    name = "jupyter labextension enable"
+    description = """
+    Enable an labextension in frontend configuration.
+    
+    Usage
+        jupyter labextension enable [--system|--sys-prefix]
+    """
+    _toggle_value = True
+
+
+class DisableNBExtensionApp(ToggleNBExtensionApp):
+    """An App that disables labextensions"""
+    name = "jupyter labextension disable"
+    description = """
+    Enable an labextension in frontend configuration.
+    
+    Usage
+        jupyter labextension disable [--system|--sys-prefix]
+    """
+    _toggle_value = None
+
+
+class ListNBExtensionsApp(BaseNBExtensionApp):
+    """An App that lists and validates labextensions"""
+    name = "jupyter labextension list"
+    version = __version__
+    description = "List all labextensions known by the configuration system"
+    
+    def list_labextensions(self):
+        """List all the labextensions"""
+        config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
+        
+        print("Known labextensions:")
+        
+        for config_dir in config_dirs:
+            cm = BaseJSONConfigManager(parent=self, config_dir=config_dir)
+            data = cm.get("jupyter_notebook_config")
+            lab_extensions = (
+                data.setdefault("NotebookApp", {})
+                .setdefault("lab_extensions", {})
+            )
+            if lab_extensions:
+                print(u'config dir: {}'.format(config_dir))
+            for name, enabled in lab_extensions.items():
+                print(u'    {} {}'.format(
+                              name,
+                              GREEN_ENABLED if enabled else RED_DISABLED))
+                validate_labextension(name, self.log)
+
+    def start(self):
+        """Perform the App's functions as configured"""
+        self.list_labextensions()
+
+
+_examples = """
+jupyter labextension list                          # list all configured labextensions
+jupyter labextension install --py <packagename>    # install an labextension from a Python package
+jupyter labextension enable --py <packagename>     # enable all labextensions in a Python package
+jupyter labextension disable --py <packagename>    # disable all labextensions in a Python package
+jupyter labextension uninstall --py <packagename>  # uninstall an labextension in a Python package
+"""
+
+class NBExtensionApp(BaseNBExtensionApp):
+    """Base jupyter labextension command entry point"""
+    name = "jupyter labextension"
+    version = __version__
+    description = "Work with Jupyter notebook extensions"
+    examples = _examples
+
+    subcommands = dict(
+        install=(InstallNBExtensionApp,"Install an labextension"),
+        enable=(EnableNBExtensionApp, "Enable an labextension"),
+        disable=(DisableNBExtensionApp, "Disable an labextension"),
+        uninstall=(UninstallNBExtensionApp, "Uninstall an labextension"),
+        list=(ListNBExtensionsApp, "List labextensions")
+    )
+
+    def start(self):
+        """Perform the App's functions as configured"""
+        super(NBExtensionApp, self).start()
+
+        # The above should have called a subcommand and raised NoStart; if we
+        # get here, it didn't, so we should self.log.info a message.
+        subcmds = ", ".join(sorted(self.subcommands))
+        sys.exit("Please supply at least one subcommand: %s" % subcmds)
+
+main = NBExtensionApp.launch_instance
+
+#------------------------------------------------------------------------------
+# Private API
+#------------------------------------------------------------------------------
+
+
+def _should_copy(src, dest, logger=None):
+    """Should a file be copied, if it doesn't exist, or is newer?
+
+    Returns whether the file needs to be updated.
+
+    Parameters
+    ----------
+
+    src : string
+        A path that should exist from which to copy a file
+    src : string
+        A path that might exist to which to copy a file
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    if not os.path.exists(dest):
+        return True
+    if os.stat(src).st_mtime - os.stat(dest).st_mtime > 1e-6:
+        # we add a fudge factor to work around a bug in python 2.x
+        # that was fixed in python 3.x: http://bugs.python.org/issue12904
+        if logger:
+            logger.warn("Out of date: %s" % dest)
+        return True
+    if logger:
+        logger.info("Up to date: %s" % dest)
+    return False
+
+
+def _maybe_copy(src, dest, logger=None):
+    """Copy a file if it needs updating.
+
+    Parameters
+    ----------
+
+    src : string
+        A path that should exist from which to copy a file
+    src : string
+        A path that might exist to which to copy a file
+    logger : Jupyter logger [optional]
+        Logger instance to use
+    """
+    if _should_copy(src, dest, logger=logger):
+        if logger:
+            logger.info("Copying: %s -> %s" % (src, dest))
+        shutil.copy2(src, dest)
+
+
+def _safe_is_tarfile(path):
+    """Safe version of is_tarfile, return False on IOError.
+
+    Returns whether the file exists and is a tarfile.
+
+    Parameters
+    ----------
+
+    path : string
+        A path that might not exist and or be a tarfile
+    """
+    try:
+        return tarfile.is_tarfile(path)
+    except IOError:
+        return False
+
+
+def _get_labextension_dir(user=False, sys_prefix=False, prefix=None, labextensions_dir=None):
+    """Return the labextension directory specified
+
+    Parameters
+    ----------
+
+    user : bool [default: False]
+        Get the user's .jupyter/labextensions directory
+    sys_prefix : bool [default: False]
+        Get sys.prefix, i.e. ~/.envs/my-env/share/jupyter/labextensions
+    prefix : str [optional]
+        Get custom prefix
+    labextensions_dir : str [optional]
+        Get what you put in
+    """
+    if sum(map(bool, [user, prefix, labextensions_dir, sys_prefix])) > 1:
+        raise ArgumentConflict("cannot specify more than one of user, sys_prefix, prefix, or labextensions_dir")
+    if user:
+        labext = pjoin(jupyter_data_dir(), u'labextensions')
+    elif sys_prefix:
+        labext = pjoin(ENV_JUPYTER_PATH[0], u'labextensions')
+    elif prefix:
+        labext = pjoin(prefix, 'share', 'jupyter', 'labextensions')
+    elif labextensions_dir:
+        labext = labextensions_dir
+    else:
+        labext = pjoin(SYSTEM_JUPYTER_PATH[0], 'labextensions')
+    return labext
+
+
+def _labextension_dirs():
+    """The possible locations of labextensions.
+
+    Returns a list of known base extension locations
+    """
+    return [
+        pjoin(jupyter_data_dir(), u'labextensions'),
+        pjoin(ENV_JUPYTER_PATH[0], u'labextensions'),
+        pjoin(SYSTEM_JUPYTER_PATH[0], 'labextensions')
+    ]
+
+
+def _get_config_dir(user=False, sys_prefix=False):
+    """Get the location of config files for the current context
+
+    Returns the string to the enviornment
+
+    Parameters
+    ----------
+
+    user : bool [default: False]
+        Get the user's .jupyter config directory
+    sys_prefix : bool [default: False]
+        Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
+    """
+    user = False if sys_prefix else user
+    if user and sys_prefix:
+        raise ArgumentConflict("Cannot specify more than one of user or sys_prefix")
+    if user:
+        labext = jupyter_config_dir()
+    elif sys_prefix:
+        labext = ENV_CONFIG_PATH[0]
+    else:
+        labext = SYSTEM_CONFIG_PATH[0]
+    return labext
+
+
+def _get_labextension_metadata(module):
+    """Get the list of labextension paths associated with a Python module.
+
+    Returns a tuple of (the module,             [{
+        'name': 'mockextension',
+        'src': 'static',
+    }])
+
+    Parameters
+    ----------
+
+    module : str
+        Importable Python module exposing the
+        magic-named `_jupyter_labextension_paths` function
+    """
+    m = import_item(module)
+    if not hasattr(m, '_jupyter_labextension_paths'):
+        raise KeyError('The Python module {} is not a valid labextension'.format(module))
+    labexts = m._jupyter_labextension_paths()
+    return m, labexts
+
+
+def _read_config_data(user=False, sys_prefix=False):
+    """Get the config for the current context
+
+    Returns the string to the enviornment
+
+    Parameters
+    ----------
+
+    user : bool [default: False]
+        Get the user's .jupyter config directory
+    sys_prefix : bool [default: False]
+        Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
+    """
+    config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix)
+    config_man = BaseJSONConfigManager(config_dir=config_dir)
+    return config_man.get('jupyter_notebook_config')
+
+
+def _write_config_data(data, user=False, sys_prefix=False):
+    """Update the config for the current context
+
+    Parameters
+    ----------
+    data : object
+        An object which can be accepted by ConfigManager.update
+    user : bool [default: False]
+        Get the user's .jupyter config directory
+    sys_prefix : bool [default: False]
+        Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
+    """
+    config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix)
+    config_man = BaseJSONConfigManager(config_dir=config_dir)
+    config_man.update('jupyter_notebook_config', data)
+
+
+if __name__ == '__main__':
+    main()