diff --git a/src/jupyter_contrib_nbextensions/application.py b/src/jupyter_contrib_nbextensions/application.py index e544042..539e7a6 100644 --- a/src/jupyter_contrib_nbextensions/application.py +++ b/src/jupyter_contrib_nbextensions/application.py @@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals +import copy import sys from jupyter_contrib_core.notebook_compat.nbextensions import ArgumentConflict @@ -11,7 +12,9 @@ from tornado.log import LogFormatter from traitlets import Bool, Unicode, default import jupyter_contrib_nbextensions -from jupyter_contrib_nbextensions.install import install, uninstall +from jupyter_contrib_nbextensions.install import ( + install, toggle_install_config, toggle_install_files, uninstall, +) from jupyter_contrib_nbextensions.migrate import migrate @@ -37,7 +40,7 @@ class BaseContribNbextensionsApp(JupyterApp): class BaseContribNbextensionsInstallApp(BaseContribNbextensionsApp): - """Base jupyter_contrib_nbextensions (un)installer app.""" + """Install/Uninstall jupyter_contrib_nbextensions.""" aliases = { 'prefix': 'BaseContribNbextensionsInstallApp.prefix', @@ -70,6 +73,8 @@ class BaseContribNbextensionsInstallApp(BaseContribNbextensionsApp): ), } + _conflicting_flagsets = [['--user', '--system', '--sys-prefix'], ] + 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') @@ -93,11 +98,11 @@ class BaseContribNbextensionsInstallApp(BaseContribNbextensionsApp): Since notebook version doesn't do it very well """ - conflicting_flags = set(['--user', '--system', '--sys-prefix']) - - if len(conflicting_flags.intersection(set(argv))) > 1: - raise ArgumentConflict( - 'cannot specify more than one of user, sys_prefix, or system') + for conflicting_flags in map(set, self._conflicting_flagsets): + if len(conflicting_flags.intersection(set(argv))) > 1: + raise ArgumentConflict( + 'cannot specify more than one of {}'.format( + ', '.join(conflicting_flags))) return super(BaseContribNbextensionsInstallApp, self).parse_command_line(argv) @@ -106,40 +111,61 @@ BaseContribNbextensionsInstallApp.flags['s'] = ( class InstallContribNbextensionsApp(BaseContribNbextensionsInstallApp): - """Install all jupyter_contrib_nbextensions.""" + """Install jupyter_contrib_nbextensions.""" - name = 'jupyter contrib nbextension install' - description = ( - 'Install all jupyter_contrib_nbextensions.' - ) + _toggle_value = True # whether to install or uninstall + + flags = copy.deepcopy(BaseContribNbextensionsInstallApp.flags) + flags.update({ + 'only-config': ( + {'BaseContribNbextensionsInstallApp': {'only_config': True}}, + 'Edit config files, but do not install/remove nbextensions files' + ), + 'only-files': ( + {'BaseContribNbextensionsInstallApp': {'only_files': True}}, + 'Install/remove nbextensions files, but do not edit config files' + ), + }) + + _conflicting_flagsets = ( + BaseContribNbextensionsInstallApp._conflicting_flagsets + + ['--only-config', '--only-files']) + + only_config = Bool(False, config=True, help=( + 'Edit config files, but do not install/remove nbextensions files')) + only_files = Bool(False, config=True, help=( + 'Install/remove nbextensions files, but do not edit config files')) + + @property + def name(self): + return 'jupyter contrib nbextension {}'.format( + 'install' if self._toggle_value else 'uninstall') + + @property + def description(self): + return '{} jupyter_contrib_nbextensions.'.format( + 'Install' if self._toggle_value else 'Uninstall') def start(self): """Perform the App's actions as configured.""" if self.extra_args: sys.exit('{} takes no extra arguments'.format(self.name)) self.log.info('{} {}'.format(self.name, ' '.join(self.argv))) - return install( - user=self.user, sys_prefix=self.sys_prefix, prefix=self.prefix, - nbextensions_dir=self.nbextensions_dir, logger=self.log, - overwrite=self.overwrite, symlink=self.symlink) + kwargs = dict( + user=self.user, sys_prefix=self.sys_prefix, logger=self.log) + kwargs_files = dict(**kwargs) + kwargs_files.update(dict( + prefix=self.prefix, nbextensions_dir=self.nbextensions_dir, + overwrite=self.overwrite, symlink=self.symlink)) + if not self.only_config: + toggle_install_files(self._toggle_value, **kwargs_files) + if not self.only_files: + toggle_install_config(self._toggle_value, **kwargs) -class UninstallContribNbextensionsApp(BaseContribNbextensionsInstallApp): - """Uninstall all jupyter_contrib_nbextensions.""" - - name = 'jupyter contrib nbextension uninstall' - description = ( - 'Uninstall all jupyter_contrib_nbextensions.' - ) - - def start(self): - """Perform the App's actions as configured.""" - if self.extra_args: - sys.exit('{} takes no extra arguments'.format(self.name)) - self.log.info('{} {}'.format(self.name, ' '.join(self.argv))) - return uninstall( - user=self.user, sys_prefix=self.sys_prefix, prefix=self.prefix, - nbextensions_dir=self.nbextensions_dir, logger=self.log) +class UninstallContribNbextensionsApp(InstallContribNbextensionsApp): + """Uninstall jupyter_contrib_nbextensions.""" + _toggle_value = False class MigrateContribNbextensionsApp(BaseContribNbextensionsInstallApp): @@ -169,7 +195,7 @@ class ContribNbextensionsApp(BaseContribNbextensionsApp): examples = '\n'.join(['jupyter contrib nbextension ' + t for t in [ 'install # {}'.format(install.__doc__), 'uninstall # {}'.format(uninstall.__doc__), - 'migrate # {}'.format(migrate.__doc__), + 'migrate # {}'.format(migrate.__doc__), ]]) subcommands = dict( install=(InstallContribNbextensionsApp, install.__doc__), diff --git a/src/jupyter_contrib_nbextensions/install.py b/src/jupyter_contrib_nbextensions/install.py index 37055ad..baf7a70 100644 --- a/src/jupyter_contrib_nbextensions/install.py +++ b/src/jupyter_contrib_nbextensions/install.py @@ -51,41 +51,81 @@ def notebook_is_running(): def toggle_install(install, user=False, sys_prefix=False, overwrite=False, symlink=False, prefix=None, nbextensions_dir=None, logger=None): - """Install or remove all jupyter_contrib_nbextensions.""" + """Install or remove all jupyter_contrib_nbextensions files & config.""" if notebook_is_running(): raise NotebookRunningError( 'Cannot configure while the Jupyter notebook server is running') _check_conflicting_kwargs(user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir) - config_dir = nbextensions._get_config_dir(user=user, sys_prefix=sys_prefix) + toggle_install_files( + install, user=user, sys_prefix=sys_prefix, overwrite=overwrite, + symlink=symlink, prefix=prefix, nbextensions_dir=nbextensions_dir, + logger=logger) + toggle_install_config( + install, user=user, sys_prefix=sys_prefix, logger=logger) - verb = 'Installing' if install else 'Uninstalling' + +def toggle_install_files(install, user=False, sys_prefix=False, logger=None, + overwrite=False, symlink=False, prefix=None, + nbextensions_dir=None): + """Install/remove jupyter_contrib_nbextensions files.""" + if notebook_is_running(): + raise NotebookRunningError( + 'Cannot configure while the Jupyter notebook server is running') + kwargs = dict(user=user, sys_prefix=sys_prefix, prefix=prefix, + nbextensions_dir=nbextensions_dir) + _check_conflicting_kwargs(**kwargs) + kwargs['logger'] = logger if logger: logger.info( - '{} jupyter_contrib_nbextensions, using config in {}'.format( - verb, config_dir)) - - # Configure the jupyter_nbextensions_configurator serverextension to load - if install: - conf_app = EnableJupyterNbextensionsConfiguratorApp( - user=user, sys_prefix=sys_prefix, symlink=symlink, logger=logger) - conf_app.start() - - # nbextensions: - kwargs = dict(user=user, sys_prefix=sys_prefix, prefix=prefix, - nbextensions_dir=nbextensions_dir, logger=logger) + '{} jupyter_contrib_nbextensions nbextension files {} {}'.format( + 'Installing' if install else 'Uninstalling', + 'to' if install else 'from', + 'jupyter data directory')) if install: nbextensions.install_nbextension_python( jupyter_contrib_nbextensions.__name__, overwrite=overwrite, symlink=symlink, **kwargs) - # enable contrib_nbextensions_help_item (item in help menu) - nbextensions.enable_nbextension('notebook', - 'contrib_nbextensions_help_item/main', - user=user, sys_prefix=sys_prefix) else: nbextensions.uninstall_nbextension_python( jupyter_contrib_nbextensions.__name__, **kwargs) + +def toggle_install_config(install, user=False, sys_prefix=False, logger=None): + """Install/remove contrib nbextensions to/from jupyter_nbconvert_config.""" + if notebook_is_running(): + raise NotebookRunningError( + 'Cannot configure while the Jupyter notebook server is running') + _check_conflicting_kwargs(user=user, sys_prefix=sys_prefix) + config_dir = nbextensions._get_config_dir(user=user, sys_prefix=sys_prefix) + if logger: + logger.info( + '{} jupyter_contrib_nbextensions items {} config in {}'.format( + 'Installing' if install else 'Uninstalling', + 'to' if install else 'from', + config_dir)) + + # Configure the jupyter_nbextensions_configurator serverextension to load + if install: + configurator_app = EnableJupyterNbextensionsConfiguratorApp( + user=user, sys_prefix=sys_prefix, logger=logger) + configurator_app.start() + nbextensions.enable_nbextension( + 'notebook', 'contrib_nbextensions_help_item/main', + user=user, sys_prefix=sys_prefix, logger=logger) + else: + nbconf_cm = BaseJSONConfigManager( + config_dir=os.path.join(config_dir, 'nbconfig')) + for require, section in { + 'contrib_nbextensions_help_item/main': 'notebook'}.items(): + if logger: + logger.info('- Disabling {}'.format(require)) + logger.info( + '-- Editing config: {}'.format( + nbconf_cm.file_name(section))) + # disabled_conf['load_extensions'][require] = None + nbconf_cm.update('notebook', {'load_extensions': {require: None}}) + # Set extra template path, pre- and post-processors for nbconvert cm = BaseJSONConfigManager(config_dir=config_dir) config_basename = 'jupyter_nbconvert_config' @@ -120,7 +160,7 @@ def toggle_install(install, user=False, sys_prefix=False, overwrite=False, def install(user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None, overwrite=False, symlink=False): - """Edit jupyter config files to use jupyter_contrib_nbextensions things.""" + """Install all jupyter_contrib_nbextensions files & config.""" return toggle_install( True, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, logger=logger, @@ -129,7 +169,7 @@ def install(user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, def uninstall(user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None): - """Edit jupyter config files to not use jupyter_contrib_nbextensions.""" + """Uninstall all jupyter_contrib_nbextensions files & config.""" return toggle_install( False, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, logger=logger) diff --git a/tests/test_application.py b/tests/test_application.py index 529dfb1..58da2ea 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -148,20 +148,24 @@ class AppTest(TestCase): retcode = proc.poll() nt.assert_equal(retcode, 0, 'command should exit with code 0') - def check_app_install(self, argv=None, dirs=None): + def check_app_install(self, argv=None, dirs=None, dirs_install=None): """Check files were installed in the correct place.""" argv, dirs = self._get_default_check_kwargs(argv, dirs) + if dirs_install is None: + dirs_install = dirs self._call_main_app(argv=['install'] + argv) - installed_files = self._check_install(dirs) + installed_files = self._check_install(dirs_install) self._call_main_app(argv=['uninstall'] + argv) self._check_uninstall(dirs, installed_files) - def check_cli_install(self, argv=None, dirs=None, + def check_cli_install(self, argv=None, dirs=None, dirs_install=None, app_name='jupyter contrib nbextension'): argv, dirs = self._get_default_check_kwargs(argv, dirs) + if dirs_install is None: + dirs_install = dirs args = app_name.split(' ') + ['install'] + argv self._check_subproc(args) - installed_files = self._check_install(dirs) + installed_files = self._check_install(dirs_install) args = app_name.split(' ') + ['uninstall'] + argv self._check_subproc(args) self._check_uninstall(dirs, installed_files) @@ -184,6 +188,8 @@ class AppTest(TestCase): # sys.exit should be called if empty argv specified with nt.assert_raises(SystemExit): main_app([]) + for klass in app_classes: + klass.clear_instance() def test_02_argument_conflict(self): """Check that install objects to multiple flags.""" @@ -197,6 +203,8 @@ class AppTest(TestCase): self.log.info('testing conflicting flagset {}'.format(flagset)) nt.assert_raises(nbextensions.ArgumentConflict, main_app, [subcommand] + list(flagset)) + for klass in app_classes: + klass.clear_instance() def test_03_app_install_defaults(self): """Check that app install works correctly using defaults.""" @@ -265,3 +273,17 @@ class AppTest(TestCase): dirs = self._get_default_check_kwargs()[1] dirs['data'] = self.jupyter_dirs['custom']['data'] self.check_app_install(dirs=dirs, argv=['--prefix=' + dirs['data']]) + + def test_14_app_install_only_files(self): + """Check that install works correctly using --only-files flag.""" + argv, dirs = self._get_default_check_kwargs() + self.check_app_install( + argv=argv + ['--only-files'], dirs=dirs, + dirs_install={'data': dirs['data']}) + + def test_15_app_install_only_config(self): + """Check that install works correctly using --only-config flag.""" + argv, dirs = self._get_default_check_kwargs() + self.check_app_install( + argv=argv + ['--only-config'], dirs=dirs, + dirs_install={'conf': dirs['conf']})