diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 55cd4d625..e5926657b 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -28,7 +28,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} + key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}-pip- diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a30348548..5b0b0b36e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,7 +31,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index 8b1df9e6d..c8fd5176c 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -46,7 +46,7 @@ jobs: if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ hashFiles('**/requirements.txt', 'setup.py') }} + key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python }} diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7472f442d..90a5e6a81 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, macos-13] - python-version: [ '3.7', '3.8', '3.9', '3.10'] + python-version: [ '3.8', '3.9', '3.10', '3.11' ] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 368e744f3..94e0c369e 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -13,12 +13,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, macos-13, windows-2022] - python-version: ["3.7", "3.8", "3.9", "3.10"] - exclude: - - os: ubuntu-22.04 - python-version: "3.7" - - os: macos-13 - python-version: "3.7" + python-version: [ '3.8', '3.9', '3.10', '3.11' ] steps: - name: Checkout uses: actions/checkout@v4 @@ -57,8 +52,8 @@ jobs: # - name: Base Setup # uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 # with: -# python_version: "3.7" -# - name: Install miniumum versions +# python_version: "3.8" +# - name: Install minimum versions # uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 # - name: Run the unit tests # run: pytest -vv || pytest -vv --lf diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 28823f024..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,50 +0,0 @@ -include LICENSE -include CONTRIBUTING.rst -include README.md -include CHANGELOG.md -include package.json -include bower.json -include .bowerrc -include pyproject.toml -include setup.py -include setupbase.py -include Dockerfile -include *.js -include *.in -include *.md -include *.svg -include *.yml -recursive-include nbclassic *.cfg -recursive-include nbclassic *.json -recursive-include nbclassic *.less -recursive-include nbclassic *.md -recursive-include nbclassic *.png -recursive-include tests *.py -recursive-include jupyter_server_config.d nbclassic.json -graft tools -graft notebook/tests - -# Translations -graft notebook/i18n - -# Documentation -graft docs -exclude docs/\#* - -# Examples -graft examples - -# docs subdirs we want to skip -prune docs/build -prune docs/gh-pages -prune docs/dist - -prune git-hooks -prune docs-translations - -# Patterns to exclude from any directory -global-exclude *~ -global-exclude *.pyc -global-exclude *.pyo -global-exclude .git -global-exclude .ipynb_checkpoints diff --git a/RELEASE.md b/RELEASE.md index 9bcdc4f29..77e58889e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,17 +9,12 @@ yet support "next" or "patch" when dev versions are used. ## Manual Release -To create a release, update the version number in `nbclassic/__version__.py`, then run the following: +To create a release, run the following: ``` git clean -dffx -python setup.py sdist -python setup.py bdist_wheel -export script_version=`python setup.py --version 2>/dev/null` -git commit -a -m "Release $script_version" -git tag $script_version -git push --all -git push --tags +python -m build +tbump pip install twine twine check dist/* twine upload dist/* diff --git a/git-hooks/post-checkout b/git-hooks/post-checkout index 8ed89d879..4995e02f3 100755 --- a/git-hooks/post-checkout +++ b/git-hooks/post-checkout @@ -11,12 +11,12 @@ if [[ ! -z "$(git diff $PREVIOUS_HEAD nbclassic/static/*/js/**.js)" ]]; then echo "rebuilding javascript" - python setup.py js || echo "fail to rebuild javascript" + npm run build:js || echo "fail to rebuild javascript" fi if [[ ! -z "$(git diff $PREVIOUS_HEAD nbclassic/static/*/less/**.less)" ]]; then echo "rebuilding css sourcemaps" - python setup.py css || echo "fail to recompile css" + npm run build:css || echo "fail to recompile css" fi diff --git a/nbclassic/_version.py b/nbclassic/_version.py index 5c43112e4..fefa9767c 100644 --- a/nbclassic/_version.py +++ b/nbclassic/_version.py @@ -15,6 +15,6 @@ parts.append(match['rest']) version_info = tuple(parts) -# Downstream maintainer, when running `python.setup.py jsversion`, -# the version string is propagated to the JavaScript files, do not forget to +# Downstream maintainer, when running `tbump `, +# the version string is propagated to the JavaScript files, do not forget to # patch the JavaScript files in `.postN` release done by distributions. diff --git a/nbclassic/static/base/js/namespace.js b/nbclassic/static/base/js/namespace.js index ba18bcca1..a97e922e6 100644 --- a/nbclassic/static/base/js/namespace.js +++ b/nbclassic/static/base/js/namespace.js @@ -73,7 +73,7 @@ define(function(){ // tree jglobal('SessionList','tree/js/sessionlist'); - Jupyter.version = "1.2.0"; + Jupyter.version = "1.3.0.dev0"; Jupyter._target = '_blank'; return Jupyter; diff --git a/package.json b/package.json index 9f905bbd7..7cfaf4a92 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,29 @@ "url": "https://github.com/jupyter/nbclassic.git" }, "scripts": { - "bower": "bower install", - "build": "python setup.py js css", + "bower:copy-marked": "node tools/copy-marked.js", + "bower": "bower install && npm run bower:copy-marked", "build:webpack": "webpack --mode production", + "build:notebook": "node tools/build-main.js notebook", + "build:tree": "node tools/build-main.js tree", + "build:edit": "node tools/build-main.js edit", + "build:terminal": "node tools/build-main.js terminal", + "build:auth": "node tools/build-main.js auth", + "build:fr-translation": "po2json -p -F -f jed1.x -d nbjs nbclassic/i18n/fr_FR/LC_MESSAGES/nbjs.po nbclassic/i18n/fr_FR/LC_MESSAGES/nbjs.json", + "build:ja-translation": "po2json -p -F -f jed1.x -d nbjs nbclassic/i18n/ja_JP/LC_MESSAGES/nbjs.po nbclassic/i18n/ja_JP/LC_MESSAGES/nbjs.json", + "build:nl-translation": "po2json -p -F -f jed1.x -d nbjs nbclassic/i18n/nl/LC_MESSAGES/nbjs.po nbclassic/i18n/nl/LC_MESSAGES/nbjs.json", + "build:ru-translation": "po2json -p -F -f jed1.x -d nbjs nbclassic/i18n/ru_RU/LC_MESSAGES/nbjs.po nbclassic/i18n/ru_RU/LC_MESSAGES/nbjs.json", + "build:zh-translation": "po2json -p -F -f jed1.x -d nbjs nbclassic/i18n/zh_CN/LC_MESSAGES/nbjs.po nbclassic/i18n/zh_CN/LC_MESSAGES/nbjs.json", + "build:translations": "npm run build:fr-translation && npm run build:ja-translation && npm run build:nl-translation && npm run build:ru-translation && npm run build:zh-translation", + "build:js": "npm run build:notebook && npm run build:tree && npm run build:edit && npm run build:terminal && npm run build:auth && npm run build:translations", + "build:css-ipython": "lessc --source-map --include-path='nbclassic/static/style' nbclassic/static/style/ipython.less nbclassic/static/style/ipython.min.css", + "build:css-style": "lessc --source-map --include-path='nbclassic/static/style' nbclassic/static/style/style.less nbclassic/static/style/style.min.css", + "build:css": "npm run build:css-ipython && npm run build:css-style", + "build": "npm run bower && npm run build:webpack && npm run build:js && npm run build:css", "build:watch": "npm run watch", - "watch": "onchange 'nbclassic/static/**/!(*.min).js' 'nbclassic/static/**/*.less' 'bower.json' -- npm run build" + "watch:css": "onchange 'nbclassic/static/**/*.less' -- npm run build:css", + "watch:js": "onchange 'nbclassic/static/**/!(*.min).js' 'bower.json' -- npm run build:js", + "watch": "npm-run-all --parallel watch:*" }, "devDependencies": { "@babel/core": "^7.15.0", diff --git a/pyproject.toml b/pyproject.toml index 31d227788..66d3be86e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,42 +1,218 @@ [build-system] +build-backend = "hatchling.build" + requires = [ - "jupyter_packaging~=0.9,<2", - "jupyter_server>=1.17.0", - "babel" + "babel", + "hatchling", + "jupyter-server>=1.17", + "setuptools", +] + +[project] +name = "nbclassic" +version = "1.3.0.dev0" + +description = "Jupyter Notebook as a Jupyter Server extension." +readme = "README.md" +keywords = [ + "interactive", + "interpreter", + "ipython", + "jupyter", + "shell", + "web", +] +license.file = "LICENSE" +authors = [ + { name = "Jupyter Development Team", email = "jupyter@googlegroups.com" }, +] +requires-python = ">=3.8" +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "ipykernel", + "ipython-genutils", + "nest-asyncio>=1.5", + "notebook-shim>=0.2.3", +] +optional-dependencies.docs = [ + "myst-parser", + "nbsphinx", + "sphinx", + "sphinx-rtd-theme", + "sphinxcontrib-github-alt", +] +optional-dependencies.json-logging = [ + "json-logging", +] +optional-dependencies.test = [ + "coverage", + "nbval", + "pytest", + "pytest-cov", + "pytest-jupyter", + "pytest-playwright", + "pytest-tornasync", + "requests", + "requests-unixsocket; sys_platform!='win32'", + "testpath", +] +urls.Homepage = "https://github.com/jupyter/nbclassic" +scripts.jupyter-nbclassic = "nbclassic.notebookapp:main" +scripts.jupyter-nbclassic-bundlerextension = "nbclassic.bundler.bundlerextensions:main" +scripts.jupyter-nbclassic-extension = "nbclassic.nbextensions:main" +scripts.jupyter-nbclassic-serverextension = "nbclassic.serverextensions:main" + +[tool.hatch.build] +artifacts = [ + "nbclassic/i18n/*/LC_MESSAGES/*.mo", + "nbclassic/i18n/*/LC_MESSAGES/nbjs.json", + "nbclassic/static/style/*.min.css*", + "nbclassic/static/*/js/built/", + "nbclassic/static/*/built/", + "nbclassic/static/built/", + "nbclassic/static/*/js/main.min.js*", + "nbclassic/static/lab/*bundle.js", + "nbclassic/static/components/**/*.md", + "nbclassic/static/components/**/*.json", + "nbclassic/static/components/backbone/backbone-min.js", + "nbclassic/static/components/bootstrap/dist/js/bootstrap.min.js", + "nbclassic/static/components/bootstrap/less/**/*.less", + "nbclassic/static/components/bootstrap-tour/build/css/bootstrap-tour.min.css", + "nbclassic/static/components/bootstrap-tour/build/js/bootstrap-tour.min.js", + "nbclassic/static/components/bootstrap-tour/**/*.png", + "nbclassic/static/components/bootstrap-tour/**/*.less", + "nbclassic/static/components/codemirror/**/*.js", + "nbclassic/static/components/codemirror/**/*.css", + "nbclassic/static/components/create-react-class/index.js", + "nbclassic/static/components/font-awesome/css/*.css", + "nbclassic/static/components/google-caja/html-css-sanitizer-minified.js", + "nbclassic/static/components/es6-promise/*.js", + "nbclassic/static/components/font-awesome/fonts/*.*", + "nbclassic/static/components/font-awesome/less/*.less", + "nbclassic/static/components/jed/jed.js", + "nbclassic/static/components/jquery/jquery.min.js", + "nbclassic/static/components/jquery-typeahead/dist/jquery.typeahead.min.js", + "nbclassic/static/components/jquery-typeahead/dist/jquery.typeahead.min.css", + "nbclassic/static/components/jquery-ui/dist/jquery-ui.min.js", + "nbclassic/static/components/jquery-ui/dist/themes/smoothness/jquery-ui.min.css", + "nbclassic/static/components/jquery-ui/dist/themes/smoothness/images/*", + "nbclassic/static/components/jquery-ui/dist/themes/*/images/*", + "nbclassic/static/components/jquery-ui/themes/base/images/*", + "nbclassic/static/components/marked/lib/marked.js", + "nbclassic/static/components/react/react.production.min.js", + "nbclassic/static/components/react/react-dom.production.min.js", + "nbclassic/static/components/requirejs/require.js", + "nbclassic/static/components/requirejs-plugins/src/json.js", + "nbclassic/static/components/requirejs-plugins/**/*.png", + "nbclassic/static/components/requirejs-text/text.js", + "nbclassic/static/components/sanitizer/index.js", + "nbclassic/static/components/underscore/underscore-min.js", + "nbclassic/static/components/moment/moment.js", + "nbclassic/static/components/moment/min/*.js", + "nbclassic/static/components/xterm.js/index.js", + "nbclassic/static/components/xterm.js-css/index.css", + "nbclassic/static/components/xterm.js-fit/index.js", + "nbclassic/static/components/text-encoding/lib/encoding.js", + "nbclassic/static/components/MathJax/MathJax.js", + "nbclassic/static/components/MathJax/config/TeX-AMS-MML_HTMLorMML-full.js", + "nbclassic/static/components/MathJax/config/Safe.js", + "nbclassic/static/components/MathJax/jax/output/*/*.js", + "nbclassic/static/components/MathJax/jax/output/*/autoload/", + "nbclassic/static/components/MathJax/jax/output/HTML-CSS/fonts/STIX-Web/**/*", + "nbclassic/static/components/MathJax/jax/output/SVG/fonts/STIX-Web/**/*", + "nbclassic/static/components/MathJax/localization/", + "nbclassic/static/components/MathJax/fonts/HTML-CSS/STIX-Web/woff/**/*", + "nbclassic/static/components/MathJax/fonts/HTML-CSS/TeX/png/**/*.png", + "nbclassic/static/components/MathJax/extensions/", + "nbclassic/static/components/MathJax/jax/input/TeX/**/*", + "nbclassic/static/components/MathJax/jax/element/mml/", + "nbclassic/templates/*", + "nbclassic/tests/*.js", + "nbclassic/tests/**/*.js", + "nbclassic/resources/**/*", +] + +[tool.hatch.build.targets.sdist] +exclude = [ + ".github", ] -build-backend = "jupyter_packaging.build_api" -[tool.check-manifest] -ignore = ["tbump.toml", ".*"] +[tool.hatch.build.targets.wheel.shared-data] +"nbclassic.svg" = "share/icons/hicolor/scalable/apps/nbclassic.svg" +"jupyter-nbclassic.desktop" = "share/applications/jupyter-nbclassic.desktop" +"jupyter_server_config.d/nbclassic.json" = "etc/jupyter/jupyter_server_config.d/nbclassic.json" + +[tool.hatch.build.hooks.jupyter-builder] +dependencies = [ + "hatch-jupyter-builder>=0.9.1", +] +build-function = "hatch_jupyter_builder.npm_builder" [tool.pytest.ini_options] addopts = "--doctest-modules" -norecursedirs = ["confs"] +norecursedirs = [ + "confs", +] testpaths = [ - "tests" + "tests", ] [tool.jupyter-releaser] -skip = ["check-links", "check-manifest"] +skip = [ + "check-links", + "check-manifest", +] [tool.jupyter-releaser.hooks] -after-bump-version = "python setup.py jsversion" -before-build-python = ["npm install -g po2json"] +before-build-python = [ + "npm install -g po2json", +] [tool.jupyter-releaser.options] post-version-spec = "dev" -ignore-glob = ["docs/source/examples/Notebook/Working With Markdown Cells.ipynb", "docs-translations/**/README.md", "docs/source/contributing.rst", "docs/source/examples/Notebook/JavaScript Notebook Extensions.ipynb", "CHANGELOG.md", "nbclassic/static/components/**/*.*"] +ignore-glob = [ + "docs/source/examples/Notebook/Working With Markdown Cells.ipynb", + "docs-translations/**/README.md", + "docs/source/contributing.rst", + "docs/source/examples/Notebook/JavaScript Notebook Extensions.ipynb", + "CHANGELOG.md", + "nbclassic/static/components/**/*.*", +] + +[tool.tbump] +field = [ + { name = "channel", default = "" }, + { name = "release", default = "" }, +] [tool.tbump.version] current = "1.3.0.dev0" -regex = ''' - (?P\d+)\.(?P\d+)\.(?P\d+) - ((?Pa|b|rc|.dev)(?P\d+))? -''' +regex = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)((?Pa|b|rc|.dev)(?P\\d+))?" [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" +[[tool.tbump.file]] +src = "pyproject.toml" +version_template = "version = \"{major}.{minor}.{patch}{channel}{release}\"" + [[tool.tbump.file]] src = "nbclassic/_version.py" + +[[tool.tbump.file]] +src = "nbclassic/static/base/js/namespace.js" +version_template = "Jupyter.version = \"{major}.{minor}.{patch}{channel}{release}\";" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 1892dda1e..000000000 --- a/setup.cfg +++ /dev/null @@ -1,84 +0,0 @@ -[metadata] -name = nbclassic -version = attr: nbclassic.__version__ -description = Jupyter Notebook as a Jupyter Server extension. -long_description = file: README.md -long_description_content_type = text/markdown -license = BSD-3-Clause -license_files = LICENSE -author = Jupyter Development Team -author_email = jupyter@googlegroups.com -url = https://github.com/jupyter/nbclassic -platforms = Linux, Mac OS X, Windows -keywords = ipython, jupyter, interactive, interpreter, shell, web -classifiers = - Intended Audience :: Developers - Intended Audience :: System Administrators - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Programming Language :: Python - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - -[options] -zip_safe = False -include_package_data = True -packages = find: -python_requires = >=3.7 -install_requires = - ipython_genutils - notebook_shim>=0.2.3 - nest-asyncio>=1.5 - ipykernel # bless IPython kernel for now - -[options.data_files] -etc/jupyter/jupyter_server_config.d = - jupyter_server_config.d/nbclassic.json -share/applications = - jupyter-nbclassic.desktop -share/icons/hicolor/scalable/apps = - nbclassic.svg - -[options.extras_require] -test = - pytest - coverage - requests - testpath - nbval - pytest-playwright - pytest-cov - pytest_jupyter - pytest_tornasync - requests-unixsocket; sys_platform != "win32" -docs = - sphinx - nbsphinx - sphinxcontrib_github_alt - sphinx_rtd_theme - myst-parser -json-logging = - json-logging - -[options.entry_points] -console_scripts = - jupyter-nbclassic = nbclassic.notebookapp:main - jupyter-nbclassic-extension = nbclassic.nbextensions:main - jupyter-nbclassic-serverextension = nbclassic.serverextensions:main - jupyter-nbclassic-bundlerextension = nbclassic.bundler.bundlerextensions:main - -[options.packages.find] -exclude = - docs*, - tests* - -[flake8] -ignore = E, C, W, F401, F403, F811, F841, E402, I100, I101, D400 -builtins = c, get_config -exclude = - .cache, - .github, - docs, - setup.py diff --git a/setup.py b/setup.py index ef9cb7e3e..b6c668135 100644 --- a/setup.py +++ b/setup.py @@ -1,110 +1,2 @@ -#!/usr/bin/env python -"""Setup script for Jupyter NbClassic""" - -#----------------------------------------------------------------------------- -# Copyright (c) 2015-, Jupyter Development Team. -# Copyright (c) 2008-2015, IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file LICENSE, distributed with this software. -#----------------------------------------------------------------------------- - -import os -import sys - -if sys.version_info < (3, 6): - pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' - try: - import pip - pip_version = tuple(int(x) for x in pip.__version__.split('.')[:3]) - if pip_version < (9, 0, 1) : - pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\ - 'pip {} detected.'.format(pip.__version__) - else: - # pip is new enough - it must be something else - pip_message = '' - except Exception: - pass - - - error = """ -NbClassic 1+ supports Python 3.7 and above. - -Python {py} detected. -{pip} -""".format(py=sys.version_info, pip=pip_message ) - - print(error, file=sys.stderr) - sys.exit(1) - -# At least we're on the python version we need, move on. - -# BEFORE importing setuptools, remove MANIFEST. Setuptools doesn't properly -# update it when the contents of directories change. -if os.path.exists('MANIFEST'): os.remove('MANIFEST') - -from setuptools import setup - -# Needed to support building with `setuptools.build_meta` -sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) - -from setupbase import ( - find_packages, - find_package_data, - check_package_data_first, - CompileCSS, - CompileJS, - CompileBackendTranslation, - Bower, - JavascriptVersion, - css_js_prerelease, -) - - -setup_args = dict( - packages = find_packages(), - package_data = find_package_data(), -) - -# Custom setuptools commands --------------------- -from setuptools.command.build_py import build_py -from setuptools.command.sdist import sdist -from setuptools.command.bdist_egg import bdist_egg -from setuptools.command.develop import develop - -class bdist_egg_disabled(bdist_egg): - """Disabled version of bdist_egg - - Prevents setup.py install from 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.") - -setup_args['cmdclass'] = { - 'build_py': css_js_prerelease( - check_package_data_first(build_py)), - 'sdist' : css_js_prerelease(sdist, strict=True), - 'develop': css_js_prerelease(develop), - 'css' : CompileCSS, - 'backendtranslations': CompileBackendTranslation, - 'js' : CompileJS, - 'jsdeps' : Bower, - 'jsversion' : JavascriptVersion, - 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, -} - -try: - from wheel.bdist_wheel import bdist_wheel -except ImportError: - pass -else: - setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(bdist_wheel) - -# Run setup -------------------- -def main(): - setup(**setup_args) - -if __name__ == '__main__': - main() +# setup.py shim for use with applications that require it. +__import__("setuptools").setup() diff --git a/setupbase.py b/setupbase.py deleted file mode 100644 index 2ee325728..000000000 --- a/setupbase.py +++ /dev/null @@ -1,658 +0,0 @@ -""" -This module defines the things that are used in setup.py for building the nbclassic - -This includes: - - * Functions for finding things like packages, package data, etc. - * A function for checking dependencies. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import logging -import os -import re -import shlex -import shutil -import sys - -from setuptools import Command -from fnmatch import fnmatch -from glob import glob -from multiprocessing.pool import ThreadPool -from subprocess import check_call - -if sys.platform == 'win32': - from subprocess import list2cmdline -else: - def list2cmdline(cmd_list): - return ' '.join(map(shlex.quote, cmd_list)) - -log = logging.getLogger(__name__) - -#------------------------------------------------------------------------------- -# Useful globals and utility functions -#------------------------------------------------------------------------------- - -# A few handy globals -isfile = os.path.isfile -pjoin = os.path.join -repo_root = os.path.dirname(os.path.abspath(__file__)) -is_repo = os.path.isdir(pjoin(repo_root, '.git')) - -def oscmd(s): - print(">", s) - os.system(s) - -# Py3 compatibility hacks, without assuming IPython itself is installed with -# the full py3compat machinery. - -try: - execfile -except NameError: - def execfile(fname, globs, locs=None): - locs = locs or globs - exec(compile(open(fname).read(), fname, "exec"), globs, locs) - - -#--------------------------------------------------------------------------- -# Basic project information -#--------------------------------------------------------------------------- - -name = 'nbclassic' - -# release.py contains version, authors, license, url, keywords, etc. -version_ns = {} -execfile(pjoin(repo_root, name, '_version.py'), version_ns) - -version = version_ns['__version__'] - - -# vendored from pep440 package, we allow `.dev` suffix without trailing number. -loose_pep440re = re.compile(r'^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$') -if not loose_pep440re.match(version): - raise ValueError('Invalid version number `%s`, please follow pep440 convention or pip will get confused about which package is more recent.' % version) - -#--------------------------------------------------------------------------- -# Find packages -#--------------------------------------------------------------------------- - -def find_packages(): - """ - Find all of the packages. - """ - packages = [] - for dir,subdirs,files in os.walk(name): - package = dir.replace(os.path.sep, '.') - if '__init__.py' not in files: - # not a package - continue - packages.append(package) - return packages - -#--------------------------------------------------------------------------- -# Find package data -#--------------------------------------------------------------------------- - -def find_package_data(): - """ - Find package_data. - """ - # This is not enough for these things to appear in a sdist. - # We need to muck with the MANIFEST to get this to work - - # exclude components and less from the walk; - # we will build the components separately - excludes = [ - pjoin('static', 'components'), - pjoin('static', '*', 'less'), - pjoin('static', '*', 'node_modules') - ] - - # walk nbclassic resources: - cwd = os.getcwd() - os.chdir('nbclassic') - static_data = [] - for parent, dirs, files in os.walk('static'): - if any(fnmatch(parent, pat) for pat in excludes): - # prevent descending into subdirs - dirs[:] = [] - continue - for f in files: - static_data.append(pjoin(parent, f)) - - # for verification purposes, explicitly add main.min.js - # so that installation will fail if they are missing - for app in ['auth', 'edit', 'notebook', 'terminal', 'tree']: - static_data.append(pjoin('static', app, 'js', 'main.min.js')) - - components = pjoin("static", "components") - # select the components we actually need to install - # (there are lots of resources we bundle for sdist-reasons that we don't actually use) - static_data.extend([ - pjoin(components, "backbone", "backbone-min.js"), - pjoin(components, "bootstrap", "dist", "js", "bootstrap.min.js"), - pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"), - pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"), - pjoin(components, "create-react-class", "index.js"), - pjoin(components, "font-awesome", "css", "*.css"), - pjoin(components, "google-caja", "html-css-sanitizer-minified.js"), - pjoin(components, "es6-promise", "*.js"), - pjoin(components, "font-awesome", "fonts", "*.*"), - pjoin(components, "jed", "jed.js"), - pjoin(components, "jquery", "jquery.min.js"), - pjoin(components, "jquery-typeahead", "dist", "jquery.typeahead.min.js"), - pjoin(components, "jquery-typeahead", "dist", "jquery.typeahead.min.css"), - pjoin(components, "jquery-ui", "dist", "jquery-ui.min.js"), - pjoin(components, "jquery-ui", "dist", "themes", "smoothness", "jquery-ui.min.css"), - pjoin(components, "jquery-ui", "dist", "themes", "smoothness", "images", "*"), - pjoin(components, "marked", "lib", "marked.js"), - pjoin(components, "react", "react.production.min.js"), - pjoin(components, "react", "react-dom.production.min.js"), - pjoin(components, "requirejs", "require.js"), - pjoin(components, "requirejs-plugins", "src", "json.js"), - pjoin(components, "requirejs-text", "text.js"), - pjoin(components, "sanitizer", "index.js"), - pjoin(components, "underscore", "underscore-min.js"), - pjoin(components, "moment", "moment.js"), - pjoin(components, "moment", "min", "*.js"), - pjoin(components, "xterm.js", "index.js"), - pjoin(components, "xterm.js-css", "index.css"), - pjoin(components, "xterm.js-fit", "index.js"), - pjoin(components, "text-encoding", "lib", "encoding.js"), - ]) - - # Ship all of Codemirror's CSS and JS - for parent, dirs, files in os.walk(pjoin(components, 'codemirror')): - for f in files: - if f.endswith(('.js', '.css')): - static_data.append(pjoin(parent, f)) - - # Trim mathjax - mj = lambda *path: pjoin(components, 'MathJax', *path) - static_data.extend([ - mj('MathJax.js'), - mj('config', 'TeX-AMS-MML_HTMLorMML-full.js'), - mj('config', 'Safe.js'), - ]) - - trees = [] - mj_out = mj('jax', 'output') - - if os.path.exists(mj_out): - for output in os.listdir(mj_out): - path = pjoin(mj_out, output) - static_data.append(pjoin(path, '*.js')) - autoload = pjoin(path, 'autoload') - if os.path.isdir(autoload): - trees.append(autoload) - - for tree in trees + [ - mj('localization'), # limit to en? - mj('fonts', 'HTML-CSS', 'STIX-Web', 'woff'), - mj('extensions'), - mj('jax', 'input', 'TeX'), - mj('jax', 'output', 'HTML-CSS', 'fonts', 'STIX-Web'), - mj('jax', 'output', 'SVG', 'fonts', 'STIX-Web'), - mj('jax', 'element', 'mml'), - ]: - for parent, dirs, files in os.walk(tree): - for f in files: - static_data.append(pjoin(parent, f)) - - os.chdir(os.path.join('tests',)) - js_tests = glob('*.js') + glob('*/*.js') - - os.chdir(cwd) - - package_data = { - 'nbclassic' : ['templates/*'] + static_data, - 'nbclassic.tests' : js_tests, - 'nbclassic.bundler.tests': ['resources/*', 'resources/*/*', 'resources/*/*/.*'], - 'nbclassic.i18n': ['*/LC_MESSAGES/*.*'], - } - - return package_data - - -def check_package_data(package_data): - """verify that package_data globs make sense""" - print("checking package data") - for pkg, data in package_data.items(): - pkg_root = pjoin(*pkg.split('.')) - for d in data: - path = pjoin(pkg_root, d) - if '*' in path: - assert len(glob(path)) > 0, "No files match pattern %s" % path - else: - assert os.path.exists(path), "Missing package data: %s" % path - - -def check_package_data_first(command): - """decorator for checking package_data before running a given command - - Probably only needs to wrap build_py - """ - class DecoratedCommand(command): - def run(self): - check_package_data(self.package_data) - command.run(self) - return DecoratedCommand - -def update_package_data(distribution): - """update package_data to catch changes during setup""" - build_py = distribution.get_command_obj('build_py') - distribution.package_data = find_package_data() - # re-init build_py options which load package_data - build_py.finalize_options() - -#--------------------------------------------------------------------------- -# Notebook related -#--------------------------------------------------------------------------- - -try: - from shutil import which -except ImportError: - ## which() function copied from Python 3.4.3; PSF license - 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)) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if not os.curdir 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". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext 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: - normdir = os.path.normcase(dir) - if not normdir in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -static = pjoin(repo_root, 'nbclassic', 'static') - -npm_path = os.pathsep.join([ - pjoin(repo_root, 'node_modules', '.bin'), - os.environ.get("PATH", os.defpath), -]) - -def mtime(path): - """shorthand for mtime""" - return os.stat(path).st_mtime - - -def run(cmd, *args, **kwargs): - """Echo a command before running it""" - log.info('> ' + list2cmdline(cmd)) - kwargs['shell'] = (sys.platform == 'win32') - return check_call(cmd, *args, **kwargs) - -class CompileBackendTranslation(Command): - description = "compile the .po files into .mo files, that contain the translations." - - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - - def run(self): - paths = glob('nbclassic/i18n/*/LC_MESSAGES') - for p in paths: - LANG = p.split(os.path.sep)[-2] - for component in ['nbclassic', 'nbui']: - run(['pybabel', 'compile', - '-D', component, - '-f', - '-l', LANG, - '-i', pjoin('nbclassic', 'i18n', LANG, 'LC_MESSAGES', component+'.po'), - '-o', pjoin('nbclassic', 'i18n', LANG, 'LC_MESSAGES', component+'.mo') - ]) - -class Bower(Command): - description = "fetch static client-side components with bower" - - user_options = [ - ('force', 'f', "force fetching of bower dependencies"), - ] - - def initialize_options(self): - self.force = False - - def finalize_options(self): - self.force = bool(self.force) - - bower_dir = pjoin(static, 'components') - node_modules = pjoin(repo_root, 'node_modules') - sanitizer_dir = pjoin(bower_dir, 'sanitizer') - - def should_run(self): - if self.force: - return True - if not os.path.exists(self.bower_dir): - return True - if not os.path.exists(self.sanitizer_dir): - return True - - bower_stale = mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json')) - if bower_stale: - return True - - return mtime(self.sanitizer_dir) < mtime(pjoin(repo_root, 'webpack.config.js')) - - def should_run_npm(self): - if not which('npm'): - print("npm unavailable", file=sys.stderr) - return False - if not os.path.exists(self.node_modules): - return True - return mtime(self.node_modules) < mtime(pjoin(repo_root, 'package.json')) - - def run(self): - if not self.should_run(): - print("bower dependencies up to date") - return - - if self.should_run_npm(): - print("installing build dependencies with npm") - run(['npm', 'install'], cwd=repo_root) - os.utime(self.node_modules, None) - - env = os.environ.copy() - env['PATH'] = npm_path - - try: - run( - ['bower', 'install', '--allow-root', '--config.interactive=false'], - cwd=repo_root, - env=env - ) - # Copy the UMD files to their JavaScript equivalent to ensure correct loading. - shutil.copyfile(pjoin(self.bower_dir, "marked", "lib", "marked.umd.js"), pjoin(self.bower_dir, "marked", "lib", "marked.js")) - except OSError as e: - print("Failed to run bower: %s" % e, file=sys.stderr) - print("You can install js dependencies with `npm install`", file=sys.stderr) - raise - # self.npm_components() - if not os.path.exists(self.sanitizer_dir): - run(['npm', 'run', 'build:webpack'], cwd=repo_root, env=env) - os.utime(self.bower_dir, None) - # update package data in case this created new files - update_package_data(self.distribution) - - -def patch_out_bootstrap_bw_print(): - """Hack! Manually patch out the bootstrap rule that forces printing in B&W. - - We haven't found a way to override this rule with another one. - """ - print_less = pjoin(static, 'components', 'bootstrap', 'less', 'print.less') - with open(print_less) as f: - lines = f.readlines() - - for ix, line in enumerate(lines): - if 'Black prints faster' in line: - break - else: - return # Already patched out, nothing to do. - - rmed = lines.pop(ix) - print("Removed line", ix, "from bootstrap print.less:") - print("-", rmed) - print() - with open(print_less, 'w') as f: - f.writelines(lines) - -class CompileCSS(Command): - """Recompile Notebook CSS - - Regenerate the compiled CSS from LESS sources. - - Requires various dev dependencies, such as require and lessc. - """ - description = "Recompile Notebook CSS" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - sources = [] - targets = [] - for name in ('ipython', 'style'): - sources.append(pjoin(static, 'style', '%s.less' % name)) - targets.append(pjoin(static, 'style', '%s.min.css' % name)) - - def run(self): - self.run_command('jsdeps') - env = os.environ.copy() - env['PATH'] = npm_path - - patch_out_bootstrap_bw_print() - - for src, dst in zip(self.sources, self.targets): - try: - run(['lessc', - '--source-map', - '--include-path=%s' % shlex.quote(static), - src, - dst, - ], cwd=repo_root, env=env) - except OSError as e: - print("Failed to build css: %s" % e, file=sys.stderr) - print("You can install js dependencies with `npm install`", file=sys.stderr) - raise - # update package data in case this created new files - update_package_data(self.distribution) - - -class CompileJS(Command): - """Rebuild Notebook Javascript main.min.js files and translation files. - - Calls require via build-main.js - """ - description = "Rebuild Notebook Javascript main.min.js files" - user_options = [ - ('force', 'f', "force rebuilding js targets"), - ] - - def initialize_options(self): - self.force = False - - def finalize_options(self): - self.force = bool(self.force) - - apps = ['notebook', 'tree', 'edit', 'terminal', 'auth'] - targets = [ pjoin(static, app, 'js', 'main.min.js') for app in apps ] - - def sources(self, name): - """Generator yielding .js sources that an application depends on""" - yield pjoin(repo_root, 'tools', 'build-main.js') - yield pjoin(static, name, 'js', 'main.js') - - for sec in [name, 'base', 'auth']: - for f in glob(pjoin(static, sec, 'js', '*.js')): - if not f.endswith('.min.js'): - yield f - yield pjoin(static, 'services', 'config.js') - if name == 'nbclassic': - for f in glob(pjoin(static, 'services', '*', '*.js')): - yield f - for parent, dirs, files in os.walk(pjoin(static, 'components')): - if os.path.basename(parent) == 'MathJax': - # don't look in MathJax, since it takes forever to walk it - dirs[:] = [] - continue - for f in files: - yield pjoin(parent, f) - - def should_run(self, name, target): - if self.force or not os.path.exists(target): - return True - target_mtime = mtime(target) - for source in self.sources(name): - if mtime(source) > target_mtime: - print(source, target) - return True - return False - - def build_main(self, name): - """Build main.min.js""" - target = pjoin(static, name, 'js', 'main.min.js') - - if not self.should_run(name, target): - log.info("%s up to date" % target) - return - log.info("Rebuilding %s" % target) - run(['node', 'tools/build-main.js', name]) - - def build_jstranslation(self, trd): - lang = trd.split(os.path.sep)[-2] - run([ - pjoin('node_modules', '.bin', 'po2json'), - '-p', '-F', - '-f', 'jed1.x', - '-d', 'nbjs', - pjoin('nbclassic', 'i18n', lang, 'LC_MESSAGES', 'nbjs.po'), - pjoin('nbclassic', 'i18n', lang, 'LC_MESSAGES', 'nbjs.json'), - ]) - - def run(self): - self.run_command('jsdeps') - env = os.environ.copy() - env['PATH'] = npm_path - pool = ThreadPool() - pool.map(self.build_main, self.apps) - pool.map(self.build_jstranslation, glob('nbclassic/i18n/*/LC_MESSAGES')) - # update package data in case this created new files - update_package_data(self.distribution) - - -class JavascriptVersion(Command): - """write the javascript version to nbclassic javascript""" - description = "Write Jupyter version to javascript" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - nsfile = pjoin(repo_root, "nbclassic", "static", "base", "js", "namespace.js") - with open(nsfile) as f: - lines = f.readlines() - with open(nsfile, 'w') as f: - found = False - for line in lines: - if line.strip().startswith("Jupyter.version"): - line = f' Jupyter.version = "{version}";\n' - found = True - f.write(line) - if not found: - raise RuntimeError("Didn't find Jupyter.version line in %s" % nsfile) - - -def css_js_prerelease(command, strict=False): - """decorator for building minified js/css prior to another command""" - class DecoratedCommand(command): - def run(self): - self.distribution.run_command('jsversion') - jsdeps = self.distribution.get_command_obj('jsdeps') - js = self.distribution.get_command_obj('js') - css = self.distribution.get_command_obj('css') - jsdeps.force = js.force = strict - - targets = [ jsdeps.bower_dir ] - targets.extend(js.targets) - targets.extend(css.targets) - missing = [ t for t in targets if not os.path.exists(t) ] - - if not is_repo and not missing: - # If we're an sdist, we aren't a repo and everything should be present. - # Don't rebuild js/css in that case. - command.run(self) - return - - try: - self.distribution.run_command('js') - self.distribution.run_command('css') - self.distribution.run_command('backendtranslations') - except Exception as e: - # refresh missing - missing = [ t for t in targets if not os.path.exists(t) ] - if strict or missing: - # die if strict or any targets didn't build - prefix = os.path.commonprefix([repo_root + os.sep] + missing) - missing = [ m[len(prefix):] for m in missing ] - log.warn("rebuilding js and css failed. The following required files are missing: %s" % missing) - raise e - else: - log.warn("rebuilding js and css failed (not a problem)") - log.warn(str(e)) - - # check again for missing targets, just in case: - missing = [ t for t in targets if not os.path.exists(t) ] - if missing: - # command succeeded, but targets still missing (?!) - prefix = os.path.commonprefix([repo_root + os.sep] + missing) - missing = [ m[len(prefix):] for m in missing ] - raise ValueError("The following required files are missing: %s" % missing) - - command.run(self) - return DecoratedCommand diff --git a/tools/copy-marked.js b/tools/copy-marked.js new file mode 100644 index 000000000..80e9ae372 --- /dev/null +++ b/tools/copy-marked.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const path = require('path'); + +const sourceFile = path.join('nbclassic', 'static', 'components', 'marked', 'lib', 'marked.umd.js'); +const destFile = path.join('nbclassic', 'static', 'components', 'marked', 'lib', 'marked.js'); + +// Check file existence +const sourceExists = fs.existsSync(sourceFile); +const destExists = fs.existsSync(destFile); + +if (!sourceExists && !destExists) { + // Both files missing - critical error + console.error(`Error: ${destFile} is required but cannot be created (${sourceFile} not found)`); + process.exit(1); +} else if (!sourceExists) { + // Source missing but dest exists - skip copy + console.log(`Copy skipped: ${sourceFile} not found (using existing ${destFile})`); + process.exit(0); +} + +// Source exists, attempt copy +try { + fs.copyFileSync(sourceFile, destFile); + console.log(`Successfully copied ${sourceFile} to ${destFile}`); +} catch (err) { + console.error(`Error copying file: ${err.message}`); + process.exit(1); +}