diff -Nru jupyter-notebook-6.1.4/appveyor.yml jupyter-notebook-6.2.0/appveyor.yml --- jupyter-notebook-6.1.4/appveyor.yml 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/appveyor.yml 2021-01-13 16:30:49.000000000 +0000 @@ -21,7 +21,7 @@ - cmd: conda config --set show_channel_urls true - cmd: conda config --add channels conda-forge #- cmd: conda update --yes --quiet conda - - cmd: conda install -y python=%CONDA_PY_SPEC% pyzmq tornado jupyter_client nbformat ipykernel pip nodejs nose + - cmd: conda install -y python=%CONDA_PY_SPEC% pyzmq tornado jupyter_client nbformat ipykernel pip nodejs pytest nose # not using `conda install -y` on nbconvent package because there is # currently a bug with the version that the anaconda installs, so we will just install it with pip - cmd: pip install nbconvert @@ -29,4 +29,4 @@ - cmd: pip install .[test] test_script: - - nosetests -v notebook --exclude-dir notebook\tests\selenium + - py.test -v notebook --ignore notebook\tests\selenium diff -Nru jupyter-notebook-6.1.4/CONTRIBUTING.rst jupyter-notebook-6.2.0/CONTRIBUTING.rst --- jupyter-notebook-6.1.4/CONTRIBUTING.rst 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/CONTRIBUTING.rst 2021-01-13 16:30:49.000000000 +0000 @@ -171,19 +171,17 @@ To build the documentation you'll need `Sphinx `_, `pandoc `_ and a few other packages. -To install (and activate) a `conda environment`_ named ``notebook_docs`` +To install (and activate) a conda environment named ``notebook_docs`` containing all the necessary packages (except pandoc), use:: - conda env create -f docs/environment.yml + conda create -n notebook_docs pip conda activate notebook_docs # Linux and OS X - activate notebook_docs # Windows - -.. _conda environment: - https://conda.io/docs/user-guide/tasks/manage-environments.html#creating-an-environment-from-an-environment-yml-file + activate notebook_docs # Windows + pip install .[docs] If you want to install the necessary packages with ``pip``, use the following instead:: - pip install -r docs/doc-requirements.txt + pip install .[docs] Once you have installed the required packages, you can build the docs with:: diff -Nru jupyter-notebook-6.1.4/debian/changelog jupyter-notebook-6.2.0/debian/changelog --- jupyter-notebook-6.1.4/debian/changelog 2020-09-18 14:20:36.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/changelog 2021-01-17 19:52:42.000000000 +0000 @@ -1,9 +1,45 @@ -jupyter-notebook (6.1.4-1~ppa20.10~1) groovy; urgency=medium +jupyter-notebook (6.2.0-1~ppa20.10~1) groovy; urgency=medium - * New upstream version 6.1.4 + * New upstream version 6.2.0 + * Version dependencies on tornado, send2trash + * Disable all tests of notebook trashing; these are too sensitive to + different mount and container layouts to be useful. * PPA snapshot for 20.10 - -- Gordon Ball Fri, 18 Sep 2020 16:20:36 +0200 + -- Gordon Ball Sun, 17 Jan 2021 20:52:42 +0100 + +jupyter-notebook (6.1.6-2) unstable; urgency=medium + + * Use uglifyjs instead of node-uglify (Closes: #979898) + + -- Gordon Ball Tue, 12 Jan 2021 18:52:17 +0000 + +jupyter-notebook (6.1.6-1) unstable; urgency=medium + + * New upstream version 6.1.6 + * Run tests using pytest instead of nose, as upstream + + -- Gordon Ball Sat, 26 Dec 2020 20:18:26 +0000 + +jupyter-notebook (6.1.5-1) unstable; urgency=medium + + * New upstream version 6.1.5 + + -- Gordon Ball Sat, 07 Nov 2020 21:07:30 +0000 + +jupyter-notebook (6.1.4-1) unstable; urgency=medium + + [ Gordon Ball ] + * New upstream version 6.1.4 + * Mark the symlink autopkgtest as superficial + + [ Ondřej Nový ] + * d/control: Update Maintainer field with new Debian Python Team + contact address. + * d/control: Update Vcs-* fields with new Debian Python Team Salsa + layout. + + -- Gordon Ball Thu, 24 Sep 2020 19:06:44 +0000 jupyter-notebook (6.1.3-1) unstable; urgency=medium diff -Nru jupyter-notebook-6.1.4/debian/control jupyter-notebook-6.2.0/debian/control --- jupyter-notebook-6.1.4/debian/control 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/control 2021-01-15 15:03:16.000000000 +0000 @@ -1,32 +1,32 @@ Source: jupyter-notebook -Maintainer: Debian Python Modules Team +Maintainer: Debian Python Team Uploaders: Gordon Ball , Jerome Benoit Section: python Priority: optional Standards-Version: 4.5.0 Homepage: https://github.com/jupyter/notebook -Vcs-Git: https://salsa.debian.org/python-team/modules/jupyter-notebook.git -Vcs-Browser: https://salsa.debian.org/python-team/modules/jupyter-notebook +Vcs-Git: https://salsa.debian.org/python-team/packages/jupyter-notebook.git +Vcs-Browser: https://salsa.debian.org/python-team/packages/jupyter-notebook Build-Depends: debhelper-compat (= 13), dh-python, python3-all, python3-setuptools, python3-argon2, python3-nose , - python3-nose-exclude , python3-requests , python3-requests-unixsocket , python3-ipython , python3-jupyter-core (>= 4.6.1) , python3-jupyter-client (>= 5.3.4) , - python3-tornado , + python3-tornado (>= 6.1) , python3-nbformat (>= 4.4) , python3-nbconvert (>= 5) , python3-ipykernel , python3-prometheus-client , + python3-pytest , python3-terminado (>= 0.8.3) , python3-entrypoints , - python3-send2trash , + python3-send2trash (>= 1.5) , python3-zmq , python3-sphinx , python3-sphinx-rtd-theme , @@ -54,11 +54,11 @@ node-source-map, node-requirejs (>= 2.3), node-react (>= 16.13), - node-uglify, node-po2json, node-fbjs, node-loose-envify, node-object-assign, + uglifyjs, webpack, pandoc Testsuite: autopkgtest-pkg-python diff -Nru jupyter-notebook-6.1.4/debian/patches/0002-Use-local-MathJax-in-documentation.patch jupyter-notebook-6.2.0/debian/patches/0002-Use-local-MathJax-in-documentation.patch --- jupyter-notebook-6.1.4/debian/patches/0002-Use-local-MathJax-in-documentation.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0002-Use-local-MathJax-in-documentation.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py -index 7e924f0..ed9e491 100644 +index 6987abb..a649138 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py -@@ -348,6 +348,8 @@ intersphinx_mapping = { +@@ -347,6 +347,8 @@ intersphinx_mapping = { 'jupyter': ('https://jupyter.readthedocs.io/en/latest/', None), } diff -Nru jupyter-notebook-6.1.4/debian/patches/0004-Call-lessc-with-source-map-basepath-option-for-repro.patch jupyter-notebook-6.2.0/debian/patches/0004-Call-lessc-with-source-map-basepath-option-for-repro.patch --- jupyter-notebook-6.1.4/debian/patches/0004-Call-lessc-with-source-map-basepath-option-for-repro.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0004-Call-lessc-with-source-map-basepath-option-for-repro.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupbase.py b/setupbase.py -index c9454ee..7eeb243 100644 +index 40c3f35..0a85d82 100644 --- a/setupbase.py +++ b/setupbase.py -@@ -477,7 +477,7 @@ class CompileCSS(Command): +@@ -474,7 +474,7 @@ class CompileCSS(Command): for src, dst in zip(self.sources, self.targets): try: run(['lessc', diff -Nru jupyter-notebook-6.1.4/debian/patches/0005-Work-around-https-github.com-less-less.js-pull-3002.patch jupyter-notebook-6.2.0/debian/patches/0005-Work-around-https-github.com-less-less.js-pull-3002.patch --- jupyter-notebook-6.1.4/debian/patches/0005-Work-around-https-github.com-less-less.js-pull-3002.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0005-Work-around-https-github.com-less-less.js-pull-3002.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupbase.py b/setupbase.py -index 7eeb243..28dec37 100644 +index 0a85d82..3a72f78 100644 --- a/setupbase.py +++ b/setupbase.py -@@ -480,7 +480,7 @@ class CompileCSS(Command): +@@ -477,7 +477,7 @@ class CompileCSS(Command): '--source-map', '--source-map-basepath='+os.path.dirname(__file__), '--include-path=%s' % pipes.quote(static), src, diff -Nru jupyter-notebook-6.1.4/debian/patches/0006-Debian-specific-hack-to-fix-upstream-s-non-increment.patch jupyter-notebook-6.2.0/debian/patches/0006-Debian-specific-hack-to-fix-upstream-s-non-increment.patch --- jupyter-notebook-6.1.4/debian/patches/0006-Debian-specific-hack-to-fix-upstream-s-non-increment.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0006-Debian-specific-hack-to-fix-upstream-s-non-increment.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,7 +7,7 @@ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py -index de6ab7a..95626b2 100755 +index 676c576..c30235d 100755 --- a/setup.py +++ b/setup.py @@ -147,18 +147,21 @@ class bdist_egg_disabled(bdist_egg): diff -Nru jupyter-notebook-6.1.4/debian/patches/0007-Ignore-errors-in-documentation-notebooks-during-buil.patch jupyter-notebook-6.2.0/debian/patches/0007-Ignore-errors-in-documentation-notebooks-during-buil.patch --- jupyter-notebook-6.1.4/debian/patches/0007-Ignore-errors-in-documentation-notebooks-during-buil.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0007-Ignore-errors-in-documentation-notebooks-during-buil.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py -index ed9e491..ee2671d 100644 +index a649138..f08e073 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py -@@ -355,3 +355,4 @@ spelling_word_list_filename='spelling_wordlist.txt' +@@ -354,3 +354,4 @@ spelling_word_list_filename='spelling_wordlist.txt' # import before any doc is built, so _ is guaranteed to be injected import notebook.transutils diff -Nru jupyter-notebook-6.1.4/debian/patches/0009-Don-t-try-and-patch-bootstrap-less.patch jupyter-notebook-6.2.0/debian/patches/0009-Don-t-try-and-patch-bootstrap-less.patch --- jupyter-notebook-6.1.4/debian/patches/0009-Don-t-try-and-patch-bootstrap-less.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0009-Don-t-try-and-patch-bootstrap-less.patch 2021-01-15 15:03:16.000000000 +0000 @@ -12,10 +12,10 @@ 1 file changed, 2 deletions(-) diff --git a/setupbase.py b/setupbase.py -index 28dec37..d81c1d1 100644 +index 3a72f78..436991c 100644 --- a/setupbase.py +++ b/setupbase.py -@@ -472,8 +472,6 @@ class CompileCSS(Command): +@@ -469,8 +469,6 @@ class CompileCSS(Command): env = os.environ.copy() env['PATH'] = npm_path diff -Nru jupyter-notebook-6.1.4/debian/patches/0011-use-system-po2json.patch jupyter-notebook-6.2.0/debian/patches/0011-use-system-po2json.patch --- jupyter-notebook-6.1.4/debian/patches/0011-use-system-po2json.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0011-use-system-po2json.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupbase.py b/setupbase.py -index 5a21cb5..c9454ee 100644 +index fb3327d..40c3f35 100644 --- a/setupbase.py +++ b/setupbase.py -@@ -553,7 +553,7 @@ class CompileJS(Command): +@@ -550,7 +550,7 @@ class CompileJS(Command): def build_jstranslation(self, trd): lang = trd[-5:] run([ diff -Nru jupyter-notebook-6.1.4/debian/patches/0012-Skip-all-send2trash-tests.patch jupyter-notebook-6.2.0/debian/patches/0012-Skip-all-send2trash-tests.patch --- jupyter-notebook-6.1.4/debian/patches/0012-Skip-all-send2trash-tests.patch 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0012-Skip-all-send2trash-tests.patch 2021-01-15 15:03:16.000000000 +0000 @@ -0,0 +1,92 @@ +From: Gordon Ball +Date: Thu, 14 Jan 2021 15:56:57 +0000 +Subject: Skip all send2trash tests + +send2trash fails in various unpredictable ways in container +environments, overlays and bind mounts. This leads to lots of issues +with different build and CI environments. +--- + notebook/services/contents/tests/test_contents_api.py | 5 +++++ + notebook/services/contents/tests/test_manager.py | 4 ++++ + 2 files changed, 9 insertions(+) + +diff --git a/notebook/services/contents/tests/test_contents_api.py b/notebook/services/contents/tests/test_contents_api.py +index 6e4ad49..7f0e01f 100644 +--- a/notebook/services/contents/tests/test_contents_api.py ++++ b/notebook/services/contents/tests/test_contents_api.py +@@ -11,6 +11,7 @@ from unicodedata import normalize + + pjoin = os.path.join + ++import pytest + import requests + from send2trash import send2trash + from send2trash.exceptions import TrashPermissionError +@@ -510,6 +511,7 @@ class APITest(NotebookTestBase): + with assert_http_error(400): + resp = self.api.copy(u'å b', u'foo') + ++ @pytest.mark.skip("skip send2trash tests") + def test_delete(self): + for d, name in self.dirs_nbs: + print('%r, %r' % (d, name)) +@@ -523,6 +525,7 @@ class APITest(NotebookTestBase): + print(nbs) + self.assertEqual(nbs, []) + ++ @pytest.mark.skip("skip send2trash tests") + def test_delete_dirs(self): + # depth-first delete everything, so we don't try to delete empty directories + for name in sorted(self.dirs + ['/'], key=len, reverse=True): +@@ -532,6 +535,7 @@ class APITest(NotebookTestBase): + listing = self.api.list('/').json()['content'] + self.assertEqual(listing, []) + ++ @pytest.mark.skip("skip send2trash tests") + def test_delete_non_empty_dir(self): + if sys.platform == 'win32': + self.skipTest("Disabled deleting non-empty dirs on Windows") +@@ -559,6 +563,7 @@ class APITest(NotebookTestBase): + self.assertIn('z.ipynb', nbnames) + self.assertNotIn('a.ipynb', nbnames) + ++ @pytest.mark.skip("skip send2trash tests") + def test_checkpoints_follow_file(self): + + # Read initial file state +diff --git a/notebook/services/contents/tests/test_manager.py b/notebook/services/contents/tests/test_manager.py +index dfe5d27..bb854eb 100644 +--- a/notebook/services/contents/tests/test_manager.py ++++ b/notebook/services/contents/tests/test_manager.py +@@ -6,6 +6,7 @@ import time + from contextlib import contextmanager + from itertools import combinations + ++import pytest + from tornado.web import HTTPError + from unittest import TestCase, skipIf + from tempfile import NamedTemporaryFile +@@ -181,6 +182,7 @@ class TestFileContentsManager(TestCase): + else: + self.fail("Should have raised HTTPError(403)") + ++ @pytest.mark.skip("skip send2trash tests") + def test_escape_root(self): + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) +@@ -506,6 +508,7 @@ class TestContentsManager(TestCase): + self.assertEqual(model['name'], 'Untitled.ipynb') + self.assertEqual(model['path'], 'foo/Untitled.ipynb') + ++ @pytest.mark.skip("skip send2trash tests") + def test_delete(self): + cm = self.contents_manager + # Create a notebook +@@ -574,6 +577,7 @@ class TestContentsManager(TestCase): + # Created a notebook in the renamed directory should work + cm.new_untitled("foo/bar_diff", ext=".ipynb") + ++ @pytest.mark.skip("skip send2trash tests") + def test_delete_root(self): + cm = self.contents_manager + with self.assertRaises(HTTPError) as err: diff -Nru jupyter-notebook-6.1.4/debian/patches/0013-Avoid-manipulating-PYTHONPATH-in-tests.patch jupyter-notebook-6.2.0/debian/patches/0013-Avoid-manipulating-PYTHONPATH-in-tests.patch --- jupyter-notebook-6.1.4/debian/patches/0013-Avoid-manipulating-PYTHONPATH-in-tests.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0013-Avoid-manipulating-PYTHONPATH-in-tests.patch 2021-01-15 15:03:16.000000000 +0000 @@ -13,10 +13,10 @@ 1 file changed, 2 deletions(-) diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py -index 25ce8c8..b704c66 100644 +index bb5f8b7..cf43665 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py -@@ -97,9 +97,7 @@ class NotebookTestBase(TestCase): +@@ -98,9 +98,7 @@ class NotebookTestBase(TestCase): def get_patch_env(cls): return { 'HOME': cls.home_dir, diff -Nru jupyter-notebook-6.1.4/debian/patches/0013-documentation-fixes.patch jupyter-notebook-6.2.0/debian/patches/0013-documentation-fixes.patch --- jupyter-notebook-6.1.4/debian/patches/0013-documentation-fixes.patch 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/0013-documentation-fixes.patch 2021-01-15 15:03:16.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py -index ee2671d..6e8ae93 100644 +index f08e073..5a681ee 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py -@@ -68,9 +68,7 @@ extensions = [ +@@ -67,9 +67,7 @@ extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.autosummary', 'sphinx.ext.mathjax', diff -Nru jupyter-notebook-6.1.4/debian/patches/series jupyter-notebook-6.2.0/debian/patches/series --- jupyter-notebook-6.1.4/debian/patches/series 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/patches/series 2021-01-15 15:03:16.000000000 +0000 @@ -9,3 +9,4 @@ 0013-documentation-fixes.patch 0013-Avoid-manipulating-PYTHONPATH-in-tests.patch 0011-Requirejs-shim-config-for-xterm-xterm-addon-fit.patch +0012-Skip-all-send2trash-tests.patch diff -Nru jupyter-notebook-6.1.4/debian/rules jupyter-notebook-6.2.0/debian/rules --- jupyter-notebook-6.1.4/debian/rules 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/rules 2021-01-15 15:03:16.000000000 +0000 @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 # selenium tests require extra dependencies # test_notebookapp_integration tries to invoke jupyter-notebook which isn't in the PATH when this runs -export PYBUILD_TEST_ARGS=--exclude-dir notebook/tests/selenium --exclude test_notebookapp_integration +export PYBUILD_TEST_ARGS=--ignore=notebook/tests/selenium -k 'not notebookapp_integration' %: @@ -140,12 +140,6 @@ echo "js:Built-Using=$(BUILTUSING)" >> debian/python3-notebook.substvars dh_gencontrol -override_dh_auto_test: - mkdir -p debian/runtime/home debian/runtime/tmpdir debian/runtime/xdg - # send2trash deletion tests fail if the temporary notebook files - # are on a different block device to the implicit trash directory in HOME - TMPDIR=$(CURDIR)/debian/runtime/tmpdir XDG_RUNTIME_DIR=$(CURDIR)/debian/runtime/xdg HOME=$(CURDIR)/debian/runtime/home dh_auto_test - override_dh_installchangelogs: dh_installchangelogs -k docs/source/changelog.rst diff -Nru jupyter-notebook-6.1.4/debian/tests/control jupyter-notebook-6.2.0/debian/tests/control --- jupyter-notebook-6.1.4/debian/tests/control 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/tests/control 2021-01-15 15:03:16.000000000 +0000 @@ -1,6 +1,7 @@ -Tests: nose -Depends: @, python3-pytest, python3-nose, python3-nose-exclude, python3-requests, python3-requests-unixsocket +Tests: pytest +Depends: @, python3-pytest, python3-nose, python3-requests, python3-requests-unixsocket Restrictions: allow-stderr Test-Command: find /usr/lib/python3/dist-packages/notebook -xtype l >&2 Depends: @ +Restrictions: superficial diff -Nru jupyter-notebook-6.1.4/debian/tests/nose jupyter-notebook-6.2.0/debian/tests/nose --- jupyter-notebook-6.1.4/debian/tests/nose 2020-09-15 11:09:02.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/tests/nose 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -#!/bin/sh -e - -for d in tmp home/runtime home/data/trash; do - mkdir -p $AUTOPKGTEST_TMP/$d -done - -# try and ensure files are on the same device, since otherwise -# deletion-related tests fail attempting a cross-device rename -# by default, debci seems to place /tmp and $AUTOPKGTEST_TMP on -# different devices -export HOME=$AUTOPKGTEST_TMP/home -export XDG_RUNTIME_DIR=$AUTOPKGTEST_TMP/home/runtime -export XDG_DATA_HOME=$AUTOPKGTEST_TMP/home/data -export TMPDIR=$AUTOPKGTEST_TMP/tmp -export NOSE_IGNORE_CONFIG_FILES=1 - -chmod 0700 $XDG_RUNTIME_DIR - -nosetests3 --exclude-dir notebook/tests/selenium diff -Nru jupyter-notebook-6.1.4/debian/tests/pytest jupyter-notebook-6.2.0/debian/tests/pytest --- jupyter-notebook-6.1.4/debian/tests/pytest 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/debian/tests/pytest 2021-01-15 15:03:16.000000000 +0000 @@ -0,0 +1,7 @@ +#!/bin/sh -e + +export XDG_RUNTIME_DIR=$AUTOPKGTEST_TMP/runtime +mkdir -p $XDG_RUNTIME_DIR +chmod 0700 $XDG_RUNTIME_DIR + +python3 -m pytest --ignore=notebook/tests/selenium diff -Nru jupyter-notebook-6.1.4/docs/source/changelog.rst jupyter-notebook-6.2.0/docs/source/changelog.rst --- jupyter-notebook-6.1.4/docs/source/changelog.rst 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/docs/source/changelog.rst 2021-01-13 16:30:49.000000000 +0000 @@ -22,6 +22,59 @@ ``pip --version``. + +.. _release-6.2.0: + +6.2.0 +----- + +Merged PRs +~~~~~~~~~~ + +- Increase minimum tornado version (:ghpull:`5933`) +- Adjust skip decorators to avoid remaining dependency on nose (:ghpull:`5932`) +- Ensure that cell ids persist after save (:ghpull:`5928`) +- Add reconnection to Gateway (form nb2kg) (:ghpull:`5924`) +- Fix some typos (:ghpull:`5917`) +- Handle TrashPermissionError, now that it exists (:ghpull:`5894`) + +Thank you to all the contributors: + +- @kevin-bates +- @mishaschwartz +- @oyvsyo +- @user202729 +- @stefanor + +.. _release-6.1.6: + +6.1.6 +----- + +Merged PRs +~~~~~~~~~~ + +* do not require nose for testing (:ghpull:`5826`) +* [docs] Update Chinese and Hindi readme.md (:ghpull:`5823`) +* Add support for creating terminals via GET (:ghpull:`5813`) +* Made doc translations in Hindi and Chinese (:ghpull:`5787`) + +Thank you to all the contributors: + +- @pgajdos +- @rjn01 +- @kevin-bates +- @virejdasani + +.. _release-6.1.5: + +6.1.5 +----- + +6.1.5 is a security release, fixing one vulnerability: + +- Fix open redirect vulnerability GHSA-c7vm-f5p4-8fqh (CVE to be assigned) + .. _release-6.1.4: 6.1.4 diff -Nru jupyter-notebook-6.1.4/docs/source/config_overview.rst jupyter-notebook-6.2.0/docs/source/config_overview.rst --- jupyter-notebook-6.1.4/docs/source/config_overview.rst 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/docs/source/config_overview.rst 2021-01-13 16:30:49.000000000 +0000 @@ -54,12 +54,11 @@ Notebook front-end client ------------------------- -- :ref:`How front-end configuration works ` - * :ref:`Example: Changing the notebook's default indentation setting - ` - * :ref:`Example: Restoring the notebook's default indentation setting - ` -- :ref:`Persisting configuration settings ` + +.. toctree:: + :maxdepth: 2 + + frontend_config .. _configure_nbextensions: diff -Nru jupyter-notebook-6.1.4/docs/source/conf.py jupyter-notebook-6.2.0/docs/source/conf.py --- jupyter-notebook-6.1.4/docs/source/conf.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/docs/source/conf.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # Jupyter Notebook documentation build configuration file, created by # sphinx-quickstart on Mon Apr 13 09:51:11 2015. diff -Nru "/tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb" "/tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb" --- "/tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb" 2020-09-08 17:47:36.000000000 +0000 +++ "/tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb" 2021-01-13 16:30:49.000000000 +0000 @@ -135,7 +135,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When you run the notebook web application on your computer, notebook documents are just **files on your local filesystem with a `.ipynb` extension**. This allows you to use familiar workflows for organizing your notebooks into folders and sharing them with others." + "When you run the notebook web application on your computer, notebook documents are just **files on your local filesystem with a** `.ipynb` **extension**. This allows you to use familiar workflows for organizing your notebooks into folders and sharing them with others." ] }, { @@ -148,7 +148,7 @@ "* **Markdown cells:** Narrative text with embedded LaTeX equations\n", "* **Raw cells:** Unformatted text that is included, without modification, when notebooks are converted to different formats using nbconvert\n", "\n", - "Internally, notebook documents are **[JSON](https://en.wikipedia.org/wiki/JSON) data** with **binary values [base64](https://en.wikipedia.org/wiki/Base64)** encoded. This allows them to be **read and manipulated programmatically** by any programming language. Because JSON is a text format, notebook documents are version control friendly.\n", + "Internally, notebook documents are [JSON](https://en.wikipedia.org/wiki/JSON) **data** with **binary values** [base64](https://en.wikipedia.org/wiki/Base64) encoded. This allows them to be **read and manipulated programmatically** by any programming language. Because JSON is a text format, notebook documents are version control friendly.\n", "\n", "**Notebooks can be exported** to different static formats including HTML, reStructeredText, LaTeX, PDF, and slide shows ([reveal.js](https://revealjs.com)) using Jupyter's `nbconvert` utility.\n", "\n", diff -Nru "/tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs/source/examples/Notebook/Working With Markdown Cells.ipynb" "/tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs/source/examples/Notebook/Working With Markdown Cells.ipynb" --- "/tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs/source/examples/Notebook/Working With Markdown Cells.ipynb" 2020-09-08 17:47:36.000000000 +0000 +++ "/tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs/source/examples/Notebook/Working With Markdown Cells.ipynb" 2021-01-13 16:30:49.000000000 +0000 @@ -175,7 +175,9 @@ "Courtesy of MathJax, you can include mathematical expressions both inline: \n", "$e^{i\\pi} + 1 = 0$ and displayed:\n", "\n", - "$$e^x=\\sum_{i=0}^\\infty \\frac{1}{i!}x^i$$\n", + "\\begin{equation}\n", + "e^x=\\sum_{i=0}^\\infty \\frac{1}{i!}x^i\n", + "\\end{equation}\n", "\n", "Inline expressions can be added by surrounding the latex code with `$`:\n", "\n", @@ -183,10 +185,12 @@ "$e^{i\\pi} + 1 = 0$\n", "```\n", "\n", - "Expressions on their own line are surrounded by `$$`:\n", + "Expressions on their own line are surrounded by `\\begin{equation}` and `\\end{equation}`:\n", "\n", "```latex\n", - "$$e^x=\\sum_{i=0}^\\infty \\frac{1}{i!}x^i$$\n", + "\\begin{equation}\n", + "e^x=\\sum_{i=0}^\\infty \\frac{1}{i!}x^i\n", + "\\end{equation}\n", "```" ] }, diff -Nru jupyter-notebook-6.1.4/docs/source/frontend_config.rst jupyter-notebook-6.2.0/docs/source/frontend_config.rst --- jupyter-notebook-6.1.4/docs/source/frontend_config.rst 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/docs/source/frontend_config.rst 2021-01-13 16:30:49.000000000 +0000 @@ -48,6 +48,18 @@ `_ which are available for configuration. +You can similarly change the options of the file editor by entering the following +snippet in the browser's Javascript console once (from a file editing page).:: + + var config = Jupyter.editor.config + var patch = { + Editor: { + codemirror_options: { + indentUnit: 2 + } + } + } + config.update(patch) Example - Restoring the notebook's default indentation ------------------------------------------------------ diff -Nru jupyter-notebook-6.1.4/docs-translations/hi-IN/README.md jupyter-notebook-6.2.0/docs-translations/hi-IN/README.md --- jupyter-notebook-6.1.4/docs-translations/hi-IN/README.md 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/docs-translations/hi-IN/README.md 2021-01-13 16:30:49.000000000 +0000 @@ -0,0 +1,77 @@ +# Jupyter Notebook + +[![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) +[![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) + + + +Jupyter नोटबुक इंटरैक्टिव के लिए एक वेब-आधारित नोटबुक वातावरण है +कंप्यूटिंग। + +![Jupyter notebook example](docs/resources/running_code_med.png "Jupyter notebook example") + +### नोटिस +कृपया ध्यान दें कि इस भंडार का रखरखाव वर्तमान में जुपिटर समुदाय के एक कंकाल के दल द्वारा किया जाता है। हम उपयोगकर्ताओं को जुपिटरलैब में संक्रमण के लिए प्रोत्साहित करते हैं, जहां अधिक तत्काल समर्थन हो सकता है। हमारा दृष्टिकोण आगे बढ़ेगा: + +1. जुपिटर नोटबुक की सुरक्षा बनाए रखने के लिए। इसका मतलब है कि सुरक्षा से संबंधित मुद्दे और पुल अनुरोध हमारी सर्वोच्च प्राथमिकता है। +2. JupyterLab को संबोधित करने के लिए [समता मुद्दों की सुविधा](https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aopen+is%3Aissue+label%3A%22tag%3AFeature+Parity%22)| इस प्रयास के हिस्से के रूप में, हम एक बेहतर [नोटबुक-ओनली एक्सपीरियंस](https://github.com/jupyterlab/jupyterlab/issues/8450)JupyterLab में उन उपयोगकर्ताओं के लिए जो क्लासिक Jupyter नोटबुक के UI को पसंद करते हैं। +3. समुदाय के सदस्यों की कड़ी मेहनत के प्रति उत्तरदायी होना जिन्होंने पुल अनुरोधों को खोला है। हम इन पीआर को ट्राई कर रहे हैं। हम इस समय नई सुविधाओं का समर्थन या रखरखाव नहीं कर सकते हैं, लेकिन हम सुरक्षा और अन्य स्थिरता सुधारों का स्वागत करते हैं। + +यदि आपके पास एक नई सुविधा के साथ एक खुला पुल अनुरोध है या यदि आप एक खोलने की योजना बना रहे हैं, तो कृपया इसे [नोटबुक एक्सटेंशन](https://jupyter-notebook.readthedocs.io/en/stable/extending/) के रूप में शिपिंग करने पर विचार करें। बजाय। + +##### `नोटबुक` में योगदान करने के लिए विकल्प +इसके अतिरिक्त, कृपया विचार करें कि क्या आपका योगदान Jupyter फ्रंट-एंड के लिए अंतर्निहित सर्वर के लिए उपयुक्त होगा, [jupyter server](https://github.com/jupyter/jupyter_server) या में [JupyterLab फ़्रंट एंड](https://github.com/jupyterlab/jupyterlab). + +### जुपिटर नोटबुक, आइपीथॉन नोटबुक की भाषा-अज्ञेय विकास +Jupyter नोटबुक एक भाषा-अज्ञेय HTML नोटबुक अनुप्रयोग है +प्रोजेक्ट जुपिटर। 2015 में, जुपिटर नोटबुक के एक भाग के रूप में जारी किया गया था +IPython कोडबेस का बिग स्प्लिट ™। IPython 3 अंतिम प्रमुख अखंड था +दोनों भाषा-अज्ञेयवादी कोड, जैसे *IPython नोटबुक*, +और भाषा विशिष्ट कोड, जैसे कि *अजगर के लिए आईपीथॉन कर्नेल*। जैसा +कई भाषाओं में कंप्यूटिंग स्पैन, प्रोजेक्ट जुपिटर विकसित करना जारी रखेगा +भाषा-अज्ञेय **जुपिटर नोटबुक** इस रेपो में और की मदद से +समुदाय भाषा विशिष्ट गुठली विकसित करते हैं जो अपने आप में पाए जाते हैं +असतत रेपो। +[[Big Split™ घोषणा](https://blog.jupyter.org/the-big-split-9d7b88a031a7)] +[[Jupyter आरोही ब्लॉग पोस्ट](https://blog.jupyter.org/jupyter-ascending-1bf5b362d97e)] + +## स्थापना +आप के लिए स्थापना प्रलेखन पा सकते हैं +[बृहस्पति मंच, ReadTheDocs पर](https://jupyter.readthedocs.io/en/latest/install.html). +जुपिटर नोटबुक के उन्नत उपयोग के लिए दस्तावेज पाया जा सकता है +[यहाँ](https://jupyter-notebook.readthedocs.io/en/latest/). + +स्थानीय स्थापना के लिए, सुनिश्चित करें कि आपके पास है +[pip स्थापित](https://pip.readthedocs.io/en/stable/installing/) और भाग खड़ा हुआ: + + $ pip install notebook + +## उपयोग - जुपिटर नोटबुक चल रहा है + +### स्थानीय स्थापना में चल रहा है + +इसके साथ लॉन्च करें: + + $ jupyter notebook + +### एक दूरस्थ स्थापना में चल रहा है + +आपको बृहस्पति नोटबुक को दूरस्थ रूप से शुरू करने से पहले कुछ कॉन्फ़िगरेशन की आवश्यकता है। देखें [नोटबुक सर्वर चला रहा है](https://jupyter-notebook.readthedocs.io/en/stable/public_server.html). + +## विकास स्थापना + +स्थानीय विकास की स्थापना कैसे करें, इसके लिए [`CONTRIBUTING.rst`](CONTRIBUTING.rst) देखें। + +## योगदान + +यदि आप इस परियोजना में योगदान देने में रुचि रखते हैं, तो [`CONTRIBUTING.rst`](CONTRIBUTING.rst) देखें। + +## साधन +- [Project Jupyter website](https://jupyter.org) +- [Online Demo at jupyter.org/try](https://jupyter.org/try) +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] +- [Korean Version of Installation](https://github.com/ChungJooHo/Jupyter_Kor_doc/) +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Issues](https://github.com/jupyter/notebook/issues) +- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) \ No newline at end of file Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/hi-IN/resources/dashboard.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/hi-IN/resources/dashboard.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/hi-IN/resources/edit_mode.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/hi-IN/resources/edit_mode.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/hi-IN/resources/file_editor.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/hi-IN/resources/file_editor.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/hi-IN/resources/Notebook_Editor.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/hi-IN/resources/Notebook_Editor.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/hi-IN/resources/running_code_med.png and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/hi-IN/resources/running_code_med.png differ diff -Nru jupyter-notebook-6.1.4/docs-translations/zh-CN/README.md jupyter-notebook-6.2.0/docs-translations/zh-CN/README.md --- jupyter-notebook-6.1.4/docs-translations/zh-CN/README.md 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/docs-translations/zh-CN/README.md 2021-01-13 16:30:49.000000000 +0000 @@ -0,0 +1,77 @@ +# Jupyter Notebook + +[![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) +[![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) + + + +Jupyter Notebook是用于交互的基于Web的笔记本环境 +计算。 + +![Jupyter notebook example](docs/resources/running_code_med.png "Jupyter notebook example") + +### 注意 +请注意,这家商店目前由木星社区的骨干团队维护。我们鼓励用户过渡到 JupyterLab,那里可能会立即提供更多支持。我们的方法将向前发展: + +1.维护Jupiter笔记本电脑的安全性。这意味着与安全相关的问题和请求是我们的首要任务。 +2.解决JupyterLab [促进平等问题](https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aopen+is%3Aissue+label%3A%22tag%3AFeature+Parity%22)|作为这项工作的一部分,我们有更好的[仅限笔记本电脑的体验](https://github.com/jupyterlab/jupyterlab/issues/8450)在JupyterLab中,适合喜欢经典Jupyter笔记本UI的用户。 +3.负责提出请求请求的社区成员的辛勤工作。我们正在尝试这些PR。我们目前无法支持或维护新设施,但是我们欢迎安全性和其他稳定性方面的改进。 + +如果您有一个具有新功能的打开请求请求,或者您打算打开一个请求,请将该请求命名为[notebook extension](https://jupyter-notebook.readthedocs.io/en/stable/extending/) 考虑运送为。代替。 + +##### 选择贡献“笔记本” +此外,请考虑您的贡献是否适合Jupyter前端的基础服务器, [jupyter server](https://github.com/jupyter/jupyter_server) 或在 [JupyterLab 前端](https://github.com/jupyterlab/jupyterlab). + +### Jupyter笔记本,与IPython笔记本无关的语言开发 +Jupyter Notebook是与语言无关的HTML Notebook应用程序 +木星计划。 2015年,木星作为笔记本的一部分发布 +IPython代码库的Big Split™。 IPython 3是最后一个主要的整体 +两种与语言无关的代码,例如 *IPython notebook*, +以及特定语言的代码,例如 *用于Python的IPython内核* 。如 +通过多种语言计算SPAN,Jupyter项目将继续发展 +与语言无关 **Jupyter Notebook** 在此仓库中更多帮助下 +社区开发自己发现的特定于语言的内核 +离散回购。 +[[Big Split™ 宣言](https://blog.jupyter.org/the-big-split-9d7b88a031a7)] +[[Jupyter 升序博客文章](https://blog.jupyter.org/jupyter-ascending-1bf5b362d97e)] + +## 成立 +您可以找到以下安装文件 +[Jupiter论坛,在ReadTheDocs上](https://jupyter.readthedocs.io/en/latest/install.html). +可以找到有关Jupiter笔记本的高级使用的文档 +[这里](https://jupyter-notebook.readthedocs.io/en/latest/). + +对于本地安装,请确保您已经 +[pip 成立时间](https://pip.readthedocs.io/en/stable/installing/) 并运行: + + $ pip install notebook + +## 用法-运行木星笔记本 + +### 在本地安装中运行 + +与启动 + + $ jupyter笔记本 + +### 在远程安装中运行 + +在远程启动Jupiter笔记本电脑之前,需要进行一些配置。请参阅 [运行笔记本服务器](https://jupyter-notebook.readthedocs.io/en/stable/public_server.html). + +## 开发设置 + +有关如何建立本地发展 [`CONTRIBUTING.rst`](CONTRIBUTING.rst) 看到。 + +## 贡献 + +如果您有兴趣为这个项目做贡献,请参阅 [`CONTRIBUTING.rst`](CONTRIBUTING.rst). + +## 资源 +- [Project Jupyter website](https://jupyter.org) +- [Online Demo at jupyter.org/try](https://jupyter.org/try) +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] +- [Korean Version of Installation](https://github.com/ChungJooHo/Jupyter_Kor_doc/) +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Issues](https://github.com/jupyter/notebook/issues) +- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) \ No newline at end of file Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/zh-CN/resources/dashboard.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/zh-CN/resources/dashboard.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/zh-CN/resources/edit_mode.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/zh-CN/resources/edit_mode.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/zh-CN/resources/file_editor.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/zh-CN/resources/file_editor.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/zh-CN/resources/Notebook_Editor.GIF and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/zh-CN/resources/Notebook_Editor.GIF differ Binary files /tmp/tmpVPGDnS/E0y011L9x5/jupyter-notebook-6.1.4/docs-translations/zh-CN/resources/running_code_med.png and /tmp/tmpVPGDnS/H33b62u1IS/jupyter-notebook-6.2.0/docs-translations/zh-CN/resources/running_code_med.png differ diff -Nru jupyter-notebook-6.1.4/.github/ISSUE_TEMPLATE/bug_report.md jupyter-notebook-6.2.0/.github/ISSUE_TEMPLATE/bug_report.md --- jupyter-notebook-6.1.4/.github/ISSUE_TEMPLATE/bug_report.md 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/.github/ISSUE_TEMPLATE/bug_report.md 2021-01-13 16:30:49.000000000 +0000 @@ -0,0 +1,50 @@ +--- +name: Is this a bug in Notebook? Open an issue. +about: If you're not sure, feel free to post your question on Jupyter's Discourse channel. +title: '' +labels: '' +assignees: '' + +--- + + + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff -Nru jupyter-notebook-6.1.4/.github/ISSUE_TEMPLATE/config.yml jupyter-notebook-6.2.0/.github/ISSUE_TEMPLATE/config.yml --- jupyter-notebook-6.1.4/.github/ISSUE_TEMPLATE/config.yml 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/.github/ISSUE_TEMPLATE/config.yml 2021-01-13 16:30:49.000000000 +0000 @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Is this a common issue? See our Docs. + url: https://jupyter-notebook.readthedocs.io/en/stable/troubleshooting.html#what-to-do-when-things-go-wrong + about: Before posting an issue here, make sure your issue hasn't already been addressed here. + - name: Do you need support or a question answered? See Jupyter Discourse. + url: https://discourse.jupyter.org/c/notebook/31 + about: If you have a question or you're having issues installing Jupyter Notebook, try posting on Discourse. There are lots of friendly Joyvans there to help! + - name: Do you have a feature request? See JupyterLab. + url: https://github.com/jupyterlab/jupyterlab + about: Jupyter Notebook is in a maintenance-only phase. We won't likely accept new features; instead, we recommend you check out JupyterLab for new features and support. diff -Nru jupyter-notebook-6.1.4/notebook/auth/tests/test_security.py jupyter-notebook-6.2.0/notebook/auth/tests/test_security.py --- jupyter-notebook-6.1.4/notebook/auth/tests/test_security.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/auth/tests/test_security.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,22 +1,20 @@ -# coding: utf-8 from ..security import passwd, passwd_check -import nose.tools as nt def test_passwd_structure(): p = passwd('passphrase') algorithm, hashed = p.split(':') - nt.assert_equal(algorithm, 'argon2') - nt.assert_true(hashed.startswith('$argon2id$')) + assert algorithm == 'argon2' + assert hashed.startswith('$argon2id$') def test_roundtrip(): p = passwd('passphrase') - nt.assert_equal(passwd_check(p, 'passphrase'), True) + assert passwd_check(p, 'passphrase') == True def test_bad(): p = passwd('passphrase') - nt.assert_equal(passwd_check(p, p), False) - nt.assert_equal(passwd_check(p, 'a:b:c:d'), False) - nt.assert_equal(passwd_check(p, 'a:b'), False) + assert passwd_check(p, p) == False + assert passwd_check(p, 'a:b:c:d') == False + assert passwd_check(p, 'a:b') == False def test_passwd_check_unicode(): # GH issue #4524 diff -Nru jupyter-notebook-6.1.4/notebook/base/handlers.py jupyter-notebook-6.2.0/notebook/base/handlers.py --- jupyter-notebook-6.1.4/notebook/base/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/base/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -301,7 +301,7 @@ def set_default_headers(self): """Add CORS headers, if defined""" - super(IPythonHandler, self).set_default_headers() + super().set_default_headers() if self.allow_origin: self.set_header("Access-Control-Allow-Origin", self.allow_origin) elif self.allow_origin_pat: @@ -404,6 +404,10 @@ Used on GET for api endpoints and /files/ to block cross-site inclusion (XSSI). """ + + if self.allow_origin == "*" or self.skip_check_origin(): + return True + host = self.request.headers.get("Host") referer = self.request.headers.get("Referer") @@ -442,7 +446,7 @@ # Servers without authentication are vulnerable to XSRF return try: - return super(IPythonHandler, self).check_xsrf_cookie() + return super().check_xsrf_cookie() except web.HTTPError as e: if self.request.method in {'GET', 'HEAD'}: # Consider Referer a sufficient cross-origin check for GET requests @@ -496,7 +500,7 @@ def prepare(self): if not self.check_host(): raise web.HTTPError(403) - return super(IPythonHandler, self).prepare() + return super().prepare() #--------------------------------------------------------------- # template rendering @@ -591,7 +595,7 @@ def prepare(self): if not self.check_origin(): raise web.HTTPError(404) - return super(APIHandler, self).prepare() + return super().prepare() def write_error(self, status_code, **kwargs): """APIHandler errors are JSON, not human pages""" @@ -618,7 +622,7 @@ # preserve _user_cache so we don't raise more than once if hasattr(self, '_user_cache'): return self._user_cache - self._user_cache = user = super(APIHandler, self).get_current_user() + self._user_cache = user = super().get_current_user() return user def get_login_url(self): @@ -627,12 +631,12 @@ # instead of redirecting, raise 403 instead. if not self.current_user: raise web.HTTPError(403) - return super(APIHandler, self).get_login_url() + return super().get_login_url() @property def content_security_policy(self): csp = '; '.join([ - super(APIHandler, self).content_security_policy, + super().content_security_policy, "default-src 'none'", ]) return csp @@ -653,7 +657,7 @@ def finish(self, *args, **kwargs): self.update_api_activity() self.set_header('Content-Type', 'application/json') - return super(APIHandler, self).finish(*args, **kwargs) + return super().finish(*args, **kwargs) def options(self, *args, **kwargs): if 'Access-Control-Allow-Headers' in self.settings.get('headers', {}): @@ -700,13 +704,12 @@ def content_security_policy(self): # In case we're serving HTML/SVG, confine any Javascript to a unique # origin so it can't interact with the notebook server. - return super(AuthenticatedFileHandler, self).content_security_policy + \ - "; sandbox allow-scripts" + return super().content_security_policy + "; sandbox allow-scripts" @web.authenticated def head(self, path): self.check_xsrf_cookie() - return super(AuthenticatedFileHandler, self).head(path) + return super().head(path) @web.authenticated def get(self, path): @@ -731,10 +734,10 @@ if cur_mime == 'text/plain': return 'text/plain; charset=UTF-8' else: - return super(AuthenticatedFileHandler, self).get_content_type() + return super().get_content_type() def set_headers(self): - super(AuthenticatedFileHandler, self).set_headers() + super().set_headers() # disable browser caching, rely on 304 replies for savings if "v" not in self.request.arguments: self.add_header("Cache-Control", "no-cache") @@ -749,7 +752,7 @@ Adding to tornado's own handling, forbids the serving of hidden files. """ - abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path) + abs_path = super().validate_absolute_path(root, absolute_path) abs_root = os.path.abspath(root) if is_hidden(abs_path, abs_root) and not self.contents_manager.allow_hidden: self.log.info("Refusing to serve hidden file, via 404 Error, use flag 'ContentsManager.allow_hidden' to enable") @@ -795,7 +798,7 @@ _static_paths = {} def set_headers(self): - super(FileFindHandler, self).set_headers() + super().set_headers() # disable browser caching, rely on 304 replies for savings if "v" not in self.request.arguments or \ any(self.request.path.startswith(path) for path in self.no_cache_paths): @@ -842,7 +845,7 @@ if (absolute_path + os.sep).startswith(root): break - return super(FileFindHandler, self).validate_absolute_path(root, absolute_path) + return super().validate_absolute_path(root, absolute_path) class APIVersionHandler(APIHandler): @@ -854,13 +857,18 @@ class TrailingSlashHandler(web.RequestHandler): """Simple redirect handler that strips trailing slashes - + This should be the first, highest priority handler. """ - + def get(self): - self.redirect(self.request.uri.rstrip('/')) - + path, *rest = self.request.uri.partition("?") + # trim trailing *and* leading / + # to avoid misinterpreting repeated '//' + path = "/" + path.strip("/") + new_uri = "".join([path, *rest]) + self.redirect(new_uri) + post = put = get @@ -911,12 +919,15 @@ url = sep.join([self._url, self.request.query]) self.redirect(url, permanent=self._permanent) + class PrometheusMetricsHandler(IPythonHandler): """ Return prometheus metrics for this notebook server """ - @web.authenticated def get(self): + if self.settings['authenticate_prometheus'] and not self.logged_in: + raise web.HTTPError(403) + self.set_header('Content-Type', prometheus_client.CONTENT_TYPE_LATEST) self.write(prometheus_client.generate_latest(prometheus_client.REGISTRY)) diff -Nru jupyter-notebook-6.1.4/notebook/base/zmqhandlers.py jupyter-notebook-6.2.0/notebook/base/zmqhandlers.py --- jupyter-notebook-6.1.4/notebook/base/zmqhandlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/base/zmqhandlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Tornado handlers for WebSocket <-> ZMQ sockets.""" # Copyright (c) Jupyter Development Team. @@ -41,8 +40,6 @@ # don't modify msg or buffer list in-place msg = msg.copy() buffers = list(msg.pop('buffers')) - if sys.version_info < (3, 4): - buffers = [x.tobytes() for x in buffers] bmsg = json.dumps(msg, default=date_default).encode('utf8') buffers.insert(0, bmsg) nbufs = len(buffers) @@ -289,7 +286,7 @@ # assign and yield in two step to avoid tornado 3 issues res = self.pre_get() yield maybe_future(res) - res = super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs) + res = super().get(*args, **kwargs) yield maybe_future(res) def initialize(self): diff -Nru jupyter-notebook-6.1.4/notebook/bundler/bundlerextensions.py jupyter-notebook-6.2.0/notebook/bundler/bundlerextensions.py --- jupyter-notebook-6.1.4/notebook/bundler/bundlerextensions.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/bundler/bundlerextensions.py 2021-01-13 16:30:49.000000000 +0000 @@ -294,7 +294,7 @@ def start(self): """Perform the App's functions as configured""" - super(BundlerExtensionApp, self).start() + super().start() # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. diff -Nru jupyter-notebook-6.1.4/notebook/bundler/tests/test_bundler_api.py jupyter-notebook-6.2.0/notebook/bundler/tests/test_bundler_api.py --- jupyter-notebook-6.1.4/notebook/bundler/tests/test_bundler_api.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/bundler/tests/test_bundler_api.py 2021-01-13 16:30:49.000000000 +0000 @@ -46,7 +46,7 @@ self.assertIn('Missing argument bundler', resp.text) def test_notebook_not_found(self): - """Shoudl respond with 404 error about missing notebook""" + """Should respond with 404 error about missing notebook""" resp = self.request('GET', 'bundle/fake.ipynb', params={'bundler': 'fake_bundler'}) self.assertEqual(resp.status_code, 404) diff -Nru jupyter-notebook-6.1.4/notebook/config_manager.py jupyter-notebook-6.2.0/notebook/config_manager.py --- jupyter-notebook-6.1.4/notebook/config_manager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/config_manager.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Manager to read and modify config data in JSON files.""" # Copyright (c) Jupyter Development Team. diff -Nru jupyter-notebook-6.1.4/notebook/edit/handlers.py jupyter-notebook-6.2.0/notebook/edit/handlers.py --- jupyter-notebook-6.1.4/notebook/edit/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/edit/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -#encoding: utf-8 """Tornado handlers for the terminal emulator.""" # Copyright (c) Jupyter Development Team. diff -Nru jupyter-notebook-6.1.4/notebook/extensions.py jupyter-notebook-6.2.0/notebook/extensions.py --- jupyter-notebook-6.1.4/notebook/extensions.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/extensions.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Utilities for installing extensions""" # Copyright (c) Jupyter Development Team. diff -Nru jupyter-notebook-6.1.4/notebook/files/handlers.py jupyter-notebook-6.2.0/notebook/files/handlers.py --- jupyter-notebook-6.1.4/notebook/files/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/files/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -26,8 +26,7 @@ def content_security_policy(self): # In case we're serving HTML/SVG, confine any Javascript to a unique # origin so it can't interact with the notebook server. - return super(FilesHandler, self).content_security_policy + \ - "; sandbox allow-scripts" + return super().content_security_policy + "; sandbox allow-scripts" @web.authenticated def head(self, path): diff -Nru jupyter-notebook-6.1.4/notebook/gateway/handlers.py jupyter-notebook-6.2.0/notebook/gateway/handlers.py --- jupyter-notebook-6.1.4/notebook/gateway/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/gateway/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -4,6 +4,7 @@ import os import logging import mimetypes +import random from ..base.handlers import APIHandler, IPythonHandler from ..utils import url_path_join @@ -68,7 +69,7 @@ def get(self, kernel_id, *args, **kwargs): self.authenticate() self.kernel_id = cast_unicode(kernel_id, 'ascii') - yield super(WebSocketChannelsHandler, self).get(kernel_id=kernel_id, *args, **kwargs) + yield super().get(kernel_id=kernel_id, *args, **kwargs) def send_ping(self): if self.ws_connection is None and self.ping_callback is not None: @@ -97,7 +98,7 @@ if self.ws_connection: # prevent WebSocketClosedError if isinstance(message, bytes): binary = True - super(WebSocketChannelsHandler, self).write_message(message, binary=binary) + super().write_message(message, binary=binary) elif self.log.isEnabledFor(logging.DEBUG): msg_summary = WebSocketChannelsHandler._get_message_summary(json_decode(utf8(message))) self.log.debug("Notebook client closed websocket connection - message dropped: {}".format(msg_summary)) @@ -105,7 +106,7 @@ def on_close(self): self.log.debug("Closing websocket connection %s", self.request.path) self.gateway.on_close() - super(WebSocketChannelsHandler, self).on_close() + super().on_close() @staticmethod def _get_message_summary(message): @@ -129,11 +130,12 @@ """Proxy web socket connection to a kernel/enterprise gateway.""" def __init__(self, **kwargs): - super(GatewayWebSocketClient, self).__init__(**kwargs) + super().__init__(**kwargs) self.kernel_id = None self.ws = None self.ws_future = Future() self.disconnected = False + self.retry = 0 @gen.coroutine def _connect(self, kernel_id): @@ -155,6 +157,7 @@ def _connection_done(self, fut): if not self.disconnected and fut.exception() is None: # prevent concurrent.futures._base.CancelledError self.ws = fut.result() + self.retry = 0 self.log.debug("Connection is ready: ws: {}".format(self.ws)) else: self.log.warning("Websocket connection has been closed via client disconnect or due to error. " @@ -189,8 +192,15 @@ else: # ws cancelled - stop reading break - if not self.disconnected: # if websocket is not disconnected by client, attept to reconnect to Gateway - self.log.info("Attempting to re-establish the connection to Gateway: {}".format(self.kernel_id)) + # NOTE(esevan): if websocket is not disconnected by client, try to reconnect. + if not self.disconnected and self.retry < GatewayClient.instance().gateway_retry_max: + jitter = random.randint(10, 100) * 0.01 + retry_interval = min(GatewayClient.instance().gateway_retry_interval * (2 ** self.retry), + GatewayClient.instance().gateway_retry_interval_max) + jitter + self.retry += 1 + self.log.info("Attempting to re-establish the connection to Gateway in %s secs (%s/%s): %s", + retry_interval, self.retry, GatewayClient.instance().gateway_retry_max, self.kernel_id) + yield gen.sleep(retry_interval) self._connect(self.kernel_id) loop = IOLoop.current() loop.add_future(self.ws_future, lambda future: self._read_messages(callback)) diff -Nru jupyter-notebook-6.1.4/notebook/gateway/managers.py jupyter-notebook-6.2.0/notebook/gateway/managers.py --- jupyter-notebook-6.1.4/notebook/gateway/managers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/gateway/managers.py 2021-01-13 16:30:49.000000000 +0000 @@ -15,14 +15,14 @@ from jupyter_client.kernelspec import KernelSpecManager from ..utils import url_path_join -from traitlets import Instance, Unicode, Float, Bool, default, validate, TraitError +from traitlets import Instance, Unicode, Int, Float, Bool, default, validate, TraitError from traitlets.config import SingletonConfigurable class GatewayClient(SingletonConfigurable): """This class manages the configuration. It's its own singleton class so that we can share these values across all objects. It also contains some helper methods - to build request arguments out of the various config options. + to build request arguments out of the various config options. """ @@ -205,7 +205,7 @@ return bool(os.environ.get(self.validate_cert_env, str(self.validate_cert_default_value)) not in ['no', 'false']) def __init__(self, **kwargs): - super(GatewayClient, self).__init__(**kwargs) + super().__init__(**kwargs) self._static_args = {} # initialized on first use env_whitelist_default_value = '' @@ -220,6 +220,38 @@ def _env_whitelist_default(self): return os.environ.get(self.env_whitelist_env, self.env_whitelist_default_value) + gateway_retry_interval_default_value = 1.0 + gateway_retry_interval_env = 'JUPYTER_GATEWAY_RETRY_INTERVAL' + gateway_retry_interval = Float(default_value=gateway_retry_interval_default_value, config=True, + help="""The time allowed for HTTP reconnection with the Gateway server for the first time. + Next will be JUPYTER_GATEWAY_RETRY_INTERVAL multiplied by two in factor of numbers of retries + but less than JUPYTER_GATEWAY_RETRY_INTERVAL_MAX. + (JUPYTER_GATEWAY_RETRY_INTERVAL env var)""") + + @default('gateway_retry_interval') + def gateway_retry_interval_default(self): + return float(os.environ.get('JUPYTER_GATEWAY_RETRY_INTERVAL', self.gateway_retry_interval_default_value)) + + gateway_retry_interval_max_default_value = 30.0 + gateway_retry_interval_max_env = 'JUPYTER_GATEWAY_RETRY_INTERVAL_MAX' + gateway_retry_interval_max = Float(default_value=gateway_retry_interval_max_default_value, config=True, + help="""The maximum time allowed for HTTP reconnection retry with the Gateway server. + (JUPYTER_GATEWAY_RETRY_INTERVAL_MAX env var)""") + + @default('gateway_retry_interval_max') + def gateway_retry_interval_max_default(self): + return float(os.environ.get('JUPYTER_GATEWAY_RETRY_INTERVAL_MAX', self.gateway_retry_interval_max_default_value)) + + gateway_retry_max_default_value = 5 + gateway_retry_max_env = 'JUPYTER_GATEWAY_RETRY_MAX' + gateway_retry_max = Int(default_value=gateway_retry_max_default_value, config=True, + help="""The maximum retries allowed for HTTP reconnection with the Gateway server. + (JUPYTER_GATEWAY_RETRY_MAX env var)""") + + @default('gateway_retry_max') + def gateway_retry_max_default(self): + return int(os.environ.get('JUPYTER_GATEWAY_RETRY_MAX', self.gateway_retry_max_default_value)) + @property def gateway_enabled(self): return bool(self.url is not None and len(self.url) > 0) @@ -310,7 +342,7 @@ _kernels = {} def __init__(self, **kwargs): - super(GatewayKernelManager, self).__init__(**kwargs) + super().__init__(**kwargs) self.base_endpoint = url_path_join(GatewayClient.instance().url, GatewayClient.instance().kernels_endpoint) def __contains__(self, kernel_id): @@ -503,11 +535,10 @@ self.remove_kernel(kernel_id) - class GatewayKernelSpecManager(KernelSpecManager): def __init__(self, **kwargs): - super(GatewayKernelSpecManager, self).__init__(**kwargs) + super().__init__(**kwargs) base_endpoint = url_path_join(GatewayClient.instance().url, GatewayClient.instance().kernelspecs_endpoint) diff -Nru jupyter-notebook-6.1.4/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po jupyter-notebook-6.2.0/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po --- jupyter-notebook-6.1.4/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po 2021-01-13 16:30:49.000000000 +0000 @@ -19,15 +19,15 @@ #: notebook/static/base/js/dialog.js:161 msgid "Manually edit the JSON below to manipulate the metadata for this cell." -msgstr "手动编辑下面的JSON代码来修改块元数据." +msgstr "手动编辑下面的 JSON 代码来修改块元数据。" #: notebook/static/base/js/dialog.js:163 msgid "Manually edit the JSON below to manipulate the metadata for this notebook." -msgstr "手动编辑下面的JSON代码来修改界面元数据." +msgstr "手动编辑下面的 JSON 代码来修改笔记本元数据。" #: notebook/static/base/js/dialog.js:165 msgid " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others." -msgstr "我们建议将自定义的元数据属性放入适当的子结构中,这样就不会与其他的子结构发生冲突." +msgstr "我们建议将自定义的元数据属性放入适当的子结构中,这样就不会与其他的子结构发生冲突。" #: notebook/static/base/js/dialog.js:180 msgid "Edit the metadata" @@ -35,7 +35,7 @@ #: notebook/static/base/js/dialog.js:202 msgid "Edit Notebook Metadata" -msgstr "编辑界面元数据" +msgstr "编辑笔记本元数据" #: notebook/static/base/js/dialog.js:204 msgid "Edit Cell Metadata" @@ -70,11 +70,11 @@ #: notebook/static/base/js/dialog.js:225 msgid "WARNING: Could not save invalid JSON." -msgstr "警告: 不能保存无效的JSON." +msgstr "警告: 不能保存无效的JSON。" #: notebook/static/base/js/dialog.js:247 msgid "There are no attachments for this cell." -msgstr "这个块没有附件." +msgstr "这个块没有附件。" #: notebook/static/base/js/dialog.js:250 msgid "Current cell attachments" @@ -100,7 +100,7 @@ #: notebook/static/base/js/dialog.js:348 msgid "Edit Notebook Attachments" -msgstr "编辑代码附件" +msgstr "编辑笔记本附件" #: notebook/static/base/js/dialog.js:350 msgid "Edit Cell Attachments" @@ -116,19 +116,19 @@ #: notebook/static/notebook/js/about.js:14 msgid "You are using Jupyter notebook." -msgstr "你正在运行notebook." +msgstr "您正在使用 Jupyter Notebook。" #: notebook/static/notebook/js/about.js:16 msgid "The version of the notebook server is: " -msgstr "该notebook 服务的版本是:" +msgstr "该 notebook 服务的版本是:" #: notebook/static/notebook/js/about.js:22 msgid "The server is running on this version of Python:" -msgstr "该服务运行中使用的python版本为:" +msgstr "该服务运行中使用的 Python 版本为:" #: notebook/static/notebook/js/about.js:25 msgid "Waiting for kernel to be available..." -msgstr "等待服务可用..." +msgstr "等待内核可用..." #: notebook/static/notebook/js/about.js:27 msgid "Server Information:" @@ -136,15 +136,15 @@ #: notebook/static/notebook/js/about.js:29 msgid "Current Kernel Information:" -msgstr "当前服务信息:" +msgstr "当前内核信息:" #: notebook/static/notebook/js/about.js:32 msgid "Could not access sys_info variable for version information." -msgstr "无法为版本信息访问sysinfo变量." +msgstr "无法访问 sys_info 变量来获取版本信息。" #: notebook/static/notebook/js/about.js:34 msgid "Cannot find sys_info!" -msgstr "找不到sys_info!" +msgstr "找不到 sys_info!" #: notebook/static/notebook/js/about.js:38 msgid "About Jupyter Notebook" @@ -152,19 +152,19 @@ #: notebook/static/notebook/js/about.js:47 msgid "unable to contact kernel" -msgstr "不能连接到服务" +msgstr "不能连接到内核" #: notebook/static/notebook/js/actions.js:69 msgid "toggle rtl layout" -msgstr "切换trl布局" +msgstr "切换 RTL 布局" #: notebook/static/notebook/js/actions.js:70 msgid "Toggle the screen directionality between left-to-right and right-to-left" -msgstr "切换左右至右至左之间的屏幕方向" +msgstr "切换左至右或右至左的屏幕方向" #: notebook/static/notebook/js/actions.js:76 msgid "edit command mode keyboard shortcuts" -msgstr "编辑命令模式快捷键" +msgstr "编辑命令模式键盘快捷键" #: notebook/static/notebook/js/actions.js:77 msgid "Open a dialog to edit the command mode keyboard shortcuts" @@ -172,56 +172,56 @@ #: notebook/static/notebook/js/actions.js:97 msgid "restart kernel" -msgstr "重启服务" +msgstr "重启内核" #: notebook/static/notebook/js/actions.js:98 msgid "restart the kernel (no confirmation dialog)" -msgstr "重启服务(没有确认窗口)" +msgstr "重启内核(无确认对话框)" #: notebook/static/notebook/js/actions.js:106 msgid "confirm restart kernel" -msgstr "确定重启服务" +msgstr "确定重启内核" #: notebook/static/notebook/js/actions.js:107 msgid "restart the kernel (with dialog)" -msgstr "重启服务(带窗口)" +msgstr "重启内核(带确认对话框)" #: notebook/static/notebook/js/actions.js:113 msgid "restart kernel and run all cells" -msgstr "重启服务并且运行所有代码块" +msgstr "重启内核并且运行所有代码块" #: notebook/static/notebook/js/actions.js:114 msgid "restart the kernel, then re-run the whole notebook (no confirmation dialog)" -msgstr "重启服务, 然后重新运行整个代码(不含确认窗口)" +msgstr "重启服务,然后重新运行整个笔记本(无确认对话框)" #: notebook/static/notebook/js/actions.js:120 msgid "confirm restart kernel and run all cells" -msgstr "确认重启服务并且运行所有代码块" +msgstr "确认重启内核并且运行所有代码块" #: notebook/static/notebook/js/actions.js:121 msgid "restart the kernel, then re-run the whole notebook (with dialog)" -msgstr "重启服务, 然后重新运行整个代码(含窗口)" +msgstr "重启内核, 然后重新运行整个代码(带确认对话框)" #: notebook/static/notebook/js/actions.js:127 msgid "restart kernel and clear output" -msgstr "重启服务并且清空输入" +msgstr "重启内核并且清空输出" #: notebook/static/notebook/js/actions.js:128 msgid "restart the kernel and clear all output (no confirmation dialog)" -msgstr "重启服务并且清空所有输出(不含确认窗口)" +msgstr "重启内核并且清空所有输出(无确认对话框)" #: notebook/static/notebook/js/actions.js:134 msgid "confirm restart kernel and clear output" -msgstr "确认重启服务并且清空输出" +msgstr "确认重启内核并且清空输出" #: notebook/static/notebook/js/actions.js:135 msgid "restart the kernel and clear all output (with dialog)" -msgstr "重启服务并且清空所有输出(含窗口)" +msgstr "重启内核并且清空所有输出(带确认对话框)" #: notebook/static/notebook/js/actions.js:142 #: notebook/static/notebook/js/actions.js:143 msgid "interrupt the kernel" -msgstr "中断服务" +msgstr "中断内核" #: notebook/static/notebook/js/actions.js:150 msgid "run cell and select next" @@ -239,7 +239,7 @@ #: notebook/static/notebook/js/actions.js:167 #: notebook/static/notebook/js/actions.js:168 msgid "run cell and insert below" -msgstr "运行代码块并且插入下面" +msgstr "运行代码块并且在下面插入代码块" #: notebook/static/notebook/js/actions.js:175 #: notebook/static/notebook/js/actions.js:176 @@ -269,22 +269,22 @@ #: notebook/static/notebook/js/actions.js:213 #: notebook/static/notebook/js/actions.js:214 msgid "cut cell attachments" -msgstr "剪切代码块" +msgstr "剪切代码块的附件" #: notebook/static/notebook/js/actions.js:221 #: notebook/static/notebook/js/actions.js:222 msgid "copy cell attachments" -msgstr "复制代码块" +msgstr "复制代码块的附件" #: notebook/static/notebook/js/actions.js:229 #: notebook/static/notebook/js/actions.js:230 msgid "paste cell attachments" -msgstr "粘贴代码块" +msgstr "粘贴代码块的附件" #: notebook/static/notebook/js/actions.js:237 #: notebook/static/notebook/js/actions.js:238 msgid "split cell at cursor" -msgstr "在鼠标处分割代码块" +msgstr "在光标处分割代码块" #: notebook/static/notebook/js/actions.js:245 #: notebook/static/notebook/js/actions.js:246 @@ -361,7 +361,7 @@ #: notebook/static/notebook/js/actions.js:373 #: notebook/static/notebook/js/actions.js:374 msgid "change cell to markdown" -msgstr "把代码块变成标签" +msgstr "把代码块变成 Markdown" #: notebook/static/notebook/js/actions.js:381 #: notebook/static/notebook/js/actions.js:382 @@ -371,48 +371,48 @@ #: notebook/static/notebook/js/actions.js:389 #: notebook/static/notebook/js/actions.js:390 msgid "change cell to heading 1" -msgstr "把代码块变成heading 1" +msgstr "把代码块变成标题 1" #: notebook/static/notebook/js/actions.js:397 #: notebook/static/notebook/js/actions.js:398 msgid "change cell to heading 2" -msgstr "把代码块变成heading 2" +msgstr "把代码块变成标题 2" #: notebook/static/notebook/js/actions.js:405 #: notebook/static/notebook/js/actions.js:406 msgid "change cell to heading 3" -msgstr "把代码块变成heading 3" +msgstr "把代码块变成标题 3" #: notebook/static/notebook/js/actions.js:413 #: notebook/static/notebook/js/actions.js:414 msgid "change cell to heading 4" -msgstr "把代码块变成heading 4" +msgstr "把代码块变成标题 4" #: notebook/static/notebook/js/actions.js:421 #: notebook/static/notebook/js/actions.js:422 msgid "change cell to heading 5" -msgstr "把代码块变成heading 5" +msgstr "把代码块变成标题 5" #: notebook/static/notebook/js/actions.js:429 #: notebook/static/notebook/js/actions.js:430 msgid "change cell to heading 6" -msgstr "把代码块变成heading 6" +msgstr "把代码块变成标题 6" #: notebook/static/notebook/js/actions.js:437 msgid "toggle cell output" -msgstr "切换单元输出" +msgstr "切换代码块输出" #: notebook/static/notebook/js/actions.js:438 msgid "toggle output of selected cells" -msgstr "选择单元格的输出" +msgstr "切换选定单元格的输出" #: notebook/static/notebook/js/actions.js:445 msgid "toggle cell scrolling" -msgstr "切换单元滚动" +msgstr "切换单元格滚动" #: notebook/static/notebook/js/actions.js:446 msgid "toggle output scrolling of selected cells" -msgstr "切换选定单元的输出滚动" +msgstr "切换选中单元格的输出滚动" #: notebook/static/notebook/js/actions.js:453 msgid "clear cell output" @@ -446,7 +446,7 @@ #: notebook/static/notebook/js/actions.js:486 #: notebook/static/notebook/js/actions.js:487 msgid "show keyboard shortcuts" -msgstr "显示快捷键" +msgstr "显示键盘快捷键" #: notebook/static/notebook/js/actions.js:494 msgid "delete cells" @@ -528,7 +528,7 @@ #: notebook/static/notebook/js/actions.js:590 msgid "switch between showing and hiding the header" -msgstr "显示和隐藏标题之间的切换" +msgstr "切换显示和隐藏标题" #: notebook/static/notebook/js/actions.js:605 #: notebook/static/notebook/js/actions.js:606 @@ -546,7 +546,7 @@ #: notebook/static/notebook/js/actions.js:647 msgid "switch between showing and hiding the toolbar" -msgstr "选择显示/隐藏工具栏" +msgstr "切换显示/隐藏工具栏" #: notebook/static/notebook/js/actions.js:660 #: notebook/static/notebook/js/actions.js:661 @@ -561,7 +561,7 @@ #: notebook/static/notebook/js/actions.js:678 #: notebook/static/notebook/js/actions.js:679 msgid "close the pager" -msgstr "关闭页面" +msgstr "关闭分页器" #: notebook/static/notebook/js/actions.js:704 msgid "ignore" @@ -589,7 +589,7 @@ #: notebook/static/notebook/js/actions.js:770 msgid "scroll cell center" -msgstr "滚动到单元格中间" +msgstr "滚动单元格到中间" #: notebook/static/notebook/js/actions.js:771 msgid "Scroll the current cell to the center" @@ -597,7 +597,7 @@ #: notebook/static/notebook/js/actions.js:781 msgid "scroll cell top" -msgstr "滚动到单元格开始处" +msgstr "滚动单元格到顶" #: notebook/static/notebook/js/actions.js:782 msgid "Scroll the current cell to the top" @@ -605,31 +605,31 @@ #: notebook/static/notebook/js/actions.js:792 msgid "duplicate notebook" -msgstr "复制代码" +msgstr "制作笔记本副本" #: notebook/static/notebook/js/actions.js:793 msgid "Create and open a copy of the current notebook" -msgstr "创建并打开当前代码的一个副本" +msgstr "创建并打开当前笔记本的一个副本" #: notebook/static/notebook/js/actions.js:799 msgid "trust notebook" -msgstr "信任代码" +msgstr "信任笔记本" #: notebook/static/notebook/js/actions.js:800 msgid "Trust the current notebook" -msgstr "信任当前代码" +msgstr "信任当前笔记本" #: notebook/static/notebook/js/actions.js:806 msgid "rename notebook" -msgstr "重命名" +msgstr "重命名笔记本" #: notebook/static/notebook/js/actions.js:807 msgid "Rename the current notebook" -msgstr "重命名" +msgstr "重命名当前笔记本" #: notebook/static/notebook/js/actions.js:813 msgid "toggle all cells output collapsed" -msgstr "切换所有单元格的输出" +msgstr "切换折叠所有单元格的输出" #: notebook/static/notebook/js/actions.js:814 msgid "Toggle the hidden state of all output areas" @@ -637,7 +637,7 @@ #: notebook/static/notebook/js/actions.js:820 msgid "toggle all cells output scrolled" -msgstr "切换所有单元格的输出" +msgstr "切换所有单元格输出的滚动状态" #: notebook/static/notebook/js/actions.js:821 msgid "Toggle the scrolling state of all output areas" @@ -653,15 +653,15 @@ #: notebook/static/notebook/js/actions.js:835 msgid "save notebook" -msgstr "保存代码" +msgstr "保存笔记本" #: notebook/static/notebook/js/actions.js:836 msgid "Save and Checkpoint" -msgstr "保存并检查" +msgstr "保存并建立检查点" #: notebook/static/notebook/js/cell.js:79 msgid "Warning: accessing Cell.cm_config directly is deprecated." -msgstr "警告: 访问Cell.cm_config已经被弃用了." +msgstr "警告: 直接访问 Cell.cm_config 已经被弃用了。" #: notebook/static/notebook/js/cell.js:763 #, python-format @@ -697,11 +697,11 @@ #: notebook/static/notebook/js/clipboard.js:113 #, python-format msgid "Press %s again to paste" -msgstr "再按一次 %s 粘贴" +msgstr "再按一次 %s 来粘贴" #: notebook/static/notebook/js/clipboard.js:116 msgid "Why is this needed? " -msgstr "这个为啥需要?" +msgstr "为什么需要它?" #: notebook/static/notebook/js/clipboard.js:118 msgid "We can't get paste events in this browser without a text box. " @@ -718,7 +718,7 @@ #: notebook/static/notebook/js/codecell.js:310 msgid "Can't execute cell since kernel is not set." -msgstr "只要服务没有设置就不能执行单元格代码." +msgstr "当前不能执行单元格代码,因为内核还没有准备好。" #: notebook/static/notebook/js/codecell.js:481 msgid "In" @@ -727,30 +727,30 @@ #: notebook/static/notebook/js/kernelselector.js:269 #, python-format msgid "Could not find a kernel matching %s. Please select a kernel:" -msgstr "找不到服务匹配 %s. 请选择一个服务:" +msgstr "找不到匹配 %s 的内核。请选择一个内核:" #: notebook/static/notebook/js/kernelselector.js:278 msgid "Continue Without Kernel" -msgstr "继续运行没有服务" +msgstr "无内核继续运行" #: notebook/static/notebook/js/kernelselector.js:278 msgid "Set Kernel" -msgstr "设置服务" +msgstr "设置内核" #: notebook/static/notebook/js/kernelselector.js:281 msgid "Kernel not found" -msgstr "服务没找到" +msgstr "找不到内核" #: notebook/static/notebook/js/kernelselector.js:319 #: notebook/static/tree/js/newnotebook.js:99 msgid "Creating Notebook Failed" -msgstr "创建代码失败" +msgstr "创建笔记本失败" #: notebook/static/notebook/js/kernelselector.js:320 #: notebook/static/tree/js/notebooklist.js:1362 #, python-format msgid "The error was: %s" -msgstr "错误; %s" +msgstr "错误: %s" #: notebook/static/notebook/js/maintoolbar.js:54 msgid "Run" @@ -774,29 +774,29 @@ #: notebook/static/notebook/js/maintoolbar.js:115 msgid "unrecognized cell type:" -msgstr "未识别的单元格类型:" +msgstr "未识别的单元格类型:" #: notebook/static/notebook/js/mathjaxutils.js:45 #, python-format msgid "Failed to retrieve MathJax from '%s'" -msgstr "从 '%s' 中未能检索 MathJax" +msgstr "未能从 '%s' 中检索 MathJax" #: notebook/static/notebook/js/mathjaxutils.js:47 msgid "Math/LaTeX rendering will be disabled." -msgstr "Math/LaTeX 渲染 将会失效." +msgstr "Math/LaTeX 渲染将被禁用。" #: notebook/static/notebook/js/menubar.js:220 msgid "Trusted Notebook" -msgstr "可信的笔记" +msgstr "可信的笔记本" #: notebook/static/notebook/js/menubar.js:226 msgid "Trust Notebook" -msgstr "信任笔记" +msgstr "信任笔记本" #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:16 #: notebook/static/notebook/js/menubar.js:383 msgid "None" -msgstr "" +msgstr "无" #: notebook/static/notebook/js/menubar.js:406 msgid "No checkpoints" @@ -808,23 +808,23 @@ #: notebook/static/notebook/js/notebook.js:441 msgid "Autosave in progress, latest changes may be lost." -msgstr "自动保存, 最新的改变有可能被丢弃." +msgstr "自动保存进行中,最新的改变可能会丢失。" #: notebook/static/notebook/js/notebook.js:443 msgid "Unsaved changes will be lost." -msgstr "未保存的修改将会丢失." +msgstr "未保存的修改将会丢失。" #: notebook/static/notebook/js/notebook.js:448 msgid "The Kernel is busy, outputs may be lost." -msgstr "服务正忙, 输出也许会丢失." +msgstr "内核正忙,输出也许会丢失。" #: notebook/static/notebook/js/notebook.js:471 msgid "This notebook is version %1$s, but we only fully support up to %2$s." -msgstr "本程序版本 %1$s, 但是我们只是支持到 %2$s." +msgstr "该笔记本使用了版本 %1$s,但是我们只支持到 %2$s." #: notebook/static/notebook/js/notebook.js:473 msgid "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available." -msgstr "您仍然可以使用本程序, 但是在以后的版本中引入的单元和输出类型将不可用." +msgstr "您仍然可以使用该笔记本,但是在新版本中引入的单元和输出类型将不可用。" #: notebook/static/notebook/js/notebook.js:480 msgid "Restart and Run All Cells" @@ -860,72 +860,72 @@ #: notebook/static/notebook/js/notebook.js:493 msgid "Newer Notebook" -msgstr "新笔记" +msgstr "新笔记本" #: notebook/static/notebook/js/notebook.js:1558 msgid "Use markdown headings" -msgstr "使用标签 headings" +msgstr "使用 Markdown 标题" #: notebook/static/notebook/js/notebook.js:1560 msgid "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:" -msgstr "Jupyter不再使用特殊的标题单元格. 相反, 在Markdown单元中使用字符来写标题:" +msgstr "Jupyter 不再使用特殊的标题单元格。请在 Markdown 单元格中使用 # 字符来写标题:" #: notebook/static/notebook/js/notebook.js:1563 msgid "## This is a level 2 heading" -msgstr "## 这是一个 2 heading" +msgstr "## 这是一个二级标题" #: notebook/static/notebook/js/notebook.js:2261 msgid "Restart kernel and re-run the whole notebook?" -msgstr "重新启动内核并重新运行整个笔记本?" +msgstr "重新启动内核并重新运行整个笔记本?" #: notebook/static/notebook/js/notebook.js:2263 msgid "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost." -msgstr "您确定要重新启动当前的内核并重新执行整个笔记本吗? 所有的变量和输出都将丢失." +msgstr "您确定要重新启动当前的内核并重新执行整个笔记本吗?所有的变量和输出都将丢失。" #: notebook/static/notebook/js/notebook.js:2288 msgid "Restart kernel and clear all output?" -msgstr "重启服务并且清空输出?" +msgstr "重启内核并且清空输出?" #: notebook/static/notebook/js/notebook.js:2290 msgid "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost." -msgstr "您是否希望重新启动当前的内核并清除所有输出? 所有的变量和输出都将丢失." +msgstr "您是否希望重新启动当前的内核并清除所有输出?所有的变量和输出都将丢失。" #: notebook/static/notebook/js/notebook.js:2335 msgid "Restart kernel?" -msgstr "重启服务?" +msgstr "重启内核?" #: notebook/static/notebook/js/notebook.js:2337 msgid "Do you want to restart the current kernel? All variables will be lost." -msgstr "如果重启服务, 所有变量都会丢弃." +msgstr "如果重启内核,所有变量都会丢失。是否重启?" #: notebook/static/notebook/js/notebook.js:2320 msgid "Shutdown kernel?" -msgstr "关闭服务" +msgstr "关闭内核?" #: notebook/static/notebook/js/notebook.js:2322 msgid "Do you want to shutdown the current kernel? All variables will be lost." -msgstr "如果关闭服务,所有变量都会丢失" +msgstr "如果关闭内核,所有变量都会丢失。是否关闭?" #: notebook/static/notebook/js/notebook.js:2750 msgid "Notebook changed" -msgstr "笔记改变了" +msgstr "笔记本改变了" #: notebook/static/notebook/js/notebook.js:2751 msgid "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page)?" -msgstr "自从上次我们打开或保存它以来, 笔记本文件已经在磁盘上发生了变化. 您是否希望在磁盘上使用打开的版本, 或在磁盘上的版本(重新加载页面)上覆盖该文件?" +msgstr "自从上次我们打开或保存它以来,笔记本文件已经在磁盘上发生了变化。您希望用这里打开的版本覆盖磁盘上的版本,还是加载磁盘上的版本(刷新页面)?" #: notebook/static/notebook/js/notebook.js:2798 #: notebook/static/notebook/js/notebook.js:3020 msgid "Notebook validation failed" -msgstr "Notebook 失效" +msgstr "Notebook 校验失败" #: notebook/static/notebook/js/notebook.js:2801 msgid "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:" -msgstr "保存操作成功了, 但是这个笔记本看起来并不有效. 验证错误:" +msgstr "保存操作成功了,但是这个笔记本看起来并不有效。校验错误:" #: notebook/static/notebook/js/notebook.js:2852 msgid "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: " -msgstr "当你打开它时, 一个可信任的Jupyter笔记本可能会执行隐藏的恶意代码. 选择信任将立即在一个可信的状态中重新加载这个笔记本. 要了解更多信息, 请参阅Jupyter安全文档:" +msgstr "当你打开一个可信任的 Jupyter 笔记本时,它可能会执行隐藏的恶意代码。选择信任将立即在一个可信的状态中重新加载这个笔记本。要了解更多信息,请参阅 Jupyter 安全文档:" #: notebook/static/notebook/js/notebook.js:2856 msgid "here" @@ -933,11 +933,11 @@ #: notebook/static/notebook/js/notebook.js:2864 msgid "Trust this notebook?" -msgstr "信任这个代码?" +msgstr "信任这个笔记本?" #: notebook/static/notebook/js/notebook.js:3011 msgid "Notebook failed to load" -msgstr "代码加载失败" +msgstr "笔记本加载失败" #: notebook/static/notebook/js/notebook.js:3013 msgid "The error was: " @@ -945,74 +945,74 @@ #: notebook/static/notebook/js/notebook.js:3017 msgid "See the error console for details." -msgstr "有关详细信息, 请参阅错误控制台." +msgstr "有关详细信息,请参阅错误控制台。" #: notebook/static/notebook/js/notebook.js:3025 msgid "The notebook also failed validation:" -msgstr "代码失败了:" +msgstr "这个笔记本校验也失败了:" #: notebook/static/notebook/js/notebook.js:3027 msgid "An invalid notebook may not function properly. The validation error was:" -msgstr "无效的笔记本可能不能正常工作. 验证错误:" +msgstr "无效的笔记本可能无法正常运行。校验错误:" #: notebook/static/notebook/js/notebook.js:3066 #, python-format msgid "This notebook has been converted from an older notebook format to the current notebook format v(%s)." -msgstr "本笔记本已从较旧的笔记本格式转换为当前的笔记本格式 v(%s)." +msgstr "本笔记本已从较旧的笔记本格式转换为当前的笔记本格式 v(%s)。" #: notebook/static/notebook/js/notebook.js:3068 #, python-format msgid "This notebook has been converted from a newer notebook format to the current notebook format v(%s)." -msgstr "这个笔记本已经从一种新的笔记本格式转换为当前的笔记本格式 v(%s)." +msgstr "这个笔记本已经从一种新的笔记本格式转换为当前的笔记本格式 v(%s)。" #: notebook/static/notebook/js/notebook.js:3076 msgid "The next time you save this notebook, the current notebook format will be used." -msgstr "下次你保存这个笔记本时, 当前的笔记本格式将会被使用." +msgstr "下次你保存这个笔记本时,当前的笔记本格式将会被使用。" #: notebook/static/notebook/js/notebook.js:3081 msgid "Older versions of Jupyter may not be able to read the new format." -msgstr "旧版本的Jupyter可能无法读取新格式." +msgstr "旧版本的 Jupyter 可能无法读取新格式。" #: notebook/static/notebook/js/notebook.js:3083 msgid "Some features of the original notebook may not be available." -msgstr "原始笔记本的一些特性可能无法使用." +msgstr "原笔记本的一些特性可能无法使用。" #: notebook/static/notebook/js/notebook.js:3086 msgid "To preserve the original version, close the notebook without saving it." -msgstr "为了保存原始版本, 关闭笔记本而不保存它." +msgstr "为了保存原始版本,关闭笔记本而不保存它。" #: notebook/static/notebook/js/notebook.js:3091 msgid "Notebook converted" -msgstr "代码被修改了" +msgstr "已转换笔记本" #: notebook/static/notebook/js/notebook.js:3113 msgid "(No name)" -msgstr "(没有名字)" +msgstr "(没有名字)" #: notebook/static/notebook/js/notebook.js:3161 #, python-format msgid "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details." -msgstr "加载本笔记本时出现了一个未知的错误. 这个版本可以加载%s或更早的笔记本. 有关详细信息, 请参阅服务器日志." +msgstr "加载本笔记本时出现了一个未知的错误。这个版本可以加载 %s 或更早的笔记本。有关详细信息,请参阅服务器日志。" #: notebook/static/notebook/js/notebook.js:3172 msgid "Error loading notebook" -msgstr "加载代码出错" +msgstr "加载笔记本出错" #: notebook/static/notebook/js/notebook.js:3273 msgid "Are you sure you want to revert the notebook to the latest checkpoint?" -msgstr "确定撤销操作?" +msgstr "确定将笔记本恢复至最近的检查点?" #: notebook/static/notebook/js/notebook.js:3276 msgid "This cannot be undone." -msgstr "该操作不能被执行." +msgstr "该操作不能被还原。" #: notebook/static/notebook/js/notebook.js:3279 msgid "The checkpoint was last updated at:" -msgstr "这个代码最后更新时间:" +msgstr "笔记本的最新检查点更新于:" #: notebook/static/notebook/js/notebook.js:3290 msgid "Revert notebook to checkpoint" -msgstr "恢复代码" +msgstr "恢复笔记本至检查点" #: notebook/static/notebook/js/notificationarea.js:76 #: notebook/static/notebook/js/tour.js:61 @@ -1024,19 +1024,19 @@ #: notebook/static/notebook/js/notificationarea.js:87 #: notebook/static/notebook/js/tour.js:54 msgid "Command Mode" -msgstr "命令 模式" +msgstr "命令模式" #: notebook/static/notebook/js/notificationarea.js:94 msgid "Kernel Created" -msgstr "服务创建" +msgstr "内核已创建" #: notebook/static/notebook/js/notificationarea.js:98 msgid "Connecting to kernel" -msgstr "正在连接服务" +msgstr "正在连接内核" #: notebook/static/notebook/js/notificationarea.js:102 msgid "Not Connected" -msgstr "未连接成功" +msgstr "未连接" #: notebook/static/notebook/js/notificationarea.js:105 msgid "click to reconnect" @@ -1044,39 +1044,39 @@ #: notebook/static/notebook/js/notificationarea.js:114 msgid "Restarting kernel" -msgstr "重启服务" +msgstr "重启内核" #: notebook/static/notebook/js/notificationarea.js:128 msgid "Kernel Restarting" -msgstr "服务正重启" +msgstr "内核正在重启" #: notebook/static/notebook/js/notificationarea.js:129 msgid "The kernel appears to have died. It will restart automatically." -msgstr "服务似乎挂掉了,但是会立刻重启的." +msgstr "内核似乎挂掉了,它很快将自动重启。" #: notebook/static/notebook/js/notificationarea.js:139 #: notebook/static/notebook/js/notificationarea.js:197 #: notebook/static/notebook/js/notificationarea.js:217 msgid "Dead kernel" -msgstr "挂掉的服务" +msgstr "挂掉的内核" #: notebook/static/notebook/js/notificationarea.js:140 #: notebook/static/notebook/js/notificationarea.js:218 #: notebook/static/notebook/js/notificationarea.js:265 msgid "Kernel Dead" -msgstr "服务挂掉" +msgstr "内核挂掉" #: notebook/static/notebook/js/notificationarea.js:144 msgid "Interrupting kernel" -msgstr "正在中断服务" +msgstr "正在中断内核" #: notebook/static/notebook/js/notificationarea.js:150 msgid "No Connection to Kernel" -msgstr "没有到服务的连接" +msgstr "没有连接到内核" #: notebook/static/notebook/js/notificationarea.js:160 msgid "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration." -msgstr "到后台服务的连接没能建立, 我们会继续尝试重连, 请检查网络连接还有服务配置." +msgstr "无法建立到笔记本服务器的连接。 我们会继续尝试重连。请检查网络连接还有服务配置。" #: notebook/static/notebook/js/notificationarea.js:165 msgid "Connection failed" @@ -1084,11 +1084,11 @@ #: notebook/static/notebook/js/notificationarea.js:178 msgid "No kernel" -msgstr "没有服务" +msgstr "没有内核" #: notebook/static/notebook/js/notificationarea.js:179 msgid "Kernel is not running" -msgstr "服务没有运行" +msgstr "内核没有运行" #: notebook/static/notebook/js/notificationarea.js:186 msgid "Don't Restart" @@ -1100,90 +1100,90 @@ #: notebook/static/notebook/js/notificationarea.js:190 msgid "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened." -msgstr "内核已经死亡,自动重启也失败了。有可能内核不能重新启动。如果您不能重新启动内核,您仍然能够保存笔记本,但是运行代码将不再工作,直到笔记本重新打开. " +msgstr "内核已经死亡,自动重启也失败了。可能是内核不能重新启动。如果您不能重新启动内核,您仍然能够保存笔记本,但笔记本要重新打开才能运行代码。" #: notebook/static/notebook/js/notificationarea.js:224 msgid "No Kernel" -msgstr "没有服务" +msgstr "没有内核" #: notebook/static/notebook/js/notificationarea.js:251 msgid "Failed to start the kernel" -msgstr "启动服务失败" +msgstr "启动内核失败" #: notebook/static/notebook/js/notificationarea.js:271 #: notebook/static/notebook/js/notificationarea.js:291 #: notebook/static/notebook/js/notificationarea.js:305 msgid "Kernel Busy" -msgstr "服务正忙" +msgstr "内核正忙" #: notebook/static/notebook/js/notificationarea.js:272 msgid "Kernel starting, please wait..." -msgstr "服务正在启动,请等待..." +msgstr "内核正在启动,请等待..." #: notebook/static/notebook/js/notificationarea.js:278 #: notebook/static/notebook/js/notificationarea.js:285 msgid "Kernel Idle" -msgstr "服务空闲" +msgstr "内核空闲" #: notebook/static/notebook/js/notificationarea.js:279 msgid "Kernel ready" -msgstr "服务准备好了" +msgstr "内核就绪" #: notebook/static/notebook/js/notificationarea.js:296 msgid "Using kernel: " -msgstr "使用服务:" +msgstr "使用内核:" #: notebook/static/notebook/js/notificationarea.js:297 msgid "Only candidate for language: %1$s was %2$s." -msgstr "只支持语言: %1$s - %2$s." +msgstr "只支持语言: %1$s - %2$s." #: notebook/static/notebook/js/notificationarea.js:318 msgid "Loading notebook" -msgstr "加载服务" +msgstr "加载笔记本" #: notebook/static/notebook/js/notificationarea.js:321 msgid "Notebook loaded" -msgstr "程序已加载" +msgstr "笔记本已加载" #: notebook/static/notebook/js/notificationarea.js:324 msgid "Saving notebook" -msgstr "保存代码" +msgstr "保存笔记本" #: notebook/static/notebook/js/notificationarea.js:327 msgid "Notebook saved" -msgstr "代码已保存" +msgstr "笔记本已保存" #: notebook/static/notebook/js/notificationarea.js:330 msgid "Notebook save failed" -msgstr "代码保存失败" +msgstr "笔记本保存失败" #: notebook/static/notebook/js/notificationarea.js:333 msgid "Notebook copy failed" -msgstr "代码复制失败" +msgstr "笔记本复制失败" #: notebook/static/notebook/js/notificationarea.js:338 msgid "Checkpoint created" -msgstr "checkpoint已创建" +msgstr "检查点已创建" #: notebook/static/notebook/js/notificationarea.js:346 msgid "Checkpoint failed" -msgstr "Checkpoint 失败" +msgstr "检查点创建失败" #: notebook/static/notebook/js/notificationarea.js:349 msgid "Checkpoint deleted" -msgstr "checkpoint已删除" +msgstr "检查点已删除" #: notebook/static/notebook/js/notificationarea.js:352 msgid "Checkpoint delete failed" -msgstr "checkpoint删除失败" +msgstr "检查点删除失败" #: notebook/static/notebook/js/notificationarea.js:355 msgid "Restoring to checkpoint..." -msgstr "重新保存checkpoint..." +msgstr "正在恢复至检查点..." #: notebook/static/notebook/js/notificationarea.js:358 msgid "Checkpoint restore failed" -msgstr "checkpoint 重新保存失败" +msgstr "检查点恢复失败" #: notebook/static/notebook/js/notificationarea.js:363 msgid "Autosave disabled" @@ -1192,11 +1192,11 @@ #: notebook/static/notebook/js/notificationarea.js:366 #, python-format msgid "Saving every %d sec." -msgstr "每隔 %s 秒保存一次." +msgstr "每隔 %s 秒保存一次。" #: notebook/static/notebook/js/notificationarea.js:382 msgid "Trusted" -msgstr "可信的" +msgstr "可信" #: notebook/static/notebook/js/notificationarea.js:384 msgid "Not Trusted" @@ -1204,49 +1204,49 @@ #: notebook/static/notebook/js/outputarea.js:85 msgid "click to expand output" -msgstr "点击展开内容" +msgstr "点击展开输出" #: notebook/static/notebook/js/outputarea.js:89 msgid "click to expand output; double click to hide output" -msgstr "点击展开输出; 双击隐藏" +msgstr "点击展开输出;双击隐藏输出" #: notebook/static/notebook/js/outputarea.js:177 msgid "click to unscroll output; double click to hide" -msgstr "单击显示输出; 双击隐藏" +msgstr "单击取消滚动输出;双击隐藏" #: notebook/static/notebook/js/outputarea.js:184 msgid "click to scroll output; double click to hide" -msgstr "点击滚动输出;双击隐藏" +msgstr "点击滚动输出;双击隐藏" #: notebook/static/notebook/js/outputarea.js:434 msgid "Javascript error adding output!" -msgstr "Javascript添加输出错误!" +msgstr "添加输出时 Javascript 出错了!" #: notebook/static/notebook/js/outputarea.js:439 msgid "See your browser Javascript console for more details." -msgstr "更多细节请参见您的浏览器Javascript控制台。" +msgstr "更多细节请参见您的浏览器 Javascript 控制台。" #: notebook/static/notebook/js/outputarea.js:480 #, python-format msgid "Out[%d]:" -msgstr "输出[%d]:" +msgstr "" #: notebook/static/notebook/js/outputarea.js:589 #, python-format msgid "Unrecognized output: %s" -msgstr "未识别的输出: %s" +msgstr "未识别的输出: %s" #: notebook/static/notebook/js/pager.js:36 msgid "Open the pager in an external window" -msgstr "在内部窗口打开页面" +msgstr "在外部窗口打开分页器" #: notebook/static/notebook/js/pager.js:45 msgid "Close the pager" -msgstr "关闭页面" +msgstr "关闭分页器" #: notebook/static/notebook/js/pager.js:148 msgid "Jupyter Pager" -msgstr "Jupyter 页面" +msgstr "Jupyter 分页器" #: notebook/static/notebook/js/quickhelp.js:39 #: notebook/static/notebook/js/quickhelp.js:54 @@ -1263,12 +1263,12 @@ #: notebook/static/notebook/js/quickhelp.js:41 #: notebook/static/notebook/js/quickhelp.js:58 msgid "go one word left" -msgstr "跳到单词左边" +msgstr "往左跳一个单词" #: notebook/static/notebook/js/quickhelp.js:42 #: notebook/static/notebook/js/quickhelp.js:59 msgid "go one word right" -msgstr "跳到单词右边" +msgstr "往右跳一个单词" #: notebook/static/notebook/js/quickhelp.js:43 #: notebook/static/notebook/js/quickhelp.js:60 @@ -1292,19 +1292,19 @@ #: notebook/static/notebook/js/quickhelp.js:47 msgid "emacs-style line kill" -msgstr "" +msgstr "Emacs 风格的 Line Kill" #: notebook/static/notebook/js/quickhelp.js:48 msgid "delete line left of cursor" -msgstr "删除光标左边线" +msgstr "删除光标左边一行" #: notebook/static/notebook/js/quickhelp.js:49 msgid "delete line right of cursor" -msgstr "" +msgstr "删除光标右边一行" #: notebook/static/notebook/js/quickhelp.js:68 msgid "code completion or indent" -msgstr "代码完成或缩进" +msgstr "代码补全或缩进" #: notebook/static/notebook/js/quickhelp.js:69 msgid "tooltip" @@ -1328,7 +1328,7 @@ #: notebook/static/notebook/js/quickhelp.js:74 msgid "comment" -msgstr "评论" +msgstr "注释" #: notebook/static/notebook/js/quickhelp.js:75 msgid "delete whole line" @@ -1340,7 +1340,7 @@ #: notebook/static/notebook/js/quickhelp.js:77 msgid "toggle overwrite flag" -msgstr "切换 重写标志" +msgstr "切换重写标志" #: notebook/static/notebook/js/quickhelp.js:111 #: notebook/static/notebook/js/quickhelp.js:252 @@ -1374,7 +1374,7 @@ #: notebook/static/notebook/js/quickhelp.js:118 msgid "Caps Lock" -msgstr "Caps Lock" +msgstr "大写锁定" #: notebook/static/notebook/js/quickhelp.js:119 #: notebook/static/notebook/js/quickhelp.js:278 @@ -1414,7 +1414,7 @@ #: notebook/static/notebook/js/quickhelp.js:127 msgid "Backspace" -msgstr "删除" +msgstr "退格" #: notebook/static/notebook/js/quickhelp.js:128 msgid "Minus" @@ -1426,15 +1426,15 @@ #: notebook/static/notebook/js/quickhelp.js:206 msgid "The Jupyter Notebook has two different keyboard input modes." -msgstr "Jupyter笔记本有两种不同的键盘输入模式." +msgstr "Jupyter 笔记本有两种不同的键盘输入模式。" #: notebook/static/notebook/js/quickhelp.js:208 msgid "Edit mode allows you to type code or text into a cell and is indicated by a green cell border." -msgstr "编辑模式允许您将代码或文本输入到一个单元格中,并通过一个绿色的单元格来表示" +msgstr "编辑模式允许您将代码或文本输入到一个单元格中,并通过一个绿色边框的单元格来表示" #: notebook/static/notebook/js/quickhelp.js:210 msgid "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin." -msgstr "命令模式将键盘与笔记本级命令绑定在一起,并通过一个灰色的单元格边界显示,该边框为蓝色的左边框。" +msgstr "命令模式将键盘与笔记本级命令绑定在一起,并通过一个灰框、左边距蓝色的单元格显示。" #: notebook/static/notebook/js/quickhelp.js:231 #: notebook/static/notebook/js/tooltip.js:58 @@ -1444,7 +1444,7 @@ #: notebook/static/notebook/js/quickhelp.js:234 msgid "Keyboard shortcuts" -msgstr "快捷键" +msgstr "键盘快捷键" #: notebook/static/notebook/js/quickhelp.js:249 msgid "Command" @@ -1465,7 +1465,7 @@ #: notebook/static/notebook/js/quickhelp.js:279 #, python-format msgid "Command Mode (press %s to enable)" -msgstr "命令行模式(按 %s 生效)" +msgstr "命令行模式(按 %s 生效)" #: notebook/static/notebook/js/quickhelp.js:281 msgid "Edit Shortcuts" @@ -1473,16 +1473,16 @@ #: notebook/static/notebook/js/quickhelp.js:284 msgid "edit command-mode keyboard shortcuts" -msgstr "编辑命令行快捷键" +msgstr "编辑命令模式键盘快捷键" #: notebook/static/notebook/js/quickhelp.js:301 #, python-format msgid "Edit Mode (press %s to enable)" -msgstr "编辑模式(按 %s 生效)" +msgstr "编辑模式(按 %s 生效)" #: notebook/static/notebook/js/savewidget.js:49 msgid "Autosave Failed!" -msgstr "自动保存失败!" +msgstr "自动保存失败!" #: notebook/static/notebook/js/savewidget.js:71 #: notebook/static/tree/js/notebooklist.js:850 @@ -1493,19 +1493,19 @@ #: notebook/static/notebook/js/savewidget.js:78 #: notebook/static/tree/js/notebooklist.js:841 msgid "Enter a new notebook name:" -msgstr "请输入代码名称:" +msgstr "请输入新的笔记本名称:" #: notebook/static/notebook/js/savewidget.js:86 msgid "Rename Notebook" -msgstr "重命名" +msgstr "重命名笔记本" #: notebook/static/notebook/js/savewidget.js:98 msgid "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:" -msgstr "无效的文件名. 代码名不能为空 并且不能包含\".\" 请输入一个新的文件名:" +msgstr "无效的笔记本名称。笔记本名称不能为空,并且不能包含\":/.\"。请输入一个新的笔记本名称:" #: notebook/static/notebook/js/savewidget.js:103 msgid "Renaming..." -msgstr "正在重命名..." +msgstr "正在重命名…" #: notebook/static/notebook/js/savewidget.js:109 msgid "Unknown error" @@ -1518,20 +1518,20 @@ #: notebook/static/notebook/js/savewidget.js:193 #, python-format msgid "Last Checkpoint: %s" -msgstr "最后检查: %s " +msgstr "最新检查点: %s " #: notebook/static/notebook/js/savewidget.js:217 msgid "(unsaved changes)" -msgstr "(未保存改变)" +msgstr "(更改未保存)" #: notebook/static/notebook/js/savewidget.js:219 msgid "(autosaved)" -msgstr "(自动保存)" +msgstr "(已自动保存)" #: notebook/static/notebook/js/searchandreplace.js:71 #, python-format msgid "Warning: too many matches (%d). Some changes might not be shown or applied." -msgstr "警告:太多的匹配(%d). 有些更改可能不会被显示或应用." +msgstr "警告:太多的匹配(%d)。有些更改可能不会被显示或应用." #: notebook/static/notebook/js/searchandreplace.js:74 #, python-format @@ -1542,11 +1542,11 @@ #: notebook/static/notebook/js/searchandreplace.js:141 msgid "More than 100 matches, aborting" -msgstr "超过100个匹配, 中止" +msgstr "超过 100 个匹配, 中止" #: notebook/static/notebook/js/searchandreplace.js:161 msgid "Use regex (JavaScript regex syntax)" -msgstr "" +msgstr "使用正则表达式(JavaScript 正则表达式语法)" #: notebook/static/notebook/js/searchandreplace.js:169 msgid "Replace in selected cells" @@ -1554,7 +1554,7 @@ #: notebook/static/notebook/js/searchandreplace.js:176 msgid "Match case" -msgstr "匹配" +msgstr "匹配大小写" #: notebook/static/notebook/js/searchandreplace.js:182 msgid "Find" @@ -1566,11 +1566,11 @@ #: notebook/static/notebook/js/searchandreplace.js:244 msgid "No matches, invalid or empty regular expression" -msgstr "没匹配到, 无效或空的正则表达式" +msgstr "无匹配,表达式无效或表达式为空" #: notebook/static/notebook/js/searchandreplace.js:348 msgid "Replace All" -msgstr "替换所有" +msgstr "全部替换" #: notebook/static/notebook/js/searchandreplace.js:352 msgid "Find and Replace" @@ -1583,27 +1583,27 @@ #: notebook/static/notebook/js/textcell.js:559 msgid "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified." -msgstr "" +msgstr "在这里直接写 LaTeX 或者其它格式的文本来配合 nbconvert。笔记本不会渲染它。传给 nbconvert 时,原始单元格的内容会被完好地加进输出。" #: notebook/static/notebook/js/tooltip.js:41 msgid "Grow the tooltip vertically (press shift-tab twice)" -msgstr "" +msgstr "纵向展开工具提示(按两次 Shift+Tab)" #: notebook/static/notebook/js/tooltip.js:48 msgid "show the current docstring in pager (press shift-tab 4 times)" -msgstr "" +msgstr "在分页器中显示当前的文档字符串(按四次 Shift+Tab)" #: notebook/static/notebook/js/tooltip.js:49 msgid "Open in Pager" -msgstr "" +msgstr "在分页器中打开" #: notebook/static/notebook/js/tooltip.js:68 msgid "Tooltip will linger for 10 seconds while you type" -msgstr "" +msgstr "当您键入时,工具提示会停留十秒" #: notebook/static/notebook/js/tour.js:27 msgid "Welcome to the Notebook Tour" -msgstr "欢迎使用Notebook" +msgstr "欢迎来到 Notebook 导览" #: notebook/static/notebook/js/tour.js:30 msgid "You can use the left and right arrow keys to go backwards and forwards." @@ -1615,19 +1615,19 @@ #: notebook/static/notebook/js/tour.js:35 msgid "Click here to change the filename for this notebook." -msgstr "点击改变代码文件名" +msgstr "点击这里修改笔记本的文件名" #: notebook/static/notebook/js/tour.js:39 msgid "Notebook Menubar" -msgstr "菜单栏" +msgstr "笔记本菜单栏" #: notebook/static/notebook/js/tour.js:40 msgid "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with." -msgstr "菜单栏操作界面, 它的单元格和它与之通信的内核上进行操作. " +msgstr "菜单栏上的菜单可以用来操作笔记本、单元格和与笔记本通信的内核。" #: notebook/static/notebook/js/tour.js:44 msgid "Notebook Toolbar" -msgstr "代码工具栏" +msgstr "笔记本工具栏" #: notebook/static/notebook/js/tour.js:45 msgid "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information." @@ -1639,35 +1639,35 @@ #: notebook/static/notebook/js/tour.js:50 msgid "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in." -msgstr "该笔记本有两种模式:编辑模式和命令模式. 在这个区域, 一个指示器可以显示你在哪个模式. " +msgstr "笔记本有两种模式:编辑模式和命令模式。在这个区域,一个指示器可以显示你在哪个模式。" #: notebook/static/notebook/js/tour.js:58 msgid "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area." -msgstr "现在你处于命令模式, 许多快捷键都可以使用. 在该模式下, 指示区域中没有显示图标." +msgstr "现在你处于命令模式,许多快捷键都可以使用。在该模式下,指示区域中没有显示图标。" #: notebook/static/notebook/js/tour.js:64 msgid "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode." -msgstr "按下Enter, 或者点击输入文本区域, 将你返回到命令模式. " +msgstr "按下Enter或者点击输入文本区域来切换到编辑模式. " #: notebook/static/notebook/js/tour.js:70 msgid "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell." -msgstr "请注意, 当前活动单元周围的边框改变了颜色. 键入将在当前活动单元中插入文本." +msgstr "您会发现当前活动单元格周围的边框改变了颜色。键入将在当前活动单元格中插入文本." #: notebook/static/notebook/js/tour.js:73 msgid "Back to Command Mode" -msgstr "转到命令模式" +msgstr "回到命令模式" #: notebook/static/notebook/js/tour.js:76 msgid "Pressing Esc or clicking outside of the input text area takes you back to Command Mode." -msgstr "按下Esc, 或者点击输入文本区域, 将你返回到命令模式. " +msgstr "按下Esc或者点击输入框外面来返回到命令模式。" #: notebook/static/notebook/js/tour.js:79 msgid "Keyboard Shortcuts" -msgstr "快捷键" +msgstr "键盘快捷键" #: notebook/static/notebook/js/tour.js:91 msgid "You can click here to get a list of all of the keyboard shortcuts." -msgstr "点击获得所有快捷键" +msgstr "点击这里获得所有键盘快捷键" #: notebook/static/notebook/js/tour.js:94 #: notebook/static/notebook/js/tour.js:100 @@ -1688,7 +1688,7 @@ #: notebook/static/notebook/js/tour.js:109 msgid "To cancel a computation in progress, you can click here." -msgstr "要取消正在进行的计算,您可以点击这里。" +msgstr "要取消正在进行的计算任务,您可以点击这里。" #: notebook/static/notebook/js/tour.js:114 msgid "Notification Area" @@ -1696,15 +1696,15 @@ #: notebook/static/notebook/js/tour.js:115 msgid "Messages in response to user actions (Save, Interrupt, etc.) appear here." -msgstr "响应用户操作(保存, 中断等)的消息出现在这里." +msgstr "响应用户操作(保存,中断等)的消息出现在这里。" #: notebook/static/notebook/js/tour.js:117 msgid "End of Tour" -msgstr "结束" +msgstr "结束导览" #: notebook/static/notebook/js/tour.js:120 msgid "This concludes the Jupyter Notebook User Interface Tour." -msgstr "这就结束了Jupyter笔记本用户界面之旅。" +msgstr "Jupyter 笔记本用户界面之旅到此为止。" #: notebook/static/notebook/js/celltoolbarpresets/attachments.js:32 msgid "Edit Attachments" @@ -1721,23 +1721,23 @@ #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:22 msgid "Custom" -msgstr "" +msgstr "自定义" #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:32 msgid "Set the MIME type of the raw cell:" -msgstr "" +msgstr "设置原始单元格的 MIME 类型:" #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:40 msgid "Raw Cell MIME Type" -msgstr "" +msgstr "原始单元格的 MIME 类型" #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:74 msgid "Raw NBConvert Format" -msgstr "" +msgstr "原始 NBConvert 类型" #: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:81 msgid "Raw Cell Format" -msgstr "" +msgstr "原始单元格格式" #: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:15 msgid "Slide" @@ -1761,7 +1761,7 @@ #: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:35 msgid "Slide Type" -msgstr "类型" +msgstr "幻灯片类型" #: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:41 msgid "Slideshow" @@ -1773,15 +1773,15 @@ #: notebook/static/notebook/js/celltoolbarpresets/tags.js:163 msgid "Edit the list of tags below. All whitespace is treated as tag separators." -msgstr "编辑下面的标签列表. 所有空格都被当作标记分隔符." +msgstr "编辑下面的标签列表。所有空格都被当作标记分隔符。" #: notebook/static/notebook/js/celltoolbarpresets/tags.js:172 msgid "Edit the tags" -msgstr "编辑tags" +msgstr "编辑标签" #: notebook/static/notebook/js/celltoolbarpresets/tags.js:186 msgid "Edit Tags" -msgstr "编辑tags" +msgstr "编辑标签" #: notebook/static/tree/js/kernellist.js:86 #: notebook/static/tree/js/terminallist.js:105 @@ -1791,11 +1791,11 @@ #: notebook/static/tree/js/newnotebook.js:70 #, python-format msgid "Create a new notebook with %s" -msgstr "创建新的代码 %s" +msgstr "创建新的笔记本 %s" #: notebook/static/tree/js/newnotebook.js:101 msgid "An error occurred while creating a new notebook." -msgstr "当创建新的notebook的时候出现一个错误." +msgstr "创建新笔记本时出错。" #: notebook/static/tree/js/notebooklist.js:154 msgid "Creating File Failed" @@ -1803,7 +1803,7 @@ #: notebook/static/tree/js/notebooklist.js:156 msgid "An error occurred while creating a new file." -msgstr "创建新文件的时候出现了一个错误" +msgstr "创建新文件时出错。" #: notebook/static/tree/js/notebooklist.js:174 msgid "Creating Folder Failed" @@ -1811,7 +1811,7 @@ #: notebook/static/tree/js/notebooklist.js:176 msgid "An error occurred while creating a new folder." -msgstr "创建新文件夹的时候出现了一个错误." +msgstr "创建新文件夹时出错。" #: notebook/static/tree/js/notebooklist.js:271 msgid "Failed to read file" @@ -1820,13 +1820,13 @@ #: notebook/static/tree/js/notebooklist.js:272 #, python-format msgid "Failed to read file %s" -msgstr "读取代码文件 %s 失败了." +msgstr "读取文件 %s 失败了" #: notebook/static/tree/js/notebooklist.js:283 #, python-format msgid "The file size is %d MB. Do you still want to upload it?" -msgstr "文件大小为 %d MB, 还想上传么?" +msgstr "文件大小为 %d MB,依然上传?" #: notebook/static/tree/js/notebooklist.js:286 @@ -1836,16 +1836,16 @@ #: notebook/static/tree/js/notebooklist.js:355 msgid "Server error: " -msgstr "服务出现错误:" +msgstr "服务出现错误:" #: notebook/static/tree/js/notebooklist.js:380 msgid "The notebook list is empty." -msgstr "代码列表为空, 请添加代码." +msgstr "笔记本列表为空。" #: notebook/static/tree/js/notebooklist.js:453 msgid "Click here to rename, delete, etc." -msgstr "点击进行操作." +msgstr "点击这里进行重命名或删除等操作" #: notebook/static/tree/js/notebooklist.js:493 @@ -1854,17 +1854,17 @@ #: notebook/static/tree/js/notebooklist.js:839 msgid "Enter a new file name:" -msgstr "请输入一个新的文件名:" +msgstr "请输入一个新的文件名:" #: notebook/static/tree/js/notebooklist.js:840 msgid "Enter a new directory name:" -msgstr "请输入一个新的路径:" +msgstr "请输入一个新的路径:" #: notebook/static/tree/js/notebooklist.js:842 msgid "Enter a new name:" -msgstr "请输入新名字:" +msgstr "请输入新名字:" #: notebook/static/tree/js/notebooklist.js:847 @@ -1878,7 +1878,7 @@ #: notebook/static/tree/js/notebooklist.js:849 msgid "Rename notebook" -msgstr "重命名" +msgstr "重命名笔记本" #: notebook/static/tree/js/notebooklist.js:863 msgid "Move" @@ -1896,15 +1896,15 @@ #, python-format msgid "Enter a new destination directory path for this item:" msgid_plural "Enter a new destination directory path for these %d items:" -msgstr[0] "为代码选择一个新的路径:" -msgstr[1] "为选中的 %d 代码选择一个新的路径:" +msgstr[0] "为笔记本选择一个新的路径:" +msgstr[1] "为选中的 %d 笔记本选择一个新的路径:" #: notebook/static/tree/js/notebooklist.js:944 #, python-format msgid "Move an Item" msgid_plural "Move %d Items" -msgstr[0] "移动一个代码文件" -msgstr[1] "移动 %d 个代码文件" +msgstr[0] "移动一个文件" +msgstr[1] "移动 %d 个文件" #: notebook/static/tree/js/notebooklist.js:963 msgid "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\"." @@ -1918,13 +1918,13 @@ #, python-format msgid "Are you sure you want to permanently delete: \"%s\"?" msgid_plural "Are you sure you want to permanently delete the %d files or folders selected?" -msgstr[0] "删除不可恢复: \"%s\", 确定删除?" -msgstr[1] "删除不可恢复: 选择 %d 文件, 确定删除?" +msgstr[0] "确定永久删除 \"%s\"?" +msgstr[1] "确定永久删除选中的 %d 个文件或文件夹?" #: notebook/static/tree/js/notebooklist.js:1039 #, python-format msgid "An error occurred while deleting \"%s\"." -msgstr "当删除 \"%s\" 时, 出现错误." +msgstr "当删除 \"%s\" 时, 出现错误。" #: notebook/static/tree/js/notebooklist.js:1041 msgid "Delete Failed" @@ -1934,21 +1934,21 @@ #, python-format msgid "Are you sure you want to duplicate: \"%s\"?" msgid_plural "Are you sure you want to duplicate the %d files selected?" -msgstr[0] "确定复制: \"%s\"?" -msgstr[1] "确定复制: 已选择 %d 文件?" +msgstr[0] "确定制作 \"%s\" 的副本?" +msgstr[1] "确定制作选中的 %d 个文件的副本?" #: notebook/static/tree/js/notebooklist.js:1089 msgid "Duplicate" -msgstr "复制" +msgstr "制作副本" #: notebook/static/tree/js/notebooklist.js:1103 #, python-format msgid "An error occurred while duplicating \"%s\"." -msgstr "当复制\"%s\" 时出现错误." +msgstr "制作 \"%s\" 的副本时出现错误。" #: notebook/static/tree/js/notebooklist.js:1105 msgid "Duplicate Failed" -msgstr "复制失败" +msgstr "制作副本失败" #: notebook/static/tree/js/notebooklist.js:1325 msgid "Upload" @@ -1964,12 +1964,12 @@ #: notebook/static/tree/js/notebooklist.js:1364 msgid "Cannot upload invalid Notebook" -msgstr "无法上传无效的Notebook" +msgstr "无法上传无效的笔记本" #: notebook/static/tree/js/notebooklist.js:1397 #, python-format msgid "There is already a file named \"%s\". Do you want to replace it?" -msgstr "文件名已经存在 \"%s\", 需要替换现有文件?" +msgstr "已经存在一个名为 \"%s\" 的文件,替换现有文件?" #: notebook/static/tree/js/notebooklist.js:1399 msgid "Replace file" diff -Nru jupyter-notebook-6.1.4/notebook/jstest.py jupyter-notebook-6.2.0/notebook/jstest.py --- jupyter-notebook-6.1.4/notebook/jstest.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/jstest.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Notebook Javascript Test Controller This module runs one or more subprocesses which will actually run the Javascript @@ -8,8 +7,6 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import absolute_import, print_function - import argparse import json import multiprocessing.pool @@ -45,7 +42,7 @@ daemon = True # Don't hang if main thread crashes started = False def __init__(self, echo=False): - super(StreamCapturer, self).__init__() + super().__init__() self.echo = echo self.streams = [] self.buffer = BytesIO() @@ -264,14 +261,14 @@ # If the engine is SlimerJS, we need to buffer the output because # SlimerJS does not support exit codes, so CasperJS always returns 0. if self.engine == 'slimerjs' and not buffer_output: - return super(JSController, self).launch(capture_output=True) + return super().launch(capture_output=True) else: - return super(JSController, self).launch(buffer_output=buffer_output) + return super().launch(buffer_output=buffer_output) def wait(self, *pargs, **kwargs): """Wait for the JSController to finish""" - ret = super(JSController, self).wait(*pargs, **kwargs) + ret = super().wait(*pargs, **kwargs) # If this is a SlimerJS controller, check the captured stdout for # errors. Otherwise, just return the return code. if self.engine == 'slimerjs': diff -Nru jupyter-notebook-6.1.4/notebook/log.py jupyter-notebook-6.2.0/notebook/log.py --- jupyter-notebook-6.1.4/notebook/log.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/log.py 2021-01-13 16:30:49.000000000 +0000 @@ -10,7 +10,7 @@ from .prometheus.log_functions import prometheus_log_method -def log_request(handler): +def log_request(handler, log=access_log, log_json=False): """log a bit more information about each request than tornado's default - move static file get success to debug-level (reduces noise) @@ -22,29 +22,35 @@ request = handler.request if status < 300 or status == 304: # Successes (or 304 FOUND) are debug-level - log_method = access_log.debug + log_method = log.debug elif status < 400: - log_method = access_log.info + log_method = log.info elif status < 500: - log_method = access_log.warning + log_method = log.warning else: - log_method = access_log.error + log_method = log.error - request_time = 1000.0 * handler.request.request_time() + request_time = 1000.0 * request.request_time() ns = dict( status=status, method=request.method, ip=request.remote_ip, uri=request.uri, - request_time=request_time, + request_time=float('%.2f' % request_time), ) - msg = "{status} {method} {uri} ({ip}) {request_time:.2f}ms" + msg = "{status} {method} {uri} ({ip}) {request_time:f}ms" if status >= 400: # log bad referers ns['referer'] = request.headers.get('Referer', 'None') msg = msg + ' referer={referer}' if status >= 500 and status != 502: # log all headers if it caused an error - log_method(json.dumps(dict(request.headers), indent=2)) - log_method(msg.format(**ns)) + if log_json: + log_method("", extra=dict(props=dict(request.headers))) + else: + log_method(json.dumps(dict(request.headers), indent=2)) + if log_json: + log_method("", extra=dict(props=ns)) + else: + log_method(msg.format(**ns)) prometheus_log_method(handler) diff -Nru jupyter-notebook-6.1.4/notebook/__main__.py jupyter-notebook-6.2.0/notebook/__main__.py --- jupyter-notebook-6.1.4/notebook/__main__.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/__main__.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -from __future__ import absolute_import if __name__ == '__main__': from notebook import notebookapp as app diff -Nru jupyter-notebook-6.1.4/notebook/nbconvert/handlers.py jupyter-notebook-6.2.0/notebook/nbconvert/handlers.py --- jupyter-notebook-6.1.4/notebook/nbconvert/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/nbconvert/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -83,8 +83,7 @@ def content_security_policy(self): # In case we're serving HTML/SVG, confine any Javascript to a unique # origin so it can't interact with the notebook server. - return super(NbconvertFileHandler, self).content_security_policy + \ - "; sandbox allow-scripts" + return super().content_security_policy + "; sandbox allow-scripts" @web.authenticated @gen.coroutine @@ -158,8 +157,7 @@ def content_security_policy(self): # In case we're serving HTML/SVG, confine any Javascript to a unique # origin so it can't interact with the notebook server. - return super(NbconvertPostHandler, self).content_security_policy + \ - "; sandbox allow-scripts" + return super().content_security_policy + "; sandbox allow-scripts" @web.authenticated def post(self, format): diff -Nru jupyter-notebook-6.1.4/notebook/nbconvert/tests/test_nbconvert_handlers.py jupyter-notebook-6.2.0/notebook/nbconvert/tests/test_nbconvert_handlers.py --- jupyter-notebook-6.1.4/notebook/nbconvert/tests/test_nbconvert_handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/nbconvert/tests/test_nbconvert_handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 import io import json import os diff -Nru jupyter-notebook-6.1.4/notebook/nbextensions.py jupyter-notebook-6.2.0/notebook/nbextensions.py --- jupyter-notebook-6.1.4/notebook/nbextensions.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/nbextensions.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,11 +1,8 @@ -# coding: utf-8 """Utilities for installing Javascript extensions for the notebook""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import os import shutil import sys @@ -980,7 +977,7 @@ def start(self): """Perform the App's functions as configured""" - super(NBExtensionApp, self).start() + super().start() # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. diff -Nru jupyter-notebook-6.1.4/notebook/notebookapp.py jupyter-notebook-6.2.0/notebook/notebookapp.py --- jupyter-notebook-6.1.4/notebook/notebookapp.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/notebookapp.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,16 +1,14 @@ -# coding: utf-8 """A tornado based Jupyter notebook server.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import absolute_import, print_function - import notebook import asyncio import binascii import datetime import errno +import functools import gettext import hashlib import hmac @@ -190,7 +188,7 @@ if settings['autoreload']: log.info('Autoreload enabled: the webapp will restart when any Python src file changes.') - super(NotebookWebApplication, self).__init__(handlers, **settings) + super().__init__(handlers, **settings) def init_settings(self, jupyter_app, kernel_manager, contents_manager, session_manager, kernel_spec_manager, @@ -250,9 +248,12 @@ # collapse $HOME to ~ root_dir = '~' + root_dir[len(home):] + # Use the NotebookApp logger and its formatting for tornado request logging. + log_function = functools.partial( + log_request, log=log, log_json=jupyter_app.log_json) settings = dict( # basics - log_function=log_request, + log_function=log_function, base_url=base_url, default_url=default_url, template_path=template_path, @@ -282,6 +283,7 @@ disable_check_xsrf=jupyter_app.disable_check_xsrf, allow_remote_access=jupyter_app.allow_remote_access, local_hostnames=jupyter_app.local_hostnames, + authenticate_prometheus=jupyter_app.authenticate_prometheus, # managers kernel_manager=kernel_manager, @@ -511,7 +513,7 @@ help="UNIX socket of the server to be killed.") def parse_command_line(self, argv=None): - super(NbserverStopApp, self).parse_command_line(argv) + super().parse_command_line(argv) if self.extra_args: try: self.port = int(self.extra_args[0]) @@ -704,6 +706,43 @@ _log_formatter_cls = LogFormatter + _json_logging_import_error_logged = False + + log_json = Bool(False, config=True, + help=_('Set to True to enable JSON formatted logs. ' + 'Run "pip install notebook[json-logging]" to install the ' + 'required dependent packages. Can also be set using the ' + 'environment variable JUPYTER_ENABLE_JSON_LOGGING=true.') + ) + + @default('log_json') + def _default_log_json(self): + """Get the log_json value from the environment.""" + return os.getenv('JUPYTER_ENABLE_JSON_LOGGING', 'false').lower() == 'true' + + @validate('log_json') + def _validate_log_json(self, proposal): + # If log_json=True, see if the json_logging package can be imported and + # override _log_formatter_cls if so. + value = proposal['value'] + if value: + try: + import json_logging + self.log.debug('initializing json logging') + json_logging.init_non_web(enable_json=True) + self._log_formatter_cls = json_logging.JSONLogFormatter + except ImportError: + # If configured for json logs and we can't do it, log a hint. + # Only log the error once though. + if not self._json_logging_import_error_logged: + self.log.warning( + 'Unable to use json logging due to missing packages. ' + 'Run "pip install notebook[json-logging]" to fix.' + ) + self._json_logging_import_error_logged = True + value = False + return value + @default('log_level') def _default_log_level(self): return logging.INFO @@ -1513,6 +1552,13 @@ is not available. """)) + authenticate_prometheus = Bool( + True, + help="""" + Require authentication to access prometheus metrics. + """ + ).tag(config=True) + # Since use of terminals is also a function of whether the terminado package is # available, this variable holds the "final indication" of whether terminal functionality # should be considered (particularly during shutdown/cleanup). It is enabled only @@ -1523,7 +1569,7 @@ terminals_available = False def parse_command_line(self, argv=None): - super(NotebookApp, self).parse_command_line(argv) + super().parse_command_line(argv) if self.extra_args: arg0 = self.extra_args[0] @@ -1962,6 +2008,8 @@ # ensure css, js are correct, which are required for pages to function mimetypes.add_type('text/css', '.css') mimetypes.add_type('application/javascript', '.js') + # for python <3.8 + mimetypes.add_type('application/wasm', '.wasm') def shutdown_no_activity(self): """Shutdown server on timeout when there are no kernels or terminals.""" @@ -1993,7 +2041,7 @@ def _init_asyncio_patch(self): """set default asyncio policy to be compatible with tornado - Tornado 6 (at least) is not compatible with the default + Tornado <6.1 is not compatible with the default asyncio implementation on Windows Pick the older SelectorEventLoopPolicy on Windows @@ -2006,7 +2054,7 @@ FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): import asyncio try: from asyncio import ( @@ -2026,7 +2074,7 @@ def initialize(self, argv=None): self._init_asyncio_patch() - super(NotebookApp, self).initialize(argv) + super().initialize(argv) self.init_logging() if self._dispatching: return @@ -2192,7 +2240,7 @@ This method takes no arguments so all configuration and initialization must be done prior to calling this method.""" - super(NotebookApp, self).start() + super().start() if not self.allow_root: # check if we are running as root, and abort if it's not allowed diff -Nru jupyter-notebook-6.1.4/notebook/serverextensions.py jupyter-notebook-6.2.0/notebook/serverextensions.py --- jupyter-notebook-6.1.4/notebook/serverextensions.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/serverextensions.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,11 +1,8 @@ -# coding: utf-8 """Utilities for installing server extensions for the notebook""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import importlib import sys @@ -291,7 +288,7 @@ def start(self): """Perform the App's actions as configured""" - super(ServerExtensionApp, self).start() + super().start() # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. diff -Nru jupyter-notebook-6.1.4/notebook/services/config/tests/test_config_api.py jupyter-notebook-6.2.0/notebook/services/config/tests/test_config_api.py --- jupyter-notebook-6.1.4/notebook/services/config/tests/test_config_api.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/config/tests/test_config_api.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Test the config webservice API.""" import json diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/fileio.py jupyter-notebook-6.2.0/notebook/services/contents/fileio.py --- jupyter-notebook-6.1.4/notebook/services/contents/fileio.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/fileio.py 2021-01-13 16:30:49.000000000 +0000 @@ -183,7 +183,7 @@ Classes using this mixin must provide the following attributes: root_dir : unicode - A directory against against which API-style paths are to be resolved. + A directory against which API-style paths are to be resolved. log : logging.Logger """ diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/filemanager.py jupyter-notebook-6.2.0/notebook/services/contents/filemanager.py --- jupyter-notebook-6.1.4/notebook/services/contents/filemanager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/filemanager.py 2021-01-13 16:30:49.000000000 +0000 @@ -15,6 +15,7 @@ import nbformat from send2trash import send2trash +from send2trash.exceptions import TrashPermissionError from tornado import web from .filecheckpoints import FileCheckpoints @@ -512,17 +513,6 @@ if not os.path.exists(os_path): raise web.HTTPError(404, u'File or directory does not exist: %s' % os_path) - def _check_trash(os_path): - if sys.platform in {'win32', 'darwin'}: - return True - - # It's a bit more nuanced than this, but until we can better - # distinguish errors from send2trash, assume that we can only trash - # files on the same partition as the home directory. - file_dev = os.stat(os_path).st_dev - home_dev = os.stat(os.path.expanduser('~')).st_dev - return file_dev == home_dev - def is_non_empty_dir(os_path): if os.path.isdir(os_path): # A directory containing only leftover checkpoints is @@ -538,16 +528,12 @@ # send2trash can really delete files on Windows, so disallow # deleting non-empty files. See Github issue 3631. raise web.HTTPError(400, u'Directory %s not empty' % os_path) - if _check_trash(os_path): + try: self.log.debug("Sending %s to trash", os_path) - # Looking at the code in send2trash, I don't think the errors it - # raises let us distinguish permission errors from other errors in - # code. So for now, just let them all get logged as server errors. send2trash(os_path) return - else: - self.log.warning("Skipping trash for %s, on different device " - "to home directory", os_path) + except TrashPermissionError as e: + self.log.warning("Skipping trash for %s, %s", os_path, e) if os.path.isdir(os_path): # Don't permanently delete non-empty directories. diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/largefilemanager.py jupyter-notebook-6.2.0/notebook/services/contents/largefilemanager.py --- jupyter-notebook-6.1.4/notebook/services/contents/largefilemanager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/largefilemanager.py 2021-01-13 16:30:49.000000000 +0000 @@ -27,7 +27,7 @@ if chunk == 1: self.log.debug("Saving %s", os_path) self.run_pre_save_hook(model=model, path=path) - super(LargeFileManager, self)._save_file(os_path, model['content'], model.get('format')) + super()._save_file(os_path, model['content'], model.get('format')) else: self._save_large_file(os_path, model['content'], model.get('format')) except web.HTTPError: @@ -43,7 +43,7 @@ self.run_post_save_hook(model=model, os_path=os_path) return model else: - return super(LargeFileManager, self).save(model, path) + return super().save(model, path) def _save_large_file(self, os_path, content, format): """Save content of a generic file.""" diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/tests/test_contents_api.py jupyter-notebook-6.2.0/notebook/services/contents/tests/test_contents_api.py --- jupyter-notebook-6.1.4/notebook/services/contents/tests/test_contents_api.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/tests/test_contents_api.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Test the contents webservice API.""" from contextlib import contextmanager @@ -13,6 +12,8 @@ pjoin = os.path.join import requests +from send2trash import send2trash +from send2trash.exceptions import TrashPermissionError from ..filecheckpoints import GenericFileCheckpoints @@ -198,6 +199,14 @@ def isdir(self, api_path): return os.path.isdir(self.to_os_path(api_path)) + def can_send2trash(self, api_path): + """Send a path to trash, if possible. Return success.""" + try: + send2trash(self.to_os_path(api_path)) + return True + except TrashPermissionError as e: + return False + def setUp(self): for d in (self.dirs + self.hidden_dirs): self.make_dir(d) @@ -527,7 +536,13 @@ if sys.platform == 'win32': self.skipTest("Disabled deleting non-empty dirs on Windows") # Test that non empty directory can be deleted - self.api.delete(u'å b') + try: + self.api.delete(u'å b') + except requests.HTTPError as e: + if e.response.status_code == 400: + if not self.can_send2trash(u'å b'): + self.skipTest("Dir can't be sent to trash") + raise # Check if directory has actually been deleted with assert_http_error(404): self.api.list(u'å b') diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/tests/test_fileio.py jupyter-notebook-6.2.0/notebook/services/contents/tests/test_fileio.py --- jupyter-notebook-6.1.4/notebook/services/contents/tests/test_fileio.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/tests/test_fileio.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# encoding: utf-8 """Tests for file IO""" # Copyright (c) Jupyter Development Team. @@ -6,11 +5,11 @@ import io as stdlib_io import os.path +import unittest +import pytest import stat +import sys -import nose.tools as nt - -from ipython_genutils.testing.decorators import skip_win32 from ..fileio import atomic_writing from ipython_genutils.tempdir import TemporaryDirectory @@ -39,24 +38,24 @@ # OSError: The user lacks the privilege (Windows) have_symlink = False - with nt.assert_raises(CustomExc): + with pytest.raises(CustomExc): with atomic_writing(f1) as f: f.write(u'Failing write') raise CustomExc # Because of the exception, the file should not have been modified with stdlib_io.open(f1, 'r') as f: - nt.assert_equal(f.read(), u'Before') + assert f.read() == u'Before' with atomic_writing(f1) as f: f.write(u'Overwritten') with stdlib_io.open(f1, 'r') as f: - nt.assert_equal(f.read(), u'Overwritten') + assert f.read() == u'Overwritten' if os.name != 'nt': mode = stat.S_IMODE(os.stat(f1).st_mode) - nt.assert_equal(mode, orig_mode) + assert mode == orig_mode if have_symlink: # Check that writing over a file preserves a symlink @@ -64,33 +63,35 @@ f.write(u'written from symlink') with stdlib_io.open(f1, 'r') as f: - nt.assert_equal(f.read(), u'written from symlink') + assert f.read() == u'written from symlink' -def _save_umask(): - global umask - umask = os.umask(0) - os.umask(umask) - -def _restore_umask(): - os.umask(umask) - -@skip_win32 -@nt.with_setup(_save_umask, _restore_umask) -def test_atomic_writing_umask(): - with TemporaryDirectory() as td: - os.umask(0o022) - f1 = os.path.join(td, '1') - with atomic_writing(f1) as f: - f.write(u'1') - mode = stat.S_IMODE(os.stat(f1).st_mode) - nt.assert_equal(mode, 0o644, '{:o} != 644'.format(mode)) - - os.umask(0o057) - f2 = os.path.join(td, '2') - with atomic_writing(f2) as f: - f.write(u'2') - mode = stat.S_IMODE(os.stat(f2).st_mode) - nt.assert_equal(mode, 0o620, '{:o} != 620'.format(mode)) +class TestWithSetUmask(unittest.TestCase): + def setUp(self): + # save umask + global umask + umask = os.umask(0) + os.umask(umask) + + def tearDown(self): + # restore umask + os.umask(umask) + + @pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows") + def test_atomic_writing_umask(self): + with TemporaryDirectory() as td: + os.umask(0o022) + f1 = os.path.join(td, '1') + with atomic_writing(f1) as f: + f.write(u'1') + mode = stat.S_IMODE(os.stat(f1).st_mode) + assert mode == 0o644 + + os.umask(0o057) + f2 = os.path.join(td, '2') + with atomic_writing(f2) as f: + f.write(u'2') + mode = stat.S_IMODE(os.stat(f2).st_mode) + assert mode == 0o620 def test_atomic_writing_newlines(): @@ -106,21 +107,21 @@ f.write(lf) with stdlib_io.open(path, 'r', newline='') as f: read = f.read() - nt.assert_equal(read, plat) + assert read == plat # test newline=LF with stdlib_io.open(path, 'w', newline='\n') as f: f.write(lf) with stdlib_io.open(path, 'r', newline='') as f: read = f.read() - nt.assert_equal(read, lf) + assert read == lf # test newline=CRLF with atomic_writing(path, newline='\r\n') as f: f.write(lf) with stdlib_io.open(path, 'r', newline='') as f: read = f.read() - nt.assert_equal(read, crlf) + assert read == crlf # test newline=no convert text = u'crlf\r\ncr\rlf\n' @@ -128,4 +129,4 @@ f.write(text) with stdlib_io.open(path, 'r', newline='') as f: read = f.read() - nt.assert_equal(read, text) + assert read == text diff -Nru jupyter-notebook-6.1.4/notebook/services/contents/tests/test_manager.py jupyter-notebook-6.2.0/notebook/services/contents/tests/test_manager.py --- jupyter-notebook-6.1.4/notebook/services/contents/tests/test_manager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/contents/tests/test_manager.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,6 +1,4 @@ -# coding: utf-8 """Tests for the notebook manager.""" -from __future__ import print_function import os import sys @@ -8,16 +6,14 @@ from contextlib import contextmanager from itertools import combinations -from nose import SkipTest from tornado.web import HTTPError -from unittest import TestCase +from unittest import TestCase, skipIf from tempfile import NamedTemporaryFile from nbformat import v4 as nbformat from ipython_genutils.tempdir import TemporaryDirectory from traitlets import TraitError -from ipython_genutils.testing import decorators as dec from ..filemanager import FileContentsManager @@ -108,7 +104,6 @@ self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name)) self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name)) - @dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3) def test_bad_symlink(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -129,7 +124,7 @@ # broken symlinks should still be shown in the contents manager self.assertTrue('bad symlink' in contents) - @dec.skipif(sys.platform == 'win32') + @skipIf(sys.platform == 'win32', "will not run on windows") def test_recursive_symlink(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -149,7 +144,6 @@ # recursive symlinks should not be shown in the contents manager self.assertNotIn('recursive', contents) - @dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3) def test_good_symlink(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -169,13 +163,10 @@ [symlink_model, file_model], ) - def test_403(self): - if hasattr(os, 'getuid'): - if os.getuid() == 0: - raise SkipTest("Can't test permissions as root") - if sys.platform.startswith('win'): - raise SkipTest("Can't test permissions on Windows") + @skipIf(hasattr(os, 'getuid') and os.getuid() == 0, "Can't test permissions as root") + @skipIf(sys.platform.startswith('win'), "Can't test permissions on Windows") + def test_403(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) model = cm.new_untitled(type='file') diff -Nru jupyter-notebook-6.1.4/notebook/services/kernels/handlers.py jupyter-notebook-6.2.0/notebook/services/kernels/handlers.py --- jupyter-notebook-6.1.4/notebook/services/kernels/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/kernels/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -123,11 +123,125 @@ def create_stream(self): km = self.kernel_manager identity = self.session.bsession - for channel in ('shell', 'control', 'iopub', 'stdin'): - meth = getattr(km, 'connect_' + channel) + for channel in ("iopub", "shell", "control", "stdin"): + meth = getattr(km, "connect_" + channel) self.channels[channel] = stream = meth(self.kernel_id, identity=identity) stream.channel = channel + def nudge(self): + """Nudge the zmq connections with kernel_info_requests + + Returns a Future that will resolve when we have received + a shell reply and at least one iopub message, + ensuring that zmq subscriptions are established, + sockets are fully connected, and kernel is responsive. + + Keeps retrying kernel_info_request until these are both received. + """ + kernel = self.kernel_manager.get_kernel(self.kernel_id) + + # Do not nudge busy kernels as kernel info requests sent to shell are + # queued behind execution requests. + # nudging in this case would cause a potentially very long wait + # before connections are opened, + # plus it is *very* unlikely that a busy kernel will not finish + # establishing its zmq subscriptions before processing the next request. + if getattr(kernel, "execution_state") == "busy": + self.log.debug("Nudge: not nudging busy kernel %s", self.kernel_id) + f = Future() + f.set_result(None) + return f + + # Use a transient shell channel to prevent leaking + # shell responses to the front-end. + shell_channel = kernel.connect_shell() + # The IOPub used by the client, whose subscriptions we are verifying. + iopub_channel = self.channels["iopub"] + + info_future = Future() + iopub_future = Future() + both_done = gen.multi([info_future, iopub_future]) + + def finish(f=None): + """Ensure all futures are resolved + + which in turn triggers cleanup + """ + for f in (info_future, iopub_future): + if not f.done(): + f.set_result(None) + + def cleanup(f=None): + """Common cleanup""" + loop.remove_timeout(nudge_handle) + iopub_channel.stop_on_recv() + if not shell_channel.closed(): + shell_channel.close() + + # trigger cleanup when both message futures are resolved + both_done.add_done_callback(cleanup) + + def on_shell_reply(msg): + self.log.debug("Nudge: shell info reply received: %s", self.kernel_id) + if not info_future.done(): + self.log.debug("Nudge: resolving shell future: %s", self.kernel_id) + info_future.set_result(None) + + def on_iopub(msg): + self.log.debug("Nudge: IOPub received: %s", self.kernel_id) + if not iopub_future.done(): + iopub_channel.stop_on_recv() + self.log.debug("Nudge: resolving iopub future: %s", self.kernel_id) + iopub_future.set_result(None) + + iopub_channel.on_recv(on_iopub) + shell_channel.on_recv(on_shell_reply) + loop = IOLoop.current() + + # Nudge the kernel with kernel info requests until we get an IOPub message + def nudge(count): + count += 1 + + # NOTE: this close check appears to never be True during on_open, + # even when the peer has closed the connection + if self.ws_connection is None or self.ws_connection.is_closing(): + self.log.debug( + "Nudge: cancelling on closed websocket: %s", self.kernel_id + ) + finish() + return + + # check for stopped kernel + if self.kernel_id not in self.kernel_manager: + self.log.debug( + "Nudge: cancelling on stopped kernel: %s", self.kernel_id + ) + finish() + return + + # check for closed zmq socket + if shell_channel.closed(): + self.log.debug( + "Nudge: cancelling on closed zmq socket: %s", self.kernel_id + ) + finish() + return + + if not both_done.done(): + log = self.log.warning if count % 10 == 0 else self.log.debug + log("Nudge: attempt %s on kernel %s" % (count, self.kernel_id)) + self.session.send(shell_channel, "kernel_info_request") + nonlocal nudge_handle + nudge_handle = loop.call_later(0.5, nudge, count) + + nudge_handle = loop.call_later(0, nudge, count=0) + + # resolve with a timeout if we get no response + future = gen.with_timeout(loop.time() + self.kernel_info_timeout, both_done) + # ensure we have no dangling resources or unresolved Futures in case of timeout + future.add_done_callback(finish) + return future + def request_kernel_info(self): """send a request for kernel_info""" km = self.kernel_manager @@ -150,7 +264,7 @@ self.log.debug("Waiting for pending kernel_info request") future.add_done_callback(lambda f: self._finish_kernel_info(f.result())) return self._kernel_info_future - + def _handle_kernel_info_reply(self, msg): """process the kernel_info_reply @@ -190,7 +304,7 @@ self._kernel_info_future.set_result(info) def initialize(self): - super(ZMQChannelsHandler, self).initialize() + super().initialize() self.zmq_stream = None self.channels = {} self.kernel_id = None @@ -212,7 +326,7 @@ @gen.coroutine def pre_get(self): # authenticate first - super(ZMQChannelsHandler, self).pre_get() + super().pre_get() # check session collision: yield self._register_session() # then request kernel info, waiting up to a certain time before giving up. @@ -236,7 +350,7 @@ @gen.coroutine def get(self, kernel_id): self.kernel_id = cast_unicode(kernel_id, 'ascii') - yield super(ZMQChannelsHandler, self).get(kernel_id=kernel_id) + yield super().get(kernel_id=kernel_id) @gen.coroutine def _register_session(self): @@ -254,7 +368,7 @@ self._open_sessions[self.session_key] = self def open(self, kernel_id): - super(ZMQChannelsHandler, self).open() + super().open() km = self.kernel_manager km.notify_connect(kernel_id) @@ -263,15 +377,21 @@ if buffer_info and buffer_info['session_key'] == self.session_key: self.log.info("Restoring connection for %s", self.session_key) self.channels = buffer_info['channels'] - replay_buffer = buffer_info['buffer'] - if replay_buffer: - self.log.info("Replaying %s buffered messages", len(replay_buffer)) - for channel, msg_list in replay_buffer: - stream = self.channels[channel] - self._on_zmq_reply(stream, msg_list) + connected = self.nudge() + + def replay(value): + replay_buffer = buffer_info['buffer'] + if replay_buffer: + self.log.info("Replaying %s buffered messages", len(replay_buffer)) + for channel, msg_list in replay_buffer: + stream = self.channels[channel] + self._on_zmq_reply(stream, msg_list) + + connected.add_done_callback(replay) else: try: self.create_stream() + connected = self.nudge() except web.HTTPError as e: self.log.error("Error opening stream: %s", e) # WebSockets don't response to traditional error codes so we @@ -285,8 +405,13 @@ km.add_restart_callback(self.kernel_id, self.on_kernel_restarted) km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead') - for channel, stream in self.channels.items(): - stream.on_recv_stream(self._on_zmq_reply) + def subscribe(value): + for channel, stream in self.channels.items(): + stream.on_recv_stream(self._on_zmq_reply) + + connected.add_done_callback(subscribe) + + return connected def on_message(self, msg): if not self.channels: @@ -419,10 +544,10 @@ self._iopub_window_byte_count -= byte_count self._iopub_window_byte_queue.pop(-1) return - super(ZMQChannelsHandler, self)._on_zmq_reply(stream, msg) + super()._on_zmq_reply(stream, msg) def close(self): - super(ZMQChannelsHandler, self).close() + super().close() return self._close_future def on_close(self): diff -Nru jupyter-notebook-6.1.4/notebook/services/kernels/kernelmanager.py jupyter-notebook-6.2.0/notebook/services/kernels/kernelmanager.py --- jupyter-notebook-6.1.4/notebook/services/kernels/kernelmanager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/kernels/kernelmanager.py 2021-01-13 16:30:49.000000000 +0000 @@ -294,7 +294,6 @@ kernel._activity_stream.close() kernel._activity_stream = None self.stop_buffering(kernel_id) - self._kernel_connections.pop(kernel_id, None) # Decrease the metric of number of kernels # running for the relevant kernel type by 1 @@ -302,7 +301,12 @@ type=self._kernels[kernel_id].kernel_name ).dec() - return self.pinned_superclass.shutdown_kernel(self, kernel_id, now=now, restart=restart) + self.pinned_superclass.shutdown_kernel(self, kernel_id, now=now, restart=restart) + # Unlike its async sibling method in AsyncMappingKernelManager, removing the kernel_id + # from the connections dictionary isn't as problematic before the shutdown since the + # method is synchronous. However, we'll keep the relative call orders the same from + # a maintenance perspective. + self._kernel_connections.pop(kernel_id, None) async def restart_kernel(self, kernel_id, now=False): """Restart a kernel by kernel_id""" @@ -376,8 +380,11 @@ kernels = [] kernel_ids = self.pinned_superclass.list_kernel_ids(self) for kernel_id in kernel_ids: - model = self.kernel_model(kernel_id) - kernels.append(model) + try: + model = self.kernel_model(kernel_id) + kernels.append(model) + except (web.HTTPError, KeyError): + pass # Probably due to a (now) non-existent kernel, continue building the list return kernels # override _check_kernel_id to raise 404 instead of KeyError @@ -498,7 +505,6 @@ kernel._activity_stream.close() kernel._activity_stream = None self.stop_buffering(kernel_id) - self._kernel_connections.pop(kernel_id, None) # Decrease the metric of number of kernels # running for the relevant kernel type by 1 @@ -506,4 +512,9 @@ type=self._kernels[kernel_id].kernel_name ).dec() - return await self.pinned_superclass.shutdown_kernel(self, kernel_id, now=now, restart=restart) + await self.pinned_superclass.shutdown_kernel(self, kernel_id, now=now, restart=restart) + # Remove kernel_id from the connections dictionary only after kernel has been shutdown, + # otherwise a race condition can occur since the shutdown may take a while - allowing + # list/fetch kernel operations to access _kernel_connections for a non-existent key + # (kernel_id) while "awaiting" the result of the shutdown. + self._kernel_connections.pop(kernel_id, None) diff -Nru jupyter-notebook-6.1.4/notebook/services/kernelspecs/handlers.py jupyter-notebook-6.2.0/notebook/services/kernelspecs/handlers.py --- jupyter-notebook-6.1.4/notebook/services/kernelspecs/handlers.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/kernelspecs/handlers.py 2021-01-13 16:30:49.000000000 +0000 @@ -27,7 +27,6 @@ } # Add resource files if they exist - resource_dir = resource_dir for resource in ['kernel.js', 'kernel.css']: if os.path.exists(pjoin(resource_dir, resource)): d['resources'][resource] = url_path_join( diff -Nru jupyter-notebook-6.1.4/notebook/services/kernelspecs/tests/test_kernelspecs_api.py jupyter-notebook-6.2.0/notebook/services/kernelspecs/tests/test_kernelspecs_api.py --- jupyter-notebook-6.1.4/notebook/services/kernelspecs/tests/test_kernelspecs_api.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/kernelspecs/tests/test_kernelspecs_api.py 2021-01-13 16:30:49.000000000 +0000 @@ -1,4 +1,3 @@ -# coding: utf-8 """Test the kernel specs webservice API.""" import errno diff -Nru jupyter-notebook-6.1.4/notebook/services/sessions/tests/test_sessionmanager.py jupyter-notebook-6.2.0/notebook/services/sessions/tests/test_sessionmanager.py --- jupyter-notebook-6.1.4/notebook/services/sessions/tests/test_sessionmanager.py 2020-09-08 17:47:36.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/services/sessions/tests/test_sessionmanager.py 2021-01-13 16:30:49.000000000 +0000 @@ -21,7 +21,7 @@ class DummyMKM(MappingKernelManager): """MappingKernelManager interface that doesn't start kernels, for testing""" def __init__(self, *args, **kwargs): - super(DummyMKM, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.id_letters = iter(u'ABCDEFGHIJK') def _new_id(self): diff -Nru jupyter-notebook-6.1.4/notebook/static/base/js/markdown.js jupyter-notebook-6.2.0/notebook/static/base/js/markdown.js --- jupyter-notebook-6.1.4/notebook/static/base/js/markdown.js 1970-01-01 00:00:00.000000000 +0000 +++ jupyter-notebook-6.2.0/notebook/static/base/js/markdown.js 2021-01-13 16:30:49.000000000 +0000 @@ -0,0 +1,117 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'jquery', + 'base/js/utils', + 'base/js/mathjaxutils', + 'base/js/security', + 'components/marked/lib/marked', + 'codemirror/lib/codemirror', +], function($, utils, mathjaxutils, security, marked, CodeMirror){ + "use strict"; + + marked.setOptions({ + gfm : true, + tables: true, + langPrefix: "cm-s-ipython language-", + highlight: function(code, lang, callback) { + if (!lang) { + // no language, no highlight + if (callback) { + callback(null, code); + return; + } else { + return code; + } + } + utils.requireCodeMirrorMode(lang, function (spec) { + var el = document.createElement("div"); + var mode = CodeMirror.getMode({}, spec); + if (!mode) { + console.log("No CodeMirror mode: " + lang); + callback(null, code); + return; + } + try { + CodeMirror.runMode(code, spec, el); + callback(null, el.innerHTML); + } catch (err) { + console.log("Failed to highlight " + lang + " code", err); + callback(err, code); + } + }, function (err) { + console.log("No CodeMirror mode: " + lang); + console.log("Require CodeMirror mode error: " + err); + callback(null, code); + }); + } + }); + + var mathjax_init_done = false; + function ensure_mathjax_init() { + if(!mathjax_init_done) { + mathjax_init_done = true; + mathjaxutils.init(); + } + } + + function render(markdown, options, callback) { + /** + * Find a readme in the current directory. Look for files with + * a name like 'readme.md' (case insensitive) or similar and + * mimetype 'text/markdown'. + * + * @param markdown: the markdown text to parse + * @param options + * Object with parameters: + * with_math: the markdown can contain mathematics + * clean_tables: prevent default inline styles for table cells + * sanitize: remove dangerous html (like