# -*- coding: utf-8 -*- """ Functions to remove old installs, and migrate to newer module names. This may change or disappear at any time, so don't rely on it! """ import errno import io import os import shutil import tempfile from jupyter_core.paths import jupyter_config_dir, jupyter_data_dir from notebook.services.config import ConfigManager as FrontendConfigManager from traitlets.config import Config from traitlets.config.manager import BaseJSONConfigManager from jupyter_contrib_nbextensions.install import ( _set_managed_config, _update_config_list, ) def _migrate_require_paths(logger=None): """Migrate require paths from old to new values.""" if logger: logger.info('- Migrating require paths from old to new locations') mappings = { 'notebook': [ ('config/config_menu/main', 'nbextensions_configurator/config_menu/main'), # noqa: E501 ('skill/skill', 'skill/main'), ('yapf_ext/yapf_ext', 'code_prettify/code_prettify'), ] + [(req, req.split('/', 1)[1]) for req in [ 'codemirrormode/skill/skill', 'publishing/gist_it/main', 'publishing/printview/main', 'styling/table_beautifier/main', 'styling/zenmode/main', 'usability/autosavetime/main', 'usability/autoscroll/main', 'usability/chrome-clipboard/main', 'usability/code_font_size/code_font_size', 'usability/codefolding/main', 'usability/collapsible_headings/main', 'usability/comment-uncomment/main', 'usability/datestamper/main', 'usability/dragdrop/main', 'usability/equation-numbering/main', 'usability/execute_time/ExecuteTime', 'usability/exercise/main', 'usability/exercise2/main', 'usability/freeze/main', 'usability/help_panel/help_panel', 'usability/hide_input/main', 'usability/hide_input_all/main', 'usability/highlighter/highlighter', 'usability/hinterland/hinterland', 'usability/init_cell/main', 'usability/keyboard_shortcut_editor/main', 'usability/latex_envs/latex_envs', 'usability/limit_output/main', 'usability/move_selected_cells/main', 'usability/navigation-hotkeys/main', 'usability/notify/notify', 'usability/python-markdown/main', 'usability/qtconsole/qtconsole', 'usability/rubberband/main', 'usability/ruler/main', 'usability/runtools/main', 'usability/scratchpad/main', 'usability/search-replace/main', 'usability/skip-traceback/main', 'usability/spellchecker/main', 'usability/splitcell/splitcell', 'usability/toc2/main', 'usability/toggle_all_line_numbers/main', ]], 'tree': [ ('usability/tree-filter/index', 'tree-filter/index'), ] } fecm = FrontendConfigManager() for section in mappings: conf = fecm.get(section) load_extensions = conf.get('load_extensions', {}) for old, new in mappings[section]: status = load_extensions.pop(old, None) if status is not None: if logger: logger.debug('-- Migrating {!r} -> {!r}'.format(old, new)) load_extensions[new] = status fecm.set(section, conf) def _uninstall_pre_config(logger=None): """Undo config settings inserted by an old installation.""" # for application json config files cm = BaseJSONConfigManager(config_dir=jupyter_config_dir()) # ------------------------------------------------------------------------- # notebook json config config_basename = 'jupyter_notebook_config' config = Config(cm.get(config_basename)) config_path = cm.file_name(config_basename) if config and logger: logger.info('- Removing old config values from {}'.format(config_path)) to_remove = ['nbextensions'] # remove from notebook >= 4.2 key nbserver_extensions section = config.get('NotebookApp', Config()) server_extensions = section.get('nbserver_extensions', {}) for se in to_remove: server_extensions.pop(se, None) if len(server_extensions) == 0: section.pop('nbserver_extensions', None) # and notebook < 4.2 key server_extensions _update_config_list( config, 'NotebookApp.server_extensions', to_remove, False) _update_config_list(config, 'NotebookApp.extra_template_paths', [ os.path.join(jupyter_data_dir(), 'templates'), ], False) _set_managed_config(cm, config_basename, config, logger) # ------------------------------------------------------------------------- # nbconvert json config config_basename = 'jupyter_nbconvert_config' config = Config(cm.get(config_basename)) if config and logger: logger.info('- Removing old config values from {}'.format(config_path)) _update_config_list(config, 'Exporter.template_path', [ '.', os.path.join(jupyter_data_dir(), 'templates'), ], False) _update_config_list(config, 'Exporter.preprocessors', [ 'pre_codefolding.CodeFoldingPreprocessor', 'pre_pymarkdown.PyMarkdownPreprocessor', ], False) section = config.get('NbConvertApp', {}) old_postproc_classes = [ 'post_embedhtml.EmbedPostProcessor', 'jupyter_contrib_nbextensions.nbconvert_support.EmbedPostProcessor', 'jupyter_contrib_nbextensions.nbconvert_support.post_embedhtml.EmbedPostProcessor', # noqa: E501 ] if section.get('postprocessor_class') in old_postproc_classes: section.pop('postprocessor_class', None) if len(section) == 0: config.pop('NbConvertApp', None) _set_managed_config(cm, config_basename, config, logger) # ------------------------------------------------------------------------- # Remove old config lines from .py configuration files for config_basename in ('jupyter_notebook_config.py', 'jupyter_nbconvert_config.py'): py_config_path = os.path.join(jupyter_config_dir(), config_basename) if not os.path.isfile(py_config_path): continue if logger: logger.info( '-- Removing now-empty config file {}'.format(py_config_path)) with io.open(py_config_path, 'r') as f: lines = f.readlines() marker = '#--- nbextensions configuration ---' marker_inds = [ii for ii, l in enumerate(lines) if l.find(marker) >= 0] if len(marker_inds) >= 2: lines = lines[0:marker_inds[0]] + lines[marker_inds[1] + 1:] if [l for l in lines if l.strip]: with io.open(py_config_path, 'w') as f: f.writelines(lines) else: if logger: logger.info( 'Removing now-empty config file {}'.format( py_config_path)) try: os.remove(py_config_path) except OSError as ex: if ex.errno != errno.ENOENT: raise def _uninstall_pre_files(logger=None): """ Remove any files recorded from a previous installation. Rather than actually deleting, this function copies everything to a temporary directory without explicit cleanup, in case a user wants to try manual recovery at some point. The OS can then handle actual removal from the temp directory as and when it chooses to. """ data_dir = jupyter_data_dir() bom_pref = 'ipython-contrib-IPython-notebook-extensions-' bom_path = os.path.join(data_dir, bom_pref + 'installed_files.txt') if not os.path.exists(bom_path): if logger: logger.info('- No list of previously-installed files at {}'.format( bom_path)) return elif logger: logger.info( '- Removing previously-installed files listed in {}'.format( bom_path)) deleted_to = tempfile.mkdtemp(prefix=bom_pref) if logger: logger.info( '-- Files will be copied to the temp directory {}'.format( deleted_to)) with open(bom_path, 'r') as bom_file: for src in bom_file.readlines(): src = src.rstrip('\n').rstrip('\r') if os.path.exists(src): if logger: logger.info(' ' + src) dest = os.path.join( deleted_to, os.path.relpath(src, data_dir)) dest_dir = os.path.dirname(dest) if not os.path.exists(dest_dir): os.makedirs(dest_dir) shutil.move(src, dest) # remove empty directories allowed_errnos = (errno.ENOTDIR, errno.ENOTEMPTY, errno.ENOENT) while len(src) > len(data_dir): src = os.path.dirname(src) try: os.rmdir(src) except OSError as ex: if ex.errno not in allowed_errnos: raise break else: if logger: logger.info(' ' + src) os.remove(bom_path) return deleted_to def _uninstall_pre_pip(logger=None): """Uninstall the old package name from pip.""" old_pkg_name = 'Python-contrib-nbextensions' try: import pip except ImportError: pass logger.info(( "- Couldn't import pip, so can't attempt to " "pip uninstall the old package name {}").format(old_pkg_name)) else: installed_pkg_names = [ pkg.project_name for pkg in pip.get_installed_distributions()] if old_pkg_name not in installed_pkg_names: return if logger: logger.info('- Uninstalling old package name from pip: {}'.format( old_pkg_name)) try: pip.main(['uninstall', '-y', old_pkg_name]) except SystemExit: pass def migrate(logger=None): """Remove an old (pre-jupyter_contrib_nbextensions) install.""" _migrate_require_paths(logger=logger) _uninstall_pre_files(logger=logger) _uninstall_pre_config(logger=logger) _uninstall_pre_pip(logger=logger) def main(): """Allow for running module as a script.""" import logging logger = logging.getLogger('jupyter_contrib_nbextensions.migrate.main') logger.info( 'Retiring pre-jupyter_contrib_nbextensions IPython-notebook-extensions' ) migrate(logger) if __name__ == '__main__': # pragma: no cover """Run module as a script.""" main()