From 3132ddd6f71ac2c169bbb4a263a76a7180b76628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 3 Mar 2021 09:19:08 +0100 Subject: [PATCH 1/6] Port to JupyterLab 3 --- .eslintignore | 5 + .eslintrc.js | 39 + .github/workflows/build.yml | 42 +- .gitignore | 6 +- .prettierignore | 5 + .prettierrc | 5 + MANIFEST.in | 8 +- binder/environment.yml | 20 +- binder/postBuild | 27 +- install.json | 5 + .../jupyterlab_snippets.json} | 4 +- .../jupyterlab_snippets.json | 7 + jupyterlab-snippets/__init__.py | 21 - jupyterlab-snippets/_version.py | 2 - jupyterlab_snippets/__init__.py | 35 + jupyterlab_snippets/_version.py | 19 + .../handlers.py | 0 .../loader.py | 0 package.json | 88 ++- pyproject.toml | 3 + setup.py | 76 +- setupbase.py | 710 ------------------ style/base.css | 0 style/index.css | 1 + style/index.js | 1 + 25 files changed, 308 insertions(+), 821 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .prettierignore create mode 100644 .prettierrc mode change 100644 => 100755 binder/postBuild create mode 100644 install.json rename jupyter-config/{jupyterlab-snippets.json => jupyter_notebook_config.d/jupyterlab_snippets.json} (62%) create mode 100644 jupyter-config/jupyter_server_config.d/jupyterlab_snippets.json delete mode 100644 jupyterlab-snippets/__init__.py delete mode 100644 jupyterlab-snippets/_version.py create mode 100644 jupyterlab_snippets/__init__.py create mode 100644 jupyterlab_snippets/_version.py rename {jupyterlab-snippets => jupyterlab_snippets}/handlers.py (100%) rename {jupyterlab-snippets => jupyterlab_snippets}/loader.py (100%) create mode 100644 pyproject.toml delete mode 100644 setupbase.py create mode 100644 style/base.css create mode 100644 style/index.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..5c99ba7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +dist +coverage +**/*.d.ts +tests diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..d66148c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,39 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module' + }, + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/naming-convention': [ + 'error', + { + 'selector': 'interface', + 'format': ['PascalCase'], + 'custom': { + 'regex': '^I[A-Z]', + 'match': true + } + } + ], + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: false } + ], + curly: ['error', 'all'], + eqeqeq: 'error', + 'prefer-arrow-callback': 'error' + } +}; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0def3b3..ecca219 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,22 +11,48 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: Install node uses: actions/setup-node@v1 with: - node-version: '10.x' + node-version: '12.x' - name: Install Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: '3.7' architecture: 'x64' + + + - name: Setup pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: pip-3.7-${{ hashFiles('package.json') }} + restore-keys: | + pip-3.7- + pip- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Setup yarn cache + uses: actions/cache@v2 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + yarn- + - name: Install dependencies - run: python -m pip install jupyterlab + run: python -m pip install -U jupyterlab~=3.0 jupyter_packaging~=0.7.9 - name: Build the extension run: | - pip install . - jupyter lab build - jupyter serverextension list - jupyter labextension list + jlpm + jlpm run eslint:check + python -m pip install . + + jupyter server extension list 2>&1 | grep -ie "jupyterlab-snippets.*OK" + + jupyter labextension list 2>&1 | grep -ie "jupyterlab-snippets.*OK" python -m jupyterlab.browser_check diff --git a/.gitignore b/.gitignore index 4b0e8a3..0ffc032 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ *.bundle.* lib/ node_modules/ +*.egg-info/ .ipynb_checkpoints *.tsbuildinfo +jupyterlab_snippets/labextension # Created by https://www.gitignore.io/api/python # Edit at https://www.gitignore.io/?templates=python @@ -32,7 +34,6 @@ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ -*.egg-info/ .installed.cfg *.egg MANIFEST @@ -107,5 +108,8 @@ dmypy.json # End of https://www.gitignore.io/api/python +# OSX files +.DS_Store + yarn.lock .vscode diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6767615 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules +**/node_modules +**/lib +**/package.json +jupyterlab-snippets diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b0a179d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid" +} diff --git a/MANIFEST.in b/MANIFEST.in index e18d8c6..cfcc808 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,14 @@ include LICENSE include README.md - -include setupbase.py +include pyproject.toml include jupyter-config/jupyterlab-snippets.json include package.json +include install.json include ts*.json -include jupyterlab-snippets/labextension/*.tgz +include yarn.lock + +graft jupyterlab_snippets/labextension # Javascript files graft src diff --git a/binder/environment.yml b/binder/environment.yml index e3ba361..407a028 100644 --- a/binder/environment.yml +++ b/binder/environment.yml @@ -1,7 +1,19 @@ -name: jupyterlan-snippets +# a mybinder.org-ready environment for demoing jupyterlab-snippets +# this environment may also be used locally on Linux/MacOS/Windows, e.g. +# +# conda env update --file binder/environment.yml +# conda activate jupyterlab-snippets-demo +# +name: jupyterlab-snippets-demo + channels: - conda-forge + dependencies: - - jupyterlab-snippets=0.3.2 - - jupyterlab >=2 - - nodejs \ No newline at end of file + # runtime dependencies + - python >=3.8,<3.9.0a0 + - jupyterlab >=3,<4.0.0a0 + # labextension build dependencies + - nodejs >=14,<15 + - pip + - wheel diff --git a/binder/postBuild b/binder/postBuild old mode 100644 new mode 100755 index 412312e..6dae9bb --- a/binder/postBuild +++ b/binder/postBuild @@ -1,6 +1,9 @@ -#!/bin/bash +#!/usr/bin/env sh +""" perform a development install of jupyterlab-snippets -# install snippets + On Binder, this will run _after_ the environment has been fully created from + the environment.yml in this directory. +""" cd jupyter-boilerplate-converter git clone git://github.com/moble/jupyter_boilerplate @@ -23,7 +26,23 @@ cd ../binder ln -s $SNIPPET_DIR snippets_symlink -# prepare Jupyter Lab +cd .. -jupyter lab build +# verify the environment is self-consistent before even starting +python -m pip check +# install the labextension +python -m pip install -e . + +# verify the environment the extension didn't break anything +python -m pip check + +# list the extensions +jupyter server extension list + +# initially list installed extensions to determine if there are any surprises +jupyter labextension list + + +echo "JupyterLab with jupyterlab-snippets is ready to run with:" +echo " jupyter lab" diff --git a/install.json b/install.json new file mode 100644 index 0000000..4778699 --- /dev/null +++ b/install.json @@ -0,0 +1,5 @@ +{ + "packageManager": "python", + "packageName": "jupyterlab-snippets", + "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab-snippets" +} diff --git a/jupyter-config/jupyterlab-snippets.json b/jupyter-config/jupyter_notebook_config.d/jupyterlab_snippets.json similarity index 62% rename from jupyter-config/jupyterlab-snippets.json rename to jupyter-config/jupyter_notebook_config.d/jupyterlab_snippets.json index d8ecb9c..b09ab5d 100644 --- a/jupyter-config/jupyterlab-snippets.json +++ b/jupyter-config/jupyter_notebook_config.d/jupyterlab_snippets.json @@ -1,7 +1,7 @@ { "NotebookApp": { "nbserver_extensions": { - "jupyterlab-snippets": true + "jupyterlab_snippets": true } } -} +} \ No newline at end of file diff --git a/jupyter-config/jupyter_server_config.d/jupyterlab_snippets.json b/jupyter-config/jupyter_server_config.d/jupyterlab_snippets.json new file mode 100644 index 0000000..917c055 --- /dev/null +++ b/jupyter-config/jupyter_server_config.d/jupyterlab_snippets.json @@ -0,0 +1,7 @@ +{ + "ServerApp": { + "jpserver_extensions": { + "jupyterlab_snippets": true + } + } +} diff --git a/jupyterlab-snippets/__init__.py b/jupyterlab-snippets/__init__.py deleted file mode 100644 index 1905097..0000000 --- a/jupyterlab-snippets/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from ._version import __version__ -from .handlers import setup_handlers -from .loader import SnippetsLoader - - -def _jupyter_server_extension_paths(): - return [{ - 'module': 'jupyterlab-snippets' - }] - - -def load_jupyter_server_extension(nb_app): - """Registers the API handler to receive HTTP requests from the frontend extension. - Parameters - ---------- - nb_app: notebook.notebookapp.NotebookApp - Notebook application instance - """ - - loader = SnippetsLoader() - setup_handlers(nb_app.web_app, loader) diff --git a/jupyterlab-snippets/_version.py b/jupyterlab-snippets/_version.py deleted file mode 100644 index 4c7a37e..0000000 --- a/jupyterlab-snippets/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -version_info = (0, 3, 2) -__version__ = ".".join(map(str, version_info)) diff --git a/jupyterlab_snippets/__init__.py b/jupyterlab_snippets/__init__.py new file mode 100644 index 0000000..763244a --- /dev/null +++ b/jupyterlab_snippets/__init__.py @@ -0,0 +1,35 @@ +import json +from pathlib import Path + +from ._version import __version__ +from .handlers import setup_handlers +from .loader import SnippetsLoader + +HERE = Path(__file__).parent.resolve() + +with (HERE / "labextension" / "package.json").open() as fid: + data = json.load(fid) + + +def _jupyter_labextension_paths(): + return [{"src": "labextension", "dest": data["name"]}] + + +def _jupyter_server_extension_points(): + return [{"module": "jupyterlab_snippets"}] + + +def _load_jupyter_server_extension(server_app): + """Registers the API handler to receive HTTP requests from the frontend extension. + + Parameters + ---------- + server_app: jupyterlab.labapp.LabApp + JupyterLab application instance + """ + loader = SnippetsLoader() + setup_handlers(server_app.web_app, loader) + + +# For backward compatibility with the classical notebook +load_jupyter_server_extension = _load_jupyter_server_extension diff --git a/jupyterlab_snippets/_version.py b/jupyterlab_snippets/_version.py new file mode 100644 index 0000000..b96d38b --- /dev/null +++ b/jupyterlab_snippets/_version.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +__all__ = ["__version__"] + +def _fetchVersion(): + HERE = Path(__file__).parent.resolve() + + for settings in HERE.rglob("package.json"): + try: + with settings.open() as f: + return json.load(f)["version"] + except FileNotFoundError: + pass + + raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}") + +__version__ = _fetchVersion() + diff --git a/jupyterlab-snippets/handlers.py b/jupyterlab_snippets/handlers.py similarity index 100% rename from jupyterlab-snippets/handlers.py rename to jupyterlab_snippets/handlers.py diff --git a/jupyterlab-snippets/loader.py b/jupyterlab_snippets/loader.py similarity index 100% rename from jupyterlab-snippets/loader.py rename to jupyterlab_snippets/loader.py diff --git a/package.json b/package.json index 0a0b671..36b027f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jupyterlab-snippets", - "version": "0.3.2", + "version": "0.4.0", "description": "Snippets Extension for JupyterLab", "keywords": [ "jupyter", @@ -12,10 +12,14 @@ "url": "https://github.com/QuantStack/jupyterlab-snippets/issues" }, "license": "BSD-3-Clause", - "author": "QuantStack", + "author": { + "name": "QuantStack", + "email": "" + }, "files": [ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", - "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", + "style/index.js" ], "main": "lib/index.js", "types": "lib/index.d.ts", @@ -25,46 +29,64 @@ "url": "https://github.com/QuantStack/jupyterlab-snippets.git" }, "scripts": { - "build": "jlpm run build:lib", - "build:labextension": "cd jupyterlab-snippets && rimraf labextension && mkdirp labextension && cd labextension && npm pack ../..", - "build:lib": "tsc", + "build": "jlpm run build:lib && jlpm run build:labextension:dev", "build:all": "jlpm run build:labextension", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc", + "build:prod": "jlpm run build:lib && jlpm run build:labextension", "clean": "jlpm run clean:lib", - "clean:lib": "rimraf lib tsconfig.tsbuildinfo", - "clean:labextension": "rimraf jupyterlab-snippets/labextension", "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", - "prepare": "jlpm run clean && jlpm run build", - "watch": "tsc -w" + "clean:labextension": "rimraf jupyterlab-snippets/labextension", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "install:extension": "jupyter labextension develop --overwrite .", + "prepare": "jlpm run clean && jlpm run build:prod", + "watch": "run-p watch:src watch:labextension", + "watch:labextension": "jupyter labextension watch .", + "watch:src": "tsc -w" }, "dependencies": { - "@jupyterlab/application": "^2.0.2", - "@jupyterlab/apputils": "^2.0.2", - "@jupyterlab/coreutils": "^4.0.2", - "@jupyterlab/mainmenu": "^2.0.2", - "@jupyterlab/notebook": "^2.0.2", - "@jupyterlab/services": "^5.0.2", - "@lumino/commands": "^1.9.2", - "@lumino/widgets": "^1.10.0" + "@jupyterlab/application": "^3.0.4", + "@jupyterlab/apputils": "^3.0.3", + "@jupyterlab/coreutils": "^5.0.2", + "@jupyterlab/mainmenu": "^3.0.3", + "@jupyterlab/notebook": "^3.0.4", + "@jupyterlab/services": "^6.0.3", + "@lumino/commands": "^1.12.0", + "@lumino/widgets": "^1.16.1" }, "devDependencies": { - "mkdirp": "^0.5.1", - "rimraf": "^3.0.0", - "typescript": "~3.7.2" + "@jupyterlab/builder": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^4.8.1", + "@typescript-eslint/parser": "^4.8.1", + "eslint": "^7.14.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-prettier": "^3.1.4", + "mkdirp": "^1.0.3", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "rimraf": "^3.0.2", + "typescript": "~4.1.3" }, "sideEffects": [ - "style/*.css" + "style/*.css", + "style/index.js" ], "jupyterlab": { "discovery": { - "server": { - "managers": [ - "pip" - ], - "base": { - "name": "jupyterlab-snippets" - } + "server": { + "managers": [ + "pip" + ], + "base": { + "name": "jupyterlab-snippets" } - }, - "extension": true - } -} + } + }, + "extension": true, + "outputDir": "jupyterlab_snippets/labextension" + }, + "styleModule": "style/index.js" +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ba04c53 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["jupyter_packaging~=0.7.9", "jupyterlab~=3.0", "setuptools>=40.8.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index ed3f8f7..c32784a 100644 --- a/setup.py +++ b/setup.py @@ -1,42 +1,41 @@ """ -Setup Module to setup Python Handlers for the jupyterlab-snippets extension. +jupyterlab-snippets setup """ -import os +import json +from pathlib import Path -from setupbase import ( - create_cmdclass, install_npm, ensure_targets, - combine_commands, ensure_python, get_version, +from jupyter_packaging import ( + create_cmdclass, + install_npm, + ensure_targets, + combine_commands, + skip_if_exists ) import setuptools -HERE = os.path.abspath(os.path.dirname(__file__)) +HERE = Path(__file__).parent.resolve() # The name of the project -name="jupyterlab-snippets" +name = "jupyterlab_snippets" -# Ensure a valid python version -ensure_python(">=3.6") - -# Get our version -version = get_version(os.path.join(name, "_version.py")) - -lab_path = os.path.join(HERE, name, "labextension") +lab_path = (HERE / name / "labextension") # Representative files that should exist after a successful build jstargets = [ - os.path.join(HERE, "lib", "snippets.js"), + str(lab_path / "package.json"), ] package_data_spec = { - name: [ - "*" - ] + name: ["*"], } +labext_name = "jupyterlab-snippets" + data_files_spec = [ - ("share/jupyter/lab/extensions", lab_path, "*.tgz"), - ("etc/jupyter/jupyter_notebook_config.d", - "jupyter-config", "jupyterlab-snippets.json"), + ("share/jupyter/labextensions/%s" % labext_name, str(lab_path), "**"), + ("share/jupyter/labextensions/%s" % labext_name, str(HERE), "install.json"), + ("etc/jupyter/jupyter_notebook_config.d", "jupyter-config/jupyter_notebook_config.d", "jupyterlab_snippets.json"), + ("etc/jupyter/jupyter_server_config.d", "jupyter-config/jupyter_server_config.d", "jupyterlab_snippets.json"), ] cmdclass = create_cmdclass("jsdeps", @@ -44,32 +43,42 @@ data_files_spec=data_files_spec ) -cmdclass["jsdeps"] = combine_commands( - install_npm(HERE, build_cmd="build:all", npm=["jlpm"]), +js_command = combine_commands( + install_npm(HERE, build_cmd="build:prod", npm=["jlpm"]), ensure_targets(jstargets), ) -with open("README.md", "r") as fh: - long_description = fh.read() +is_repo = (HERE / ".git").exists() +if is_repo: + cmdclass["jsdeps"] = js_command +else: + cmdclass["jsdeps"] = skip_if_exists(jstargets, js_command) + +long_description = (HERE / "README.md").read_text() + +# Get the package info from package.json +pkg_json = json.loads((HERE / "package.json").read_bytes()) setup_args = dict( name=name, - version=version, - url="https://github.com/QuantStack/jupyterlab-snippets", - author="QuantStack", - description="Code Snippets Extension for JupyterLab", + version=pkg_json["version"], + url=pkg_json["homepage"], + author=pkg_json["author"]["name"], + author_email=pkg_json["author"]["email"], + description=pkg_json["description"], + license=pkg_json["license"], long_description=long_description, long_description_content_type="text/markdown", cmdclass=cmdclass, packages=setuptools.find_packages(), install_requires=[ - "jupyterlab", + "jupyterlab~=3.0", ], zip_safe=False, include_package_data=True, - license="BSD-3-Clause", + python_requires=">=3.6", platforms="Linux, Mac OS X, Windows", - keywords=["Jupyter", "JupyterLab"], + keywords=["Jupyter", "JupyterLab", "JupyterLab3"], classifiers=[ "License :: OSI Approved :: BSD License", "Programming Language :: Python", @@ -77,10 +86,11 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Framework :: Jupyter", ], ) -if __name__ == '__main__': +if __name__ == "__main__": setuptools.setup(**setup_args) diff --git a/setupbase.py b/setupbase.py deleted file mode 100644 index af6a6b9..0000000 --- a/setupbase.py +++ /dev/null @@ -1,710 +0,0 @@ -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -""" -This file originates from the 'jupyter-packaging' package, and -contains a set of useful utilities for including npm packages -within a Python package. -""" -from collections import defaultdict -from os.path import join as pjoin -import io -import os -import functools -import pipes -import re -import shlex -import subprocess -import sys - - -# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly -# update it when the contents of directories change. -if os.path.exists('MANIFEST'): os.remove('MANIFEST') - - -from setuptools import Command -from setuptools.command.build_py import build_py -from setuptools.command.sdist import sdist -from distutils import log - -from setuptools.command.develop import develop -from setuptools.command.bdist_egg import bdist_egg - -try: - from wheel.bdist_wheel import bdist_wheel -except ImportError: - bdist_wheel = None - -if sys.platform == 'win32': - from subprocess import list2cmdline -else: - def list2cmdline(cmd_list): - return ' '.join(map(pipes.quote, cmd_list)) - - -__version__ = '0.4.0' - -# --------------------------------------------------------------------------- -# Top Level Variables -# --------------------------------------------------------------------------- - -SEPARATORS = os.sep if os.altsep is None else os.sep + os.altsep - -if "--skip-npm" in sys.argv: - print("Skipping npm install as requested.") - skip_npm = True - sys.argv.remove("--skip-npm") -else: - skip_npm = False - - -# --------------------------------------------------------------------------- -# Public Functions -# --------------------------------------------------------------------------- - -def get_version(file, name='__version__'): - """Get the version of the package from the given file by - executing it and extracting the given `name`. - """ - path = os.path.realpath(file) - version_ns = {} - with io.open(path, encoding="utf8") as f: - exec(f.read(), {}, version_ns) - return version_ns[name] - - -def ensure_python(specs): - """Given a list of range specifiers for python, ensure compatibility. - """ - if not isinstance(specs, (list, tuple)): - specs = [specs] - v = sys.version_info - part = '%s.%s' % (v.major, v.minor) - for spec in specs: - if part == spec: - return - try: - if eval(part + spec): - return - except SyntaxError: - pass - raise ValueError('Python version %s unsupported' % part) - - -def find_packages(top): - """ - Find all of the packages. - """ - import warnings - warnings.warn( - 'Deprecated, please use setuptools.find_packages', - category=DeprecationWarning - ) - from setuptools import find_packages as fp - return fp(top) - - -def update_package_data(distribution): - """update build_py options to get package_data changes""" - build_py = distribution.get_command_obj('build_py') - build_py.finalize_options() - - -class bdist_egg_disabled(bdist_egg): - """Disabled version of bdist_egg - - Prevents setup.py install performing setuptools' default easy_install, - which it should never ever do. - """ - def run(self): - sys.exit("Aborting implicit building of eggs. Use `pip install .` " - " to install from source.") - - -def create_cmdclass(prerelease_cmd=None, package_data_spec=None, - data_files_spec=None): - """Create a command class with the given optional prerelease class. - - Parameters - ---------- - prerelease_cmd: (name, Command) tuple, optional - The command to run before releasing. - package_data_spec: dict, optional - A dictionary whose keys are the dotted package names and - whose values are a list of glob patterns. - data_files_spec: list, optional - A list of (path, dname, pattern) tuples where the path is the - `data_files` install path, dname is the source directory, and the - pattern is a glob pattern. - - Notes - ----- - We use specs so that we can find the files *after* the build - command has run. - - The package data glob patterns should be relative paths from the package - folder containing the __init__.py file, which is given as the package - name. - e.g. `dict(foo=['./bar/*', './baz/**'])` - - The data files directories should be absolute paths or relative paths - from the root directory of the repository. Data files are specified - differently from `package_data` because we need a separate path entry - for each nested folder in `data_files`, and this makes it easier to - parse. - e.g. `('share/foo/bar', 'pkgname/bizz, '*')` - """ - wrapped = [prerelease_cmd] if prerelease_cmd else [] - if package_data_spec or data_files_spec: - wrapped.append('handle_files') - wrapper = functools.partial(_wrap_command, wrapped) - handle_files = _get_file_handler(package_data_spec, data_files_spec) - - if 'bdist_egg' in sys.argv: - egg = wrapper(bdist_egg, strict=True) - else: - egg = bdist_egg_disabled - - is_repo = os.path.exists('.git') - - cmdclass = dict( - build_py=wrapper(build_py, strict=is_repo), - bdist_egg=egg, - develop=develop, - sdist=sdist, - handle_files=handle_files, - ) - - if bdist_wheel: - cmdclass['bdist_wheel'] = wrapper(bdist_wheel, strict=True) - - return cmdclass - - -def command_for_func(func): - """Create a command that calls the given function.""" - - class FuncCommand(BaseCommand): - - def run(self): - func() - update_package_data(self.distribution) - - return FuncCommand - - -def run(cmd, **kwargs): - """Echo a command before running it.""" - log.info('> ' + list2cmdline(cmd)) - kwargs.setdefault('shell', os.name == 'nt') - if not isinstance(cmd, (list, tuple)) and os.name != 'nt': - cmd = shlex.split(cmd) - cmd_path = which(cmd[0]) - if not cmd_path: - sys.exit("Aborting. Could not find cmd (%s) in path. " - "If command is not expected to be in user's path, " - "use an absolute path." % cmd[0]) - cmd[0] = cmd_path - return subprocess.check_call(cmd, **kwargs) - - -def is_stale(target, source): - """Test whether the target file/directory is stale based on the source - file/directory. - """ - if not os.path.exists(target): - return True - target_mtime = recursive_mtime(target) or 0 - return compare_recursive_mtime(source, cutoff=target_mtime) - - -class BaseCommand(Command): - """Empty command because Command needs subclasses to override too much""" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def get_inputs(self): - return [] - - def get_outputs(self): - return [] - - -def combine_commands(*commands): - """Return a Command that combines several commands.""" - - class CombinedCommand(Command): - user_options = [] - - def initialize_options(self): - self.commands = [] - for C in commands: - self.commands.append(C(self.distribution)) - for c in self.commands: - c.initialize_options() - - def finalize_options(self): - for c in self.commands: - c.finalize_options() - - def run(self): - for c in self.commands: - c.run() - return CombinedCommand - - -def compare_recursive_mtime(path, cutoff, newest=True): - """Compare the newest/oldest mtime for all files in a directory. - - Cutoff should be another mtime to be compared against. If an mtime that is - newer/older than the cutoff is found it will return True. - E.g. if newest=True, and a file in path is newer than the cutoff, it will - return True. - """ - if os.path.isfile(path): - mt = mtime(path) - if newest: - if mt > cutoff: - return True - elif mt < cutoff: - return True - for dirname, _, filenames in os.walk(path, topdown=False): - for filename in filenames: - mt = mtime(pjoin(dirname, filename)) - if newest: # Put outside of loop? - if mt > cutoff: - return True - elif mt < cutoff: - return True - return False - - -def recursive_mtime(path, newest=True): - """Gets the newest/oldest mtime for all files in a directory.""" - if os.path.isfile(path): - return mtime(path) - current_extreme = None - for dirname, dirnames, filenames in os.walk(path, topdown=False): - for filename in filenames: - mt = mtime(pjoin(dirname, filename)) - if newest: # Put outside of loop? - if mt >= (current_extreme or mt): - current_extreme = mt - elif mt <= (current_extreme or mt): - current_extreme = mt - return current_extreme - - -def mtime(path): - """shorthand for mtime""" - return os.stat(path).st_mtime - - -def install_npm(path=None, build_dir=None, source_dir=None, build_cmd='build', - force=False, npm=None): - """Return a Command for managing an npm installation. - - Note: The command is skipped if the `--skip-npm` flag is used. - - Parameters - ---------- - path: str, optional - The base path of the node package. Defaults to the current directory. - build_dir: str, optional - The target build directory. If this and source_dir are given, - the JavaScript will only be build if necessary. - source_dir: str, optional - The source code directory. - build_cmd: str, optional - The npm command to build assets to the build_dir. - npm: str or list, optional. - The npm executable name, or a tuple of ['node', executable]. - """ - - class NPM(BaseCommand): - description = 'install package.json dependencies using npm' - - def run(self): - if skip_npm: - log.info('Skipping npm-installation') - return - node_package = path or os.path.abspath(os.getcwd()) - node_modules = pjoin(node_package, 'node_modules') - is_yarn = os.path.exists(pjoin(node_package, 'yarn.lock')) - - npm_cmd = npm - - if npm is None: - if is_yarn: - npm_cmd = ['yarn'] - else: - npm_cmd = ['npm'] - - if not which(npm_cmd[0]): - log.error("`{0}` unavailable. If you're running this command " - "using sudo, make sure `{0}` is available to sudo" - .format(npm_cmd[0])) - return - - if force or is_stale(node_modules, pjoin(node_package, 'package.json')): - log.info('Installing build dependencies with npm. This may ' - 'take a while...') - run(npm_cmd + ['install'], cwd=node_package) - if build_dir and source_dir and not force: - should_build = is_stale(build_dir, source_dir) - else: - should_build = True - if should_build: - run(npm_cmd + ['run', build_cmd], cwd=node_package) - - return NPM - - -def ensure_targets(targets): - """Return a Command that checks that certain files exist. - - Raises a ValueError if any of the files are missing. - - Note: The check is skipped if the `--skip-npm` flag is used. - """ - - class TargetsCheck(BaseCommand): - def run(self): - if skip_npm: - log.info('Skipping target checks') - return - missing = [t for t in targets if not os.path.exists(t)] - if missing: - raise ValueError(('missing files: %s' % missing)) - - return TargetsCheck - - -# `shutils.which` function copied verbatim from the Python-3.3 source. -def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - """ - - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) and - not os.path.isdir(fn)) - - # Short circuit. If we're given a full path which matches the mode - # and it exists, we're done here. - if _access_check(cmd, mode): - return cmd - - path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if os.curdir not in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())] - # If it does match, only test that one, otherwise we have to try - # others. - files = [cmd] if matches else [cmd + ext.lower() for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - dir = os.path.normcase(dir) - if dir not in seen: - seen.add(dir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -# --------------------------------------------------------------------------- -# Private Functions -# --------------------------------------------------------------------------- - - -def _wrap_command(cmds, cls, strict=True): - """Wrap a setup command - - Parameters - ---------- - cmds: list(str) - The names of the other commands to run prior to the command. - strict: boolean, optional - Whether to raise errors when a pre-command fails. - """ - class WrappedCommand(cls): - - def run(self): - if not getattr(self, 'uninstall', None): - try: - [self.run_command(cmd) for cmd in cmds] - except Exception: - if strict: - raise - else: - pass - # update package data - update_package_data(self.distribution) - - result = cls.run(self) - return result - return WrappedCommand - - -def _get_file_handler(package_data_spec, data_files_spec): - """Get a package_data and data_files handler command. - """ - class FileHandler(BaseCommand): - - def run(self): - package_data = self.distribution.package_data - package_spec = package_data_spec or dict() - - for (key, patterns) in package_spec.items(): - package_data[key] = _get_package_data(key, patterns) - - self.distribution.data_files = _get_data_files( - data_files_spec, self.distribution.data_files - ) - - return FileHandler - - -def _glob_pjoin(*parts): - """Join paths for glob processing""" - if parts[0] in ('.', ''): - parts = parts[1:] - return pjoin(*parts).replace(os.sep, '/') - - -def _get_data_files(data_specs, existing, top=None): - """Expand data file specs into valid data files metadata. - - Parameters - ---------- - data_specs: list of tuples - See [create_cmdclass] for description. - existing: list of tuples - The existing distribution data_files metadata. - - Returns - ------- - A valid list of data_files items. - """ - if top is None: - top = os.path.abspath(os.getcwd()) - # Extract the existing data files into a staging object. - file_data = defaultdict(list) - for (path, files) in existing or []: - file_data[path] = files - - # Extract the files and assign them to the proper data - # files path. - for (path, dname, pattern) in data_specs or []: - if os.path.isabs(dname): - dname = os.path.relpath(dname, top) - dname = dname.replace(os.sep, '/') - offset = 0 if dname in ('.', '') else len(dname) + 1 - files = _get_files(_glob_pjoin(dname, pattern), top=top) - for fname in files: - # Normalize the path. - root = os.path.dirname(fname) - full_path = _glob_pjoin(path, root[offset:]) - if full_path.endswith('/'): - full_path = full_path[:-1] - file_data[full_path].append(fname) - - # Construct the data files spec. - data_files = [] - for (path, files) in file_data.items(): - data_files.append((path, files)) - return data_files - - -def _get_files(file_patterns, top=None): - """Expand file patterns to a list of paths. - - Parameters - ----------- - file_patterns: list or str - A list of glob patterns for the data file locations. - The globs can be recursive if they include a `**`. - They should be relative paths from the top directory or - absolute paths. - top: str - the directory to consider for data files - - Note: - Files in `node_modules` are ignored. - """ - if top is None: - top = os.path.abspath(os.getcwd()) - if not isinstance(file_patterns, (list, tuple)): - file_patterns = [file_patterns] - - for i, p in enumerate(file_patterns): - if os.path.isabs(p): - file_patterns[i] = os.path.relpath(p, top) - - matchers = [_compile_pattern(p) for p in file_patterns] - - files = set() - - for root, dirnames, filenames in os.walk(top): - # Don't recurse into node_modules - if 'node_modules' in dirnames: - dirnames.remove('node_modules') - for m in matchers: - for filename in filenames: - fn = os.path.relpath(_glob_pjoin(root, filename), top) - fn = fn.replace(os.sep, '/') - if m(fn): - files.add(fn) - - return list(files) - - -def _get_package_data(root, file_patterns=None): - """Expand file patterns to a list of `package_data` paths. - - Parameters - ----------- - root: str - The relative path to the package root from the current dir. - file_patterns: list or str, optional - A list of glob patterns for the data file locations. - The globs can be recursive if they include a `**`. - They should be relative paths from the root or - absolute paths. If not given, all files will be used. - - Note: - Files in `node_modules` are ignored. - """ - if file_patterns is None: - file_patterns = ['*'] - return _get_files(file_patterns, _glob_pjoin(os.path.abspath(os.getcwd()), root)) - - -def _compile_pattern(pat, ignore_case=True): - """Translate and compile a glob pattern to a regular expression matcher.""" - if isinstance(pat, bytes): - pat_str = pat.decode('ISO-8859-1') - res_str = _translate_glob(pat_str) - res = res_str.encode('ISO-8859-1') - else: - res = _translate_glob(pat) - flags = re.IGNORECASE if ignore_case else 0 - return re.compile(res, flags=flags).match - - -def _iexplode_path(path): - """Iterate over all the parts of a path. - - Splits path recursively with os.path.split(). - """ - (head, tail) = os.path.split(path) - if not head or (not tail and head == path): - if head: - yield head - if tail or not head: - yield tail - return - for p in _iexplode_path(head): - yield p - yield tail - - -def _translate_glob(pat): - """Translate a glob PATTERN to a regular expression.""" - translated_parts = [] - for part in _iexplode_path(pat): - translated_parts.append(_translate_glob_part(part)) - os_sep_class = '[%s]' % re.escape(SEPARATORS) - res = _join_translated(translated_parts, os_sep_class) - return '(?ms){res}\\Z'.format(res=res) - - -def _join_translated(translated_parts, os_sep_class): - """Join translated glob pattern parts. - - This is different from a simple join, as care need to be taken - to allow ** to match ZERO or more directories. - """ - res = '' - for part in translated_parts[:-1]: - if part == '.*': - # drop separator, since it is optional - # (** matches ZERO or more dirs) - res += part - else: - res += part + os_sep_class - - if translated_parts[-1] == '.*': - # Final part is ** - res += '.+' - # Follow stdlib/git convention of matching all sub files/directories: - res += '({os_sep_class}?.*)?'.format(os_sep_class=os_sep_class) - else: - res += translated_parts[-1] - return res - - -def _translate_glob_part(pat): - """Translate a glob PATTERN PART to a regular expression.""" - # Code modified from Python 3 standard lib fnmatch: - if pat == '**': - return '.*' - i, n = 0, len(pat) - res = [] - while i < n: - c = pat[i] - i = i + 1 - if c == '*': - # Match anything but path separators: - res.append('[^%s]*' % SEPARATORS) - elif c == '?': - res.append('[^%s]?' % SEPARATORS) - elif c == '[': - j = i - if j < n and pat[j] == '!': - j = j + 1 - if j < n and pat[j] == ']': - j = j + 1 - while j < n and pat[j] != ']': - j = j + 1 - if j >= n: - res.append('\\[') - else: - stuff = pat[i:j].replace('\\', '\\\\') - i = j + 1 - if stuff[0] == '!': - stuff = '^' + stuff[1:] - elif stuff[0] == '^': - stuff = '\\' + stuff - res.append('[%s]' % stuff) - else: - res.append(re.escape(c)) - return ''.join(res) diff --git a/style/base.css b/style/base.css new file mode 100644 index 0000000..e69de29 diff --git a/style/index.css b/style/index.css index e69de29..8a7ea29 100644 --- a/style/index.css +++ b/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/style/index.js b/style/index.js new file mode 100644 index 0000000..a028a76 --- /dev/null +++ b/style/index.js @@ -0,0 +1 @@ +import './base.css'; From e2b8176d5948b72fd400a66eb74fb108fb8b38f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 3 Mar 2021 09:32:29 +0100 Subject: [PATCH 2/6] Correct lint errors --- src/index.ts | 28 ++++++++++++++++------------ src/snippets.ts | 24 ++++++++++-------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/index.ts b/src/index.ts index e55c932..29492d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ import { JupyterFrontEnd, JupyterFrontEndPlugin -} from "@jupyterlab/application"; +} from '@jupyterlab/application'; -import { PathExt } from "@jupyterlab/coreutils"; +import { PathExt } from '@jupyterlab/coreutils'; -import { IMainMenu } from "@jupyterlab/mainmenu"; +import { IMainMenu } from '@jupyterlab/mainmenu'; -import { INotebookTracker, NotebookActions } from "@jupyterlab/notebook"; +import { INotebookTracker, NotebookActions } from '@jupyterlab/notebook'; import { CommandRegistry } from '@lumino/commands'; @@ -16,16 +16,16 @@ import { pythonIcon, terminalIcon, textEditorIcon, - folderIcon, + folderIcon } from '@jupyterlab/ui-components'; -import { listSnippets, Snippet, fetchSnippet } from "./snippets"; +import { listSnippets, Snippet, fetchSnippet } from './snippets'; /** * The command IDs used by the snippets plugin. */ namespace CommandIDs { - export const open = "snippets:open"; + export const open = 'snippets:open'; } /** @@ -46,7 +46,7 @@ function toTree(snippets: Snippet[]) { node.set(part, new Map()); } node = node.get(part); - }) + }); }); return tree; } @@ -57,7 +57,11 @@ function toTree(snippets: Snippet[]) { * @param tree The tree of snippets. * @param path The current path in the tree. */ -function createMenu(commands: CommandRegistry , tree: Tree, path: string[] = []) { +function createMenu( + commands: CommandRegistry, + tree: Tree, + path: string[] = [] +) { const menu = new MenuSvg({ commands }); for (const [name, map] of tree.entries()) { const fullpath = path.concat(name); @@ -70,7 +74,7 @@ function createMenu(commands: CommandRegistry , tree: Tree, path: string[] = []) const submenu = createMenu(commands, map, path.concat(name)); submenu.title.label = name; submenu.title.icon = folderIcon; - menu.addItem({type: 'submenu', submenu}); + menu.addItem({ type: 'submenu', submenu }); } } return menu; @@ -80,7 +84,7 @@ function createMenu(commands: CommandRegistry , tree: Tree, path: string[] = []) * Initialization data for the jupyterlab-snippets extension. */ const extension: JupyterFrontEndPlugin = { - id: "jupyterlab-snippets", + id: 'jupyterlab-snippets', autoStart: true, optional: [IMainMenu, INotebookTracker], activate: async ( @@ -95,7 +99,7 @@ const extension: JupyterFrontEndPlugin = { notebookTracker?.currentWidget !== null && notebookTracker?.currentWidget === app.shell.currentWidget ); - } + }; commands.addCommand(CommandIDs.open, { label: args => args['label'] as string, diff --git a/src/snippets.ts b/src/snippets.ts index 76903d0..03e3974 100644 --- a/src/snippets.ts +++ b/src/snippets.ts @@ -1,6 +1,6 @@ -import { URLExt } from "@jupyterlab/coreutils"; +import { URLExt } from '@jupyterlab/coreutils'; -import { ServerConnection } from "@jupyterlab/services"; +import { ServerConnection } from '@jupyterlab/services'; /** * The type for a Snippet. @@ -10,27 +10,27 @@ export type Snippet = string[]; /** * The Snippet Content interface */ -export interface SnippetContent { +export interface ISnippetContent { content: string; } /** * List the available snippets. */ -export async function listSnippets() { - return requestAPI("list"); +export async function listSnippets(): Promise { + return requestAPI('list'); } /** * Fetch a snippet given its path. * @param snippet The path of the snippet to fetch. */ -export async function fetchSnippet(snippet: Snippet) { - let request: RequestInit = { +export async function fetchSnippet(snippet: Snippet): Promise { + const request: RequestInit = { method: 'POST', body: JSON.stringify({ snippet }) }; - return requestAPI("get", request); + return requestAPI('get', request); } /** @@ -41,15 +41,11 @@ export async function fetchSnippet(snippet: Snippet) { * @returns The response body interpreted as JSON */ async function requestAPI( - endPoint: string = "", + endPoint = '', init: RequestInit = {} ): Promise { const settings = ServerConnection.makeSettings(); - const requestUrl = URLExt.join( - settings.baseUrl, - "snippets", - endPoint - ); + const requestUrl = URLExt.join(settings.baseUrl, 'snippets', endPoint); let response: Response; try { From e87ed0b1f66b19a4571114ab2b9b8fd7f4ab4887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 3 Mar 2021 10:01:14 +0100 Subject: [PATCH 3/6] Correct Python name in server extension check --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecca219..a4ac396 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: jlpm run eslint:check python -m pip install . - jupyter server extension list 2>&1 | grep -ie "jupyterlab-snippets.*OK" - + jupyter server extension list 2>&1 | grep -ie "jupyterlab_snippets.*OK" jupyter labextension list 2>&1 | grep -ie "jupyterlab-snippets.*OK" + python -m jupyterlab.browser_check From d6ab02f8d777894c653ba4f119ba7f1a2fbda03e Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 10 Mar 2021 15:18:35 +0100 Subject: [PATCH 4/6] Update development setup for JupyterLab 3.0 --- README.md | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 920b37a..b1913bf 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Using pip: pip install jupyterlab-snippets ``` -Rebuild JupyterLab: +If you use JupyterLab 2.x, you will have to rebuild JupyterLab with: ```bash jupyter lab build @@ -93,9 +93,9 @@ jupyter lab clean jupyter lab build ``` -## Contributing +## Development Install -### Development Install +Note: You will need NodeJS to build the extension package. The `jlpm` command is JupyterLab's pinned version of [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use @@ -103,42 +103,34 @@ The `jlpm` command is JupyterLab's pinned version of ```bash # Clone the repo to your local environment -# Move to jupyterlab-snippets directory -# Install the server extension +# Change directory to the jupyterlab-snippets directory +# Install package in development mode pip install -e . - -# Register the server extension -jupyter serverextension enable --py jupyterlab-snippets - -# Install the dependencies -jlpm - -# Build the TypeScript source -jlpm build - # Link your development version of the extension with JupyterLab -jupyter labextension link . +jupyter labextension develop . --overwrite +# Rebuild extension Typescript source after making changes +jlpm run build +``` -# Rebuild the TypeScript source after making changes -jlpm build +You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. -# Rebuild JupyterLab after making any changes -jupyter lab build +```bash +# Watch the source directory in one terminal, automatically rebuilding when needed +jlpm run watch +# Run JupyterLab in another terminal +jupyter lab ``` -You can watch the source directory and run JupyterLab in watch mode to watch for changes in the extension's source and automatically rebuild the extension and application. +With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). -```bash -# Watch the source directory in another terminal tab -jlpm watch +By default, the `jlpm run build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: -# Run jupyterlab in watch mode in one terminal tab -jupyter lab --watch +```bash +jupyter lab build --minimize=False ``` ### Uninstall ```bash pip uninstall jupyterlab-snippets -jupyter labextension uninstall jupyterlab-snippets ``` From 3bf48238782e173cf0bbe57fa76d6a50a99203d9 Mon Sep 17 00:00:00 2001 From: Frederic COLLONVAL Date: Thu, 11 Mar 2021 08:20:12 +0100 Subject: [PATCH 5/6] Apply review suggestion --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index c32784a..4ab4d99 100644 --- a/setup.py +++ b/setup.py @@ -16,9 +16,10 @@ HERE = Path(__file__).parent.resolve() # The name of the project -name = "jupyterlab_snippets" +name = "jupyterlab-snippets" +package = name.replace("-", "_") -lab_path = (HERE / name / "labextension") +lab_path = (HERE / package / "labextension") # Representative files that should exist after a successful build jstargets = [ @@ -26,7 +27,7 @@ ] package_data_spec = { - name: ["*"], + package: ["*"], } labext_name = "jupyterlab-snippets" @@ -60,7 +61,7 @@ pkg_json = json.loads((HERE / "package.json").read_bytes()) setup_args = dict( - name=name, + name=package, version=pkg_json["version"], url=pkg_json["homepage"], author=pkg_json["author"]["name"], From 5125c0f0c2f8494f72f8a0719c5b19545274516b Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 11 Mar 2021 10:02:07 +0100 Subject: [PATCH 6/6] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4ab4d99..7923702 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ pkg_json = json.loads((HERE / "package.json").read_bytes()) setup_args = dict( - name=package, + name=name, version=pkg_json["version"], url=pkg_json["homepage"], author=pkg_json["author"]["name"],