diff --git a/.circleci/config.yml b/.circleci/config.yml index 6e1d5ace29f849c8c0bea739ed14717bebdd641d..a9a8eee9db6a680fb4a0d3ac5a7e58678b03c827 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: # Make sure tests build in CircleCI (so we can preview w/ SSH if we wish) - run: name: Run pytest - command: pytest --ignore=tests/test_pdf.py + command: pytest -m 'not requires_chrome and not requires_tex' pdfhtml: docker: @@ -51,7 +51,7 @@ jobs: - run: name: Run pytest for pdfhtml - command: pytest tests/test_pdf.py::test_pdfhtml + command: pytest -m 'requires_chrome' # Build a PDF of the book to view as an artifact - run: @@ -94,7 +94,7 @@ jobs: - run: name: Run pytest for pdflatex - command: pytest tests/test_pdf.py::test_pdflatex + command: pytest -m 'requires_tex' # Build quantecon-mini-example project as a test case # TODO: revert this to building docs/ when we've added support for svg, gif, etc diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5d9d8e63217a2742139e3027dc61295cad8af3d..3690246bbb8eaf216f2ff9c33ea34bd5f7e3dc85 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 - uses: pre-commit/action@v2.0.0 @@ -34,12 +34,13 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install Python dependencies run: | python -m pip install --upgrade pip + pip install wheel pip install -e .[testing] - name: Install Headless Chrome dependencies @@ -56,7 +57,6 @@ jobs: texlive-latex-extra \ latexmk - # Tests - name: Run pytest run: | pytest --durations=10 --cov=jupyter_book --cov-report=xml --cov-report=term-missing @@ -79,16 +79,22 @@ jobs: strategy: matrix: python-version: [3.6, 3.7] + sphinx: [">=3,<4"] + include: + - python-version: 3.8 + sphinx: ">=2,<3" steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install Python dependencies run: | python -m pip install --upgrade pip + pip install wheel + pip install "sphinx${{ matrix.sphinx }}" pip install -e .[testing] - name: Install Headless Chrome dependencies @@ -96,10 +102,33 @@ jobs: sudo apt-get update sudo apt-get install -yq $(cat .github/workflows/pyppeteer_reqs.txt) - # Tests - name: Run pytest + run: pytest --durations=10 -m 'not requires_tex' + + windows: + + name: Tests on Windows + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.7 + uses: actions/setup-python@v2 + with: + python-version: 3.7 + - uses: actions/cache@v2 + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install Python dependencies run: | - pytest --durations=10 --ignore=tests/test_pdf.py + python -m pip install --upgrade pip + pip install wheel + pip install --upgrade-strategy eager -e .[testing] + - name: Run pytest + run: pytest --durations=10 -m 'not requires_chrome and not requires_tex' --jb-tempdir local_path # Build the book on OSX to make sure that building the docs works there too build-book-osx: @@ -109,22 +138,29 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python 3.7 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.7 - - name: Install dependencies + - uses: actions/cache@v2 + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install -e .[sphinx] + pip install wheel + pip install --upgrade-strategy eager -e .[sphinx] - name: Build the book run: | - jb build docs/ + jb build -W -n --keep-going --builder html docs/ publish: name: Publish to PyPi - needs: [pre-commit, tests, test-with-cov] + needs: [pre-commit, tests, test-with-cov, build-book-osx] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 987363c25bbbea0f85a2666822f3df9ff73d9d03..23c6386e90e287be29dd365402f49f3a8b4db61c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,126 @@ # Change Log +## v0.8.0 2020-09-01 + +([full changelog](https://github.com/executablebooks/jupyter-book/compare/v0.7.5...v0.8.0)) + +> **You spoke, we listened!** + +Version 0.8.0 of Jupyter Book, incorporates a bottom-up refresh of the entire Executable Books Project (EBP) stack, +with tonnes of bugs fixes, improvements and new features 🎉 + +The documentation describes all this new functionality in full detail, but below we shall try to outline the major changes and additions. + +### Breaking â€¼ï¸ + +The `jupyter-book`/`jb` executable should work almost identically to in v0.7.5, and all existing books will generally build as before (open an issue if not!). + +The key change is that `jupyter-book page` is no longer available. +Instead you can now pass a single file path to `jupyter-book build`, as opposed to a directory, and it will build your single page (thanks to [@AakashGfude](https://github.com/AakashGfude)). +See [Build a standalone page](docs/start/build.md). + +Another thing to note, is that if you are using "bare" LaTeX math in your documentation, +then this will only render if you activate in your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +See [the math documentation](docs/content/math.md) for details. + +### New and Improved ✨👌 + +Jupyter Book v0.8 incorporates all the great new features available by moving from: + +* MyST-Parser v0.9 to v0.12 (see its [CHANGELOG.md](https://github.com/executablebooks/MyST-Parser/blob/master/CHANGELOG.md)) +* MyST-NB v0.8 to v0.10 (see its [CHANGELOG.md](https://github.com/executablebooks/MyST-NB/blob/master/CHANGELOG.md)) +* This also enabled, Sphinx v2 to v3 + +Here's the headlines: + +Windows support +: Continuous Integration (CI) testing is now run against Windows OS throughout the EBP stack. + The fixes this entailed, mean that Jupyter Book can now be run on Windows with minimal issue (see [Working on Windows](docs/advanced/advanced.md)) + +Extended "Markdown friendly" syntaxes +: MyST Markdown directives offer a high degree of extensibility, to add all the features we might need to create a scientific document. + However, they are not (yet) very well integrated with external editors, like the Jupyter Notebook interface. + Extended syntax parsing to the rescue! + + By enabling in your `_config.yaml`: + + ```yaml + parse: + myst_extended_syntax: true + ``` + + You can access to a number of *Markdown friendly* syntaxes, which extend the [CommonMark specification](https://commonmark.org/): + + * `:::` fenced admonitions render Markdown as standard (see [new style admonitions](docs/content/content-blocks.md)). + * HTML images are correctly handled, allowing for control of size and style attributes, + and Markdown style figures extend this for captions and referencing (see [images and figures](docs/content/figures.md)). + * Definition lists are what you see here and provide a simple format for writing term/definition blocks (see [definition lists](docs/content/content-blocks.md)). + * LaTeX math is now intrinsically supported, + meaning that it will rendered correctly in both HTML and LaTeX/PDF outputs (see [math and equations](docs/content/math.md)). + +Custom Notebook Formats +: Want to write your notebooks as RMarkdown, Python files, ....? + Jupyter Book now supports linking any file extension to a custom conversion function, + run before notebook execution and parsing. + See the [custom notebook formats and Jupytext](docs/file-types/jupytext.Rmd) documentation, which itself is written in RMarkdown! + +Execution Configuration +: Execution and caching of notebook outputs has been improved, to make it more consistent across `auto` and `cache` methods (`cache` execution is now also run in the notebook directory) and provide numerous configuration options, including: + + * Running the execution in the local directory or in a temporary directory. + * Setting the execution timeout limit, at a global or notebook level. + * Allowing errors across all notebooks, or at a notebook or cell level. + * Removing stderr/stdout outputs or logging warnings when they are encountered + * A directive for displaying execution statistics for all notebooks in the book (status, run time, etc) + + See the [execution documentation](docs/content/execute.md) for more details. + +Code Output Formatting +: More cell outputs are handled, including Markdown and ANSI outputs, + and you can use cell metadata to set image size, style, captions and references. + See [formatting code outputs](docs/content/code-outputs.md). + +Build options and error reporting +: The `jupyter-book build` command includes additional options/flags for controlling the build behaviour, + such as verbose (`-v`), quiet (`-q`) and nitpick mode for checking references (`-n`). + See the [command-line interface documentation](docs/reference/cli.md) for more details. + +sphinx-panels integration +: The [sphinx-panels](https://sphinx-panels.readthedocs.io) package is not provided directly in the Jupyter Book distribution. + This adds additional functionality for creating web based elements, such as gridded panels and dropdown boxes. + See the [Panels and Dropdowns](docs/content/content-blocks.md) section for details. + +### Fixes 🛠+ +Among the numerous fixes: + +* Code cell syntax highlighting now works for all Jupyter kernels. +* User configuration is now recursively merged with the default configuration, and no longer overwrites an entire nested section. + You can also use `jb config sphinx mybookname/` to inspect the sphinx `conf.py` which will be parsed to the builder. + +### More to come 👀 + +We have many more improvements planned, check back in this change log for future improvements. + +Also please continue to provide us feedback on what you would like to see next. +See our [voting for new features](https://executablebooks.org/en/latest/feature-vote.html) page. + ## v0.7.5 2020-08-26 -✨ NEW: This release introduces the new "Comments and Annotations" feature, powered by [sphinx-comments](https://github.com/executablebooks/sphinx-comments). See [this documetation section](https://jupyterbook.org/interactive/comments.html) for further details. +✨ NEW: This release introduces the new "Comments and Annotations" feature, powered by [sphinx-comments](https://github.com/executablebooks/sphinx-comments). See [this documentation section](https://jupyterbook.org/interactive/comments.html) for further details. **Important:** this version also pins the `myst-nb` dependency to v0.8. Previous versions erroneously allow for the new v0.9, which is not yet strictly compatible with jupyter-book (coming very soon!) ## v0.7.0...v0.7.4 -([full changelog](https://github.com/executablebooks/jupyter-book/compare/v0.7.0...v0.7.4)) +([full changelog](https://github.com/executablebooks/jupyter-book/compare/v0.7.5...v0.8.0)) ### Enhancements made @@ -17,28 +128,32 @@ Previous versions erroneously allow for the new v0.9, which is not yet strictly * checking for toc modification time [#772](https://github.com/executablebooks/jupyter-book/pull/772) ([@choldgraf](https://github.com/choldgraf)) * first pass toc directive [#757](https://github.com/executablebooks/jupyter-book/pull/757) ([@choldgraf](https://github.com/choldgraf)) -### Bugs fixed +### Bugs fixed 🛠+ * Fix typo in content-blocks.md documentation [#811](https://github.com/executablebooks/jupyter-book/pull/811) ([@MaxGhenis](https://github.com/MaxGhenis)) -* [BUG] Using relative instead of absolute links [#747](https://github.com/executablebooks/jupyter-book/pull/747) ([@AakashGfude](https://github.com/AakashGfude)) -* 🛠FIX: fixing jupytext install/UI links [#737](https://github.com/executablebooks/jupyter-book/pull/737) ([@chrisjsewell](https://github.com/chrisjsewell)) +* Using relative instead of absolute links [#747](https://github.com/executablebooks/jupyter-book/pull/747) ([@AakashGfude](https://github.com/AakashGfude)) +* Fixing jupytext install/UI links [#737](https://github.com/executablebooks/jupyter-book/pull/737) ([@chrisjsewell](https://github.com/chrisjsewell)) -### Documentation improvements -* 📚 DOC: note about licenses [#806](https://github.com/executablebooks/jupyter-book/pull/806) ([@choldgraf](https://github.com/choldgraf)) -* 📖 DOCS: Fix google analytics instructions [#799](https://github.com/executablebooks/jupyter-book/pull/799) ([@tobydriscoll](https://github.com/tobydriscoll)) +### Documentation improvements 📚 + +* Note about licenses [#806](https://github.com/executablebooks/jupyter-book/pull/806) ([@choldgraf](https://github.com/choldgraf)) +* Fix google analytics instructions [#799](https://github.com/executablebooks/jupyter-book/pull/799) ([@tobydriscoll](https://github.com/tobydriscoll)) * Change book_path to path_to_book [#773](https://github.com/executablebooks/jupyter-book/pull/773) ([@MaxGhenis](https://github.com/MaxGhenis)) * GitHub actions example: note about selective build [#771](https://github.com/executablebooks/jupyter-book/pull/771) ([@consideRatio](https://github.com/consideRatio)) * getting sphinx thebelab to work [#749](https://github.com/executablebooks/jupyter-book/pull/749) ([@choldgraf](https://github.com/choldgraf)) * Link documentation for adding cell tags in Jupyter from "Hide or remove content" documentation section [#734](https://github.com/executablebooks/jupyter-book/pull/734) ([@MaxGhenis](https://github.com/MaxGhenis)) -* typo fix [#731](https://github.com/executablebooks/jupyter-book/pull/731) ([@MaxGhenis](https://github.com/MaxGhenis)) +* Typo fix [#731](https://github.com/executablebooks/jupyter-book/pull/731) ([@MaxGhenis](https://github.com/MaxGhenis)) * reworking interactive docs [#725](https://github.com/executablebooks/jupyter-book/pull/725) ([@choldgraf](https://github.com/choldgraf)) -* [DOC] Add documentation for Google Colab launch buttons [#721](https://github.com/executablebooks/jupyter-book/pull/721) ([@lewtun](https://github.com/lewtun)) -* [DOC] Add note about int eq labels in math directive [#720](https://github.com/executablebooks/jupyter-book/pull/720) ([@najuzilu](https://github.com/najuzilu)) +* Add documentation for Google Colab launch buttons [#721](https://github.com/executablebooks/jupyter-book/pull/721) ([@lewtun](https://github.com/lewtun)) +* Add note about int eq labels in math directive [#720](https://github.com/executablebooks/jupyter-book/pull/720) ([@najuzilu](https://github.com/najuzilu)) ### API Changes + * ✨ NEW: Adding - chapter entries to _toc.yml [#817](https://github.com/executablebooks/jupyter-book/pull/817) ([@choldgraf](https://github.com/choldgraf)) * removing config file numbered sections to use toc file instead [#768](https://github.com/executablebooks/jupyter-book/pull/768) ([@choldgraf](https://github.com/choldgraf)) ### Other merged PRs + * 📚 DOCS: Remove Issue Templates [#849](https://github.com/executablebooks/jupyter-book/pull/849) ([@chrisjsewell](https://github.com/chrisjsewell)) * 📚 DOC: document available bib styles [#845](https://github.com/executablebooks/jupyter-book/pull/845) ([@emdupre](https://github.com/emdupre)) * 🛠FIX: fixing toctree spacing bug [#836](https://github.com/executablebooks/jupyter-book/pull/836) ([@choldgraf](https://github.com/choldgraf)) diff --git a/MANIFEST.in b/MANIFEST.in index 8f9119213c7bca10c53bb7e3f809888a0b36d221..d660ff0da5a1da24afcbdc6e85b01171cb4c3861 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,6 +17,7 @@ exclude .flake8 exclude tox.ini exclude codecov.yml exclude RELEASES.md +exclude conftest.py include LICENSE include CHANGELOG.md @@ -24,4 +25,5 @@ include docs/requirements.txt recursive-include examples * include jupyter_book/default_config.yml +include jupyter_book/config_schema.json recursive-include jupyter_book/book_template * diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..9f7819da41ed921d27e3a89f9498eac8e96bec63 --- /dev/null +++ b/conftest.py @@ -0,0 +1,40 @@ +"""On GH Actions windows-latest, the supplied tmpdir is on a different Drive to the CWD, +and so relative path computations fail. +Therefore, here we allow for the directory to be directly supplied, +via an environmental variable. +""" +import shutil +from pathlib import Path +from uuid import uuid4 + +import pytest + + +def pytest_addoption(parser): + """Define pytest command-line option""" + group = parser.getgroup("jupyter_book") + group.addoption( + "--jb-tempdir", + dest="jb_tempdir", + default=None, + help="Specify a directory in which to create tempdirs", + ) + + +def pytest_report_header(config): + path = "<TEMP>" + if config.getoption("jb_tempdir"): + path = Path(config.getoption("jb_tempdir")).absolute().as_posix() + return [f"JB TEMPDIR: {path}"] + + +@pytest.fixture() +def temp_with_override(pytestconfig, tmpdir): + if pytestconfig.getoption("jb_tempdir"): + path = Path(pytestconfig.getoption("jb_tempdir")).resolve().absolute() + path = path / str(uuid4()) + path.mkdir(parents=True) + yield path + shutil.rmtree(path) + else: + yield Path(tmpdir.dirname) / tmpdir.basename diff --git a/docs/_config.yml b/docs/_config.yml index c6991659de6596944258d7f92d2688a0a24506ee..4b6103f4cdf1a9b6c0a5206a48ab6b7e7841fe29 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -7,9 +7,14 @@ email: choldgraf@berkeley.edu description: >- # this means to ignore newlines until "baseurl:" This is an example book built with Jupyter Books. +exclude_patterns: [file-types/include-rst.rst] + execute: execute_notebooks: cache +parse: + myst_extended_syntax: true + html: favicon: images/favicon.ico google_analytics_id: UA-52617120-7 @@ -32,6 +37,13 @@ launch_buttons: sphinx: config: + nb_custom_formats: + .Rmd: + - jupytext.reads + - fmt: Rmd + # TODO: #917 this path will be the default in sphinx v4 + # mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + # However, it is incompatible with the mathjax config below for macros mathjax_config: TeX: Macros: @@ -39,13 +51,32 @@ sphinx: "floor": ["\\lfloor#1\\rfloor", 1] "bmat": ["\\left[\\begin{array}"] "emat": ["\\end{array}\\right]"] - panels_add_boostrap_css: False intersphinx_mapping: ebp: - "https://executablebooks.org/en/latest/" - null + myst-parser: + - "https://myst-parser.readthedocs.io/en/latest/" + - null + myst-nb: + - "https://myst-nb.readthedocs.io/en/latest/" + - null + sphinx: + - "https://www.sphinx-doc.org/en/master" + - null + nbformat: + - "https://nbformat.readthedocs.io/en/latest" + - null + rediraffe_branch: 'master' + rediraffe_redirects: + content-types/index.md: file-types/index.md + content-types/markdown.md: file-types/markdown.md + content-types/notebooks.ipynb: file-types/notebooks.ipynb + content-types/myst-notebooks.md: file-types/myst-notebooks.md + content-types/jupytext.md: file-types/jupytext.Rmd + content-types/restructuredtext.md: file-types/restructuredtext.md extra_extensions: - sphinx_click.ext - sphinx_tabs.tabs - - sphinx_panels + - sphinxext.rediraffe diff --git a/docs/_toc.yml b/docs/_toc.yml index b6d200f9e8b5bbd844fc5c1d73c06a5e25bf0751..bdec1c70cd283e214f8659f12d212475ef237473 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -10,13 +10,13 @@ - file: publish/netlify - file: customize/config - file: customize/toc - - file: content-types/index + - file: file-types/index sections: - - file: content-types/markdown - - file: content-types/notebooks - - file: content-types/myst-notebooks - - file: content-types/jupytext - - file: content-types/restructuredtext + - file: file-types/markdown + - file: file-types/notebooks + - file: file-types/myst-notebooks + - file: file-types/jupytext + - file: file-types/restructuredtext - part: Write book content chapters: @@ -25,9 +25,9 @@ - file: content/citations - file: content/math - file: content/figures - - file: content/glue - file: content/layout - file: content/execute + - file: content/code-outputs - part: Make your book interactive @@ -53,3 +53,5 @@ - file: reference/cheatsheet - file: reference/cli - file: reference/glossary + - file: reference/_changelog + title: Change Log diff --git a/docs/advanced/advanced.md b/docs/advanced/advanced.md index 35d69e73878d923d431ee20838f76509cb944498..db6fc45f599b62e821f6fd1fe4eae9097dd2b362 100644 --- a/docs/advanced/advanced.md +++ b/docs/advanced/advanced.md @@ -8,7 +8,7 @@ This page contains more advanced and complete information about the If you have a Google Account, you can use Google Analytics to collect some information on the traffic to your Jupyter Book. With this tool, you can find out how many people are using your book, where they come from and how they -access it, wether they are using the Desktop or the mobile version etc. +access it, whether they are using the Desktop or the mobile version etc. To add Google Analytics to your Jupyter Book, navigate to [Google Analytics](https://analytics.google.com/analytics/web/), create a new @@ -36,7 +36,7 @@ the *right* target, the link checker will only ensure that it resolves. To run the link checker, use the following command: -``` +```bash jupyter-book build mybookname/ --builder linkcheck ``` @@ -71,9 +71,9 @@ This will entirely remove the folders in the `_build/` directory. (jupyter-cell-tags)= -## How should I add cell tags to my notebooks? +## How should I add cell tags and metadata to my notebooks? -You can control the behavior of Jupyter Book by putting custom tags +You can control the behaviour of Jupyter Book by putting custom tags in the metadata of your cells. This allows you to do things like {doc}`automatically hide code cells <../interactive/hiding>`) as well as {ref}`adding interactive widgets to cells <launch/thebelab>`. @@ -86,7 +86,7 @@ There are two straightforward ways to add metadata to cells: To enable the cell tag editor, go click `View -> Cell Toolbar -> Tags`. This will enable the tags UI. Here's what the menu looks like. -  +  2. **Use the JupyterLab Cell Tags plugin**. JupyterLab is an IDE-like Jupyter environment that runs in your browser. It has a "cell tags" plugin built-in, @@ -95,7 +95,16 @@ There are two straightforward ways to add metadata to cells: You'll find tags under the "wrench" menu section. Here's what the tags UI in JupyterLab looks like. -  +  + +Tags are actually just a special section of cell level metadata. +There are three levels of metadata: + +* For notebook level: in the Jupyter Notebook Toolbar go to `Edit -> Edit Notebook Metadata` +* For cell level: in the Jupyter Notebook Toolbar go to `View -> Cell Toolbar -> Edit Metadata` and a button will appear above each cell. +* For output level: using e.g. `IPython.display.display(obj,metadata={"tags": [])`, you can set metadata specific to a certain output (but jupyter-book does not utilise this just yet). + + ### Add tags to notebook cells based on their content @@ -140,23 +149,42 @@ for ipath in notebooks: ``` (raw-html-in-markdown)= -## Use raw html in Markdown +## Use raw HTML in Markdown Jupyter notebook markdown allows you to use pure HTML in markdown cells. -This is strongly discouraged and not guaranteed to work in all cases. +This is discouraged in most cases, +because it will usually just be passed through the build process as raw text, and so will not be subject to processes like: -If, for instance, you use +- Relative path corrections +- Copying of assets to the build folder +- Multiple output type formatting (e.g. it will not show in PDFs!). +So, for instance, below we add, and you will find that the HTML link is broken: + +```md + <a href="../intro.md">Go Home HTML!</a> + + [Go Home Markdown!](../intro.md) ``` -<img src="images/some/file.png" alt="Some image" style="width: 200px;"/> + + <a href="../intro.md">Go Home HTML!</a> + + [Go Home Markdown!](../intro.md) + +:::{tip} +Note that MyST markdown now has some extended syntax features, +which can allow you to use certain HTML elements in the correct manner. + +Such as: + +```html +<img src="../images/fun-fish.png" alt="the fun fish!" width="200px"/> ``` -in your Markdown texts, the source file `images/some/file.png` will not -be copied to the build directory when you run `jupyter-book build`. -You would have to copy the source file to the -build directory manually. Note that MyST markdown gives you control over the -{ref}`image appearance<content-blocks-images>` without having to resort -to pure html. +<img src="../images/fun-fish.png" alt="the fun fish!" width="200px"/> + +See the [image appearance section](content-blocks-images) for details. +::: ## Adding extra HTML to your book @@ -218,10 +246,20 @@ Note that this may not work in latex-generated PDF builds of your page. (working-on-windows)= ## Working on Windows -As of June 5, 2020, there are three open issues that require Windows-specific changes. -Work to complete windows compatibility is underway, in the meantime we provide these -community tips, which are known to work for some users. Note that there is no -guarantee that they will work on all windows installations. +Jupyter Book is now also tested against a Windows environment on Python 3.7 😀 + +For its specification, see the [`windows-latest` runner](https://docs.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources) used by GitHub CI. + +However, there is a known incompatibility for notebook execution, when using Python 3.8 +(see issue [#906](https://github.com/executablebooks/jupyter-book/issues/906)). + +If you're running a recent version of Windows 10 and encounter any issues, you may also wish to try +[installing Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). + +As of June 5, 2020, there were three open issues that required Windows-specific changes. +We hope these are now fixed in jupyter-book version 0.8 but, in case any issues still arise, +leave these community tips, which are known to work for some users. +Note that there is no guarantee that they will work on all windows installations. 1. Character encoding @@ -234,12 +272,10 @@ guarantee that they will work on all windows installations. cmd.exe or powershell enviroments that set PYTHONUTF8=1 override the native locale encoding and use UTF8 for all input/output. - ```{tip} - To make it easier to use - this option, the EOAS/UBC notebook courseware project has created a conda package - [runjb](https://anaconda.org/eoas_ubc/runjb) which - [does this automatically for powershell](https://github.com/eoas-ubc/eoas_tlef/blob/master/converted_docs/wintools/binwin/runjb.ps1) - ``` + :::{tip} + To make it easier to use this option, + the EOAS/UBC notebook courseware project has created a Conda package [runjb](https://anaconda.org/eoas_ubc/runjb) which [does this automatically for powershell](https://github.com/eoas-ubc/eoas_tlef/blob/master/converted_docs/wintools/binwin/runjb.ps1) + ::: 2. A new windows event loop @@ -253,7 +289,7 @@ guarantee that they will work on all windows installations. 3. Nested tables of contents Currently, `_toc.yml` files that reference markdown files - in subfolders are failing for some windows users. That is, this + in sub-folders are failing for some windows users. That is, this [original _toc.yml](https://github.com/eoas-ubc/quantecon-mini-example/blob/master/mini_book/_toc.yml) file will fail with a message saying jupyter-book "```cannot find index.md```" diff --git a/docs/advanced/pdf.md b/docs/advanced/pdf.md index d8597c01a0d88e3f1459403ff43a645d17b5c8ee..becaf7115e12ae5590b4a18ab2619736b34eb3ae 100644 --- a/docs/advanced/pdf.md +++ b/docs/advanced/pdf.md @@ -3,9 +3,9 @@ It is possible to build a single PDF that contains all of your book's content. This page describes a few ways to do so. -```{warning} +:::{warning} PDF building is experimental, and may change or have bugs. -``` +::: There are two approaches to building PDF files: @@ -30,7 +30,7 @@ conversion to PDF. If you wish to build a PDF from your book's HTML, you will need the `pyppeteer` package. You can install it like so: -``` +```bash pip install pyppeteer ``` @@ -49,23 +49,23 @@ to see if that fixes it. We warned you it was an experimental feature :-) To build a single PDF from your book's HTML, use the following command: -``` +```bash jupyter-book build mybookname/ --builder pdfhtml ``` or -``` +```bash jb build mybookname/ --builder pdfhtml ``` -```{warning} -If you get a "MaxRetryError" and see mentions of SSL in the error message when -when building the PDF, this could be due to a bug in `pyppeteer` as it downloads -Chromium for the first time. See [this github comment](https://github.com/miyakogi/pyppeteer/issues/258#issuecomment-563075764) +:::{warning} +If you get a "MaxRetryError" and see mentions of SSL in the error message when building the PDF, +this could be due to a bug in `pyppeteer` as it downloads Chromium for the first time. +See [this github comment](https://github.com/miyakogi/pyppeteer/issues/258#issuecomment-563075764) for a potential fix, and [this jupyter book issue](https://github.com/executablebooks/jupyter-book/issues/593) where we're tracking the issue. -``` +::: (pdf/latex)= ## Build a PDF using Latex @@ -92,18 +92,21 @@ For `Windows` please install [texlive](https://www.tug.org/texlive/windows.html) To build a single PDF using LaTeX, use the following command: -``` +```bash jupyter-book build mybookname/ --builder pdflatex ``` or -``` +```bash jb build mybookname/ --builder pdflatex ``` -```{note} +::::{note} If you would just like to generate the **latex** file you may use: +```bash jb build mybookname/ --builder latex ``` + +:::: diff --git a/docs/advanced/sphinx.md b/docs/advanced/sphinx.md index 7d7fbb4013bc40a101fc85e80fbdbe0d5baade83..dbf45db09a0cf5864ddd8875081a5d93a07e3fef 100644 --- a/docs/advanced/sphinx.md +++ b/docs/advanced/sphinx.md @@ -1,15 +1,19 @@ +(advanced/sphinx-config)= + # Custom Sphinx configuration -Jupyter Book uses the excellent documentation tool [Sphinx](http://www.sphinx-doc.org/) -to build your book and manage citations, cross-references, and extensability. +Jupyter Book uses the excellent documentation tool [Sphinx](http://www.sphinx-doc.org/), +to build your book and manage citations, cross-references, and extensibility. -While Jupyter Book comes pre-configured with several Sphinx extensions, power-users may -wish to add their own extensions and configuration. This page describes how to do so. +While Jupyter Book comes pre-configured with several Sphinx extensions, +power-users may wish to add their own extensions and configuration. +This page describes how to do so. -```{warning} +:::{warning} Adding your own Sphinx configuration and extensions may cause Jupyter Book to behave -unpredictably. Use at your own risk! -``` +unpredictably. +Use at your own risk! +::: ## Custom Sphinx extensions @@ -26,22 +30,23 @@ sphinx: Any extensions that are listed will be appended to the list of Sphinx extensions at build time. -```{note} +:::{note} Make sure that you have your extension installed on your machine, or Sphinx won't know how to build the extensions. -``` +::: ### An example: `sphinx-tabs` -For example, let's say you'd like to include **tabbed content** in your book. There -is [a sphinx-extension for that](https://github.com/djungelorm/sphinx-tabs). To enable -it, we'll do the following: +For example, let's say you'd like to include **tabbed content** in your book. +There is [a sphinx-extension for that](https://github.com/djungelorm/sphinx-tabs). +To enable it, we'll do the following: * **Install `sphinx-tabs`**. Here's the command to do so: ```bash pip install sphinx-tabs ``` + * **Add `sphinx-tabs` content to your book**. Here's an example with MyST markdown: `````md @@ -61,6 +66,7 @@ it, we'll do the following: ``` ```` ````` + * **Activate `sphinx-tabs` in `_config.yml`**. [The `sphinx-tabs` documentation](https://github.com/djungelorm/sphinx-tabs#installation) says we activate it in sphinx by adding `extensions = ["sphinx_tabs.tabs"]`, so we'll add it to our Jupyter Book like so: @@ -71,8 +77,8 @@ it, we'll do the following: - sphinx_tabs.tabs ``` -Now, Jupyter Book will know how to interpret the `{tabs}` directive (and any other -directives that `sphinx-tabs` supports). +Now, Jupyter Book will know how to interpret the `{tabs}` directive +(and any other directives that `sphinx-tabs` supports). For example, here is a rendered version of the tab code pasted above: @@ -123,8 +129,7 @@ Add the static file here: The rules should then automatically be applied to your site. In general, these CSS and JS files will be loaded *after* others are loaded on your page, so they -should overwrite pre-existing rules and behavior. - +should overwrite pre-existing rules and behaviour. ## Manual sphinx configuration @@ -138,11 +143,38 @@ you configure in `conf.py`. To do so, use the following section of `_config.yml` key2: value2 ``` +:::{warning} +Any options set in this section will **override** default configurations set by Jupyter Book. +Use at your own risk! +::: + +:::{tip} +If you wish to inspect a `conf.py` representation of the generated configuration, +which Jupyter Book will pass to Sphinx, you can run from the command-line: + +```bash +jb config sphinx mybookname/ +``` + +::: + +### Fine control of parsing and execution + +As discussed in [the components of Jupyter Book](intro/jupyter-book-components), two of the principle components of Jupyter Book are sphinx extensions; +MyST-Parser, for Markdown parsing, and MyST-NB, for notebook execution and output rendering. + +These two extensions are highly customisable *via* Sphinx configuration. +Some their configuration is already exposed in the `_config.yml`, but you can also directly set configuration, see: + +* The [MyST-Parser configuration options](myst-parser:intro/config-options) +* The [MyST-NB configuration options](myst-nb:start/config-options) + +(sphinx/tex-macros)= ### Defining TeX Macros You can add LaTeX macros for the whole book by defining them under the `Macros` option of the `TeX` block. For example, the following two macros have been pre-defined in the Sphinx configuration -``` +```yaml sphinx: config: mathjax_config: @@ -156,7 +188,7 @@ sphinx: You can also define TeX macros for a specific file by introducing them at the beginning of the file under a `math` directive. For example -```` +````md ```{math} \newcommand\N{\mathbb{N}} @@ -166,14 +198,51 @@ You can also define TeX macros for a specific file by introducing them at the be ``` ```` -The commands can be used inside a `math` directive, `$$` or inline `$`, for example +The commands can be used inside a `math` directive, `$$` or inline `$`, for example: + ```md $$ -A = \bmat 1 & 1 \\ 2 & 1\\ 3 & 2 \emat,\ b=\bmat 2\\ 3 \\ 4\emat,\ \gamma = 0.5 +A = \bmat{} 1 & 1 \\ 2 & 1\\ 3 & 2 \emat{},\ b=\bmat{} 2\\ 3 \\ 4\emat{},\ \gamma = 0.5 $$ ``` -will be rendered as + +will be rendered as: $$ -A = \bmat 1 & 1 \\ 2 & 1\\ 3 & 2 \emat,\ b=\bmat 2\\ 3 \\ 4\emat,\ \gamma = 0.5 +A = \bmat{} 1 & 1 \\ 2 & 1\\ 3 & 2 \emat{},\ b=\bmat{} 2\\ 3 \\ 4\emat{},\ \gamma = 0.5 $$ + +:::{seealso} +[How MyST-Parser works with MathJax](myst-parser:syntax/mathjax), +and the [Math and Equations](myst-content/math) section. +::: + +:::{important} +To have "bare" LaTeX rendered in HTML, you must either set in your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +or more specifically: + +```yaml +sphinx: + config: + myst_amsmath_enable: true +``` + +Then you can include: + +```latex +\begin{equation} + \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} +\end{equation} +``` + +\begin{equation} + \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} +\end{equation} + +::: diff --git a/docs/content-types/index.md b/docs/content-types/index.md deleted file mode 100644 index 87b3a8eb8161d3ab01613e1541497cc4f7998e6d..0000000000000000000000000000000000000000 --- a/docs/content-types/index.md +++ /dev/null @@ -1,44 +0,0 @@ -# Types of content source files - -Jupyter Book supports many kinds of source files for your book's content. -These sections cover the major types of content, and how you can control -their behavior in Jupyter Book. See the list of sections to the left for information -about each type/ - -## Section table of contents - -```{tableofcontents} -``` - -## Allowed content types - -In general, these are the types of content supported in Jupyter Book (along with -links to their section in this book): - -* [Markdown files](markdown). These are text files written in either CommonMark - or in MyST Markdown. -* [Jupyter Notebooks](notebooks). AKA, `.ipynb` files. These files can contain - markdown cells with MyST Markdown. -* [MyST markdown notebooks](myst-notebooks). These are markdown files (ending in `.md`) - that will be *converted to a notebook and executed*. -* [reStructuredText](restructuredtext). These are text files used by the Sphinx - documentation engine (which is used by Jupyter Book). It is recommended to use - MyST Markdown instead. - -## Rules for all content types - -There are a few things that are true for all content types. Here is a short list: - -* **Files must have a title**. Generally this means that they must begin with - a line that starts with a single `#` -* **Use only one top-level header**. Because each page must have a clear - title, it must also only have one top-level header. You cannot have multiple - headers with single `#` tags in them. -* **Headers should increase linearly**. If you're inside of a section with - one `#`, then the next section lower should start with `##`. Avoid jumping straight - from `#` to `###`. - -## Two-way conversion between text-files and `.ipynb` files - -For information about how to convert between text files and `.ipynb` files for use -with Jupyter Book, see {doc}`jupytext`. diff --git a/docs/content-types/jupytext.md b/docs/content-types/jupytext.md deleted file mode 100644 index 36fb5143a55095651a51624ba7586d065f5dffa8..0000000000000000000000000000000000000000 --- a/docs/content-types/jupytext.md +++ /dev/null @@ -1,33 +0,0 @@ -# Jupytext source files - -[Jupytext](https://jupytext.readthedocs.io/en/latest/) is an excellent Python -tool for two-way conversion between Jupyter Notebook `.ipynb` files and -[a variety of text-based files](https://jupytext.readthedocs.io/en/latest/formats.html). -Currently, Jupyter Book directly supports one Jupytext file format: -{doc}`notebooks with MyST Markdown <./myst-notebooks>`). - -```{note} -If you'd like to see support in Jupyter Book for other types of Jupytext source files, -such as Python `.py` files, [open an issue](https://github.com/executablebooks/jupyter-book/issues/new) -and let us know! -``` - -## Convert a Jupytext file into a MyST notebook - -If you'd like to convert your pre-existing Jupytext files into the MyST Notebook format, -so that they may be read in with Jupyter Book, install Jupytext and then run the -following command: - -```bash -jupytext --to myst path/to/yourfile -``` - -Note that you may also pass a wildcard that will be used to convert multiple -files. For example: - -```bash -jupytext --to myst ./*.py -``` - -See [the Jupytext CLI documentation](https://jupytext.readthedocs.io/en/latest/using-cli.html) -for more information. diff --git a/docs/content/citations.md b/docs/content/citations.md index 150c603bb154104fdfc5b7a825a7c93a333f93e4..477b65c24c64319e354b5e2082a3576a999eb17d 100644 --- a/docs/content/citations.md +++ b/docs/content/citations.md @@ -4,32 +4,120 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python name: python3 --- -# Citations and cross-references +# References and citations -Because `jupyter-book` is built on top of {term}`Sphinx`, we can use the excellent +Because `jupyter-book` is built on top of {term}`Sphinx`, +there are many ways of referencing content, including use of the excellent [sphinxcontrib-bibtex](https://sphinxcontrib-bibtex.readthedocs.io/en/latest/) extension to include citations and a bibliography with your book. +:::{tip} +When debugging your book build, the following options can be helpful: + +```bash +jupyter-book build -W -n --keep-going docs/ +``` + +This will check for missing references (`-n`), turning them into errors (`-W`), +but will still attempt to run the full build (`--keep-going`), +so that you can see all errors in one run. +::: + +(content:references)= +## Cross-references and labels + +Labels are a way to add tags to parts of your content that you can reference +later on. This is helpful if you want to quickly insert links to other +parts of your book. Labels can be added before major elements of a page, +such as titles or figures. + +To add a label, use the following pattern **before** the element you wish +to label: + +```md +(my-label)= +# The thing to label +``` + +For example, we've added the following label above the header for this section: + +```md +(content:references)= +## Cross-references and labels +``` + +You can insert cross-references to labels in your content with the following syntax: `` {ref}`label-text` ``. +For example, the following syntax: `` {ref}`content:references` `` results in a link to this section like so: {ref}`content:references`. + +### Referencing your book's content + +There are a few ways to reference your book's content, depending on what kind of +content you'd like to reference. Here is a quick overview of some common options: + +* `{ref}` is used to reference section labels that you define or figures with a `name` value +* `{numref}` is used to provide *numbered* references to figures +* `{doc}` is used to reference other files in your book +* `{eq}` is used to reference equations that have been given a `label` value + +:::{tip} +You can reference a section label through ``{ref}`label` `` or ``{ref}`some text <label>` ``. +Documents can be referenced through ``{doc}`path/to/document` `` or ``{doc}`some text <path/to/document>` `` +::: + +If you with to use Markdown style syntax, then MyST-Markdown will try to find a reference, +from any of the above reference types (and more!). +This actually has an advantage, in that you can used nested syntax, ror example: + +```md +[A **_reference_** to a page](./myst.md) + +[A reference to a header](content:references) +``` + +[A **_reference_** to a page](./myst.md) + +[A reference to a header](content:references) + +Leaving the title empty, will mean the reference uses the target as text, for example the title of a section: + +```md +[](./myst.md) +``` + +[](./myst.md) + +:::{tip} +You can control how MyST-Markdown distinguishes between internal references and external URLs in your `_config.yml`. +For example: + +```yaml +parse: + myst_url_schemes: [mailto, http, https] +``` + +Means that `[Jupyter Book](https://jupyterbook.org)` will be recognised as a URL, but `[Citations](content:citations)` will not: + +* [Jupyter Book](https://jupyterbook.org) +* [Citations](content:citations) + +::: + +(content:citations)= ## Citations and bibliographies -You can add citations and bibliographies using references that are stored in a -`bibtex` file that is in your book's folder. You can then add a citation in-line in your -markdown with the **`{cite}`** role, and add a bibliography from your bibtex file -with the `{bibliography}` directive. +You can add citations and bibliographies using references that are stored in a `bibtex` file that is in your book's folder. You can then add a citation in-line in your markdown with the **`{cite}`** role, and add a bibliography from your bibtex file with the `{bibliography}` directive. **To add citations to your book**, take the following steps: 1. **Create a references bibtex file**. - ``` + ```bash touch references.bib ``` @@ -37,7 +125,7 @@ with the `{bibliography}` directive. [the BibTex documentation](http://www.bibtex.org/Using/) for information about the BibTex reference style. Here's an example citation: - ``` + ```latex @article{perez2011python , title = {Python: an ecosystem for scientific computing} , author = {Perez, Fernando and Granger, Brian E and Hunter, John D} @@ -49,15 +137,16 @@ with the `{bibliography}` directive. , publisher = {AIP Publishing} } ``` + 3. **Add a citation**. In your content, add the following text to include a citation - ``` + ```md {cite}`mybibtexcitation` ``` For example, this text - ``` + ```md {cite}`perez2011python` ``` @@ -65,7 +154,7 @@ with the `{bibliography}` directive. You can also include multiple citations in one go, like so: - ``` + ```md {cite}`perez2011python,holdgraf_rapid_2016,RePEc:the:publsh:1367,caporaso2010qiime` ``` @@ -73,7 +162,7 @@ with the `{bibliography}` directive. 4. **Add a bibliography**. Use the following directive to do so: - ```` + ````md ```{bibliography} path/to/references.bib ``` ```` @@ -81,17 +170,16 @@ with the `{bibliography}` directive. This will generate a bibliography for your entire bibtex file. See [the bibliography at the end of this page](citations/bibliography) for an example. - When your book is built, the bibliography and citations will now be included. -`````{warning} +:::{warning} If you are adding a bibliography to a *different* page from your references, then you may need to ensure that page is processed last, which Sphinx does alphabetically. For example, name the file `zreferences.rst`. See [this `sphinxcontrib-bibtex` section](https://sphinxcontrib-bibtex.readthedocs.io/en/latest/usage.html#unresolved-citations-across-documents) for more information. -````` +::: This feature uses [`sphinxcontrib-bibtex`](https://sphinxcontrib-bibtex.readthedocs.io/en/latest/usage.html#roles-and-directives) under the hood, see its documentation for more information on how to use and configure @@ -113,7 +201,7 @@ These styles create the following bibliography formatting: To set your reference style, use the style option: -```` +````md ```{bibliography} path/to/references.bib :style: unsrt ``` @@ -128,7 +216,7 @@ Having multiple bibliography directives, however, can cause `sphinx` to issue A common fix is to add a filter to the bibliography directives: -```` +````md ```{bibliography} path/to/references.bib :filter: docname in docnames ``` @@ -136,49 +224,6 @@ A common fix is to add a filter to the bibliography directives: See `sphinxcontrib-bibtex` documentation for [local bibliographies](https://sphinxcontrib-bibtex.readthedocs.io/en/latest/usage.html#section-local-bibliographies) -(labels-and-refs)= -## Cross-references and labels - -Labels are a way to add tags to parts of your content that you can reference -later on. This is helpful if you want to quickly insert links to other -parts of your book. Labels can be added before major elements of a page, -such as titles or figures. - -To add a label, use the following pattern **before** the element you wish -to label: - -``` -(my-label)= -# The thing to label -``` - -For example, we've added the following label above the header for this section: - -```md -(labels-and-refs)= -## Cross-references and labels -``` - -You can insert cross-references to labels in your content with the following -syntax: `` {ref}`label-text` ``. For example, the following syntax: -`` {ref}`labels-and-refs` `` results in a link to this section like so: -{ref}`labels-and-refs`. - -### Referencing your book's content - -There are a few ways to reference your book's content, depending on what kind of -content you'd like to reference. Here is a quick overview of some common options: - -* `{ref}` is used to reference section labels that you define or figures with a `name` value -* `{numref}` is used to provide *numbered* references to figures -* `{doc}` is used to reference other files in your book -* `{eq}` is used to reference equations that have been given a `label` value - -```{tip} -You can reference a section label through ``{ref}`label` `` or ``{ref}`some text <label>` ``. -Documents can be referenced through ``{doc}`path/to/document` `` or ``{doc}`some text <path/to/document>` `` -``` - (citations/bibliography)= ## Bibliography diff --git a/docs/content/glue.md b/docs/content/code-outputs.md similarity index 66% rename from docs/content/glue.md rename to docs/content/code-outputs.md index 883522135db369cbb4c7d8176244ff147a31a203..2a88497771ddaa6f2387391c6e9ba75257ec512a 100644 --- a/docs/content/glue.md +++ b/docs/content/code-outputs.md @@ -3,17 +3,178 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: '1.4.1' kernelspec: display_name: Python 3 language: python name: python3 --- -(glue)= +(content:code-outputs)= -# Insert code outputs into page content +# Formatting code outputs + +The formatting of code outputs is highly configurable. +Below give examples of how to format particular outputs, and even insert outputs into other locations in the document. + +:::{seealso} +The [MyST-NB documentation](myst-nb:use/format/cutomise), for how to fully customise the output renderer. +::: + +(content:code-outputs:images)= +## Images + +For any image types output by the code, we can apply formatting *via* cell metadata. +Then for the image we can apply all the variables of the standard [image directive](https://docutils.sourceforge.io/docs/ref/rst/directives.html#image): + +```{margin} +Units of length are: 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc' +``` + +- **width**: length or percentage (%) of the current line width +- **height**: length +- **scale**: integer percentage (the "%" symbol is optional) +- **align**: "top", "middle", "bottom", "left", "center", or "right" +- **classes**: space separated strings +- **alt**: string + +We can also set a caption (which is rendered as [CommonMark](https://commonmark.org/)) and name, by which to reference the figure: + +````md +```{code-cell} ipython3 +--- +render: + image: + width: 200px + alt: fun-fish + classes: shadow bg-primary + figure: + caption: | + Hey everyone its **party** time! + name: fun-fish +--- +from IPython.display import Image +Image("../images/fun-fish.png") +``` +```` + +```{code-cell} ipython3 +--- +render: + image: + width: 300px + alt: fun-fish + classes: shadow bg-primary + figure: + caption: | + Hey everyone its **party** time! + name: fun-fish +--- +from IPython.display import Image +Image("../images/fun-fish.png") +``` + +Now we can link to the image from anywhere in our documentation: [swim to the fish](fun-fish) + +:::{seealso} +[](jupyter-cell-tags) +::: + +(content:code-outputs:markdown)= +## Markdown + +Markdown output is parsed by MyST-Parser, currently with the parsing set to strictly [CommonMark](https://commonmark.org/). + +The parsed Markdown is integrated into the wider documentation, and so it is possible, for example, to include internal references: + +```{code-cell} ipython3 +from IPython.display import display, Markdown +display(Markdown('**_some_ markdown** and an [internal reference](use/format/markdown)!')) +``` + +and even internal images can be rendered! + +```{code-cell} ipython3 +display(Markdown('')) +``` + +(content:code-outputs:ansi)= +## ANSI Outputs + +By default, the standard output/error streams and text/plain MIME outputs may contain ANSI escape sequences to change the text and background colors. + +```{code-cell} ipython3 +import sys +print("BEWARE: \x1b[1;33;41mugly colors\x1b[m!", file=sys.stderr) +print("AB\x1b[43mCD\x1b[35mEF\x1b[1mGH\x1b[4mIJ\x1b[7m" + "KL\x1b[49mMN\x1b[39mOP\x1b[22mQR\x1b[24mST\x1b[27mUV") +``` + +This uses the built-in {py:class}`~myst-nb:myst_nb.ansi_lexer.AnsiColorLexer` [pygments lexer](https://pygments.org/). +You can change the lexer used in the `_config.yml`, for example to turn off lexing: + +```yaml +sphinx: + config: + nb_render_text_lexer: "none" +``` + +The following code shows the 8 basic ANSI colors it is based on. +Each of the 8 colors has an “intense†variation, which is used for bold text. + +```{code-cell} ipython3 +text = " XYZ " +formatstring = "\x1b[{}m" + text + "\x1b[m" + +print( + " " * 6 + + " " * len(text) + + "".join("{:^{}}".format(bg, len(text)) for bg in range(40, 48)) +) +for fg in range(30, 38): + for bold in False, True: + fg_code = ("1;" if bold else "") + str(fg) + print( + " {:>4} ".format(fg_code) + + formatstring.format(fg_code) + + "".join( + formatstring.format(fg_code + ";" + str(bg)) for bg in range(40, 48) + ) + ) +``` + +:::{note} +ANSI also supports a set of 256 indexed colors. +This is currently not supported, but we hope to introduce it at a later date +(raise an issue on the repository if you require it!). +::: + +(content:code-outputs:priority)= +## Render priority + +When Jupyter executes a code cell it can produce multiple outputs, and each of these outputs can contain multiple [MIME media types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types), for use by different output formats (like HTML or LaTeX). + +MyST-NB stores a default priority dictionary for most of the common [output builders](https://www.sphinx-doc.org/en/master/usage/builders/index.html), which you can be also update in your `_config.yml`. +For example, this is the default priority list for HTML: + +```yaml +sphinx: + config: + nb_render_priority: + html: + - "application/vnd.jupyter.widget-view+json" + - "application/javascript" + - "text/html" + - "image/svg+xml" + - "image/png" + - "image/jpeg" + - "text/markdown" + - "text/latex" + - "text/plain" +``` + +(content:code-outputs:glue)= + +## Insert code outputs into page content You often wish to run analyses in one notebook and insert them into your documents text elsewhere. For example, if you'd like to include a figure, @@ -46,7 +207,7 @@ For more information about roles, see {doc}`myst`. (glue/gluing)= -## Gluing variables in your notebook +### Gluing variables in your notebook You can use `myst_nb.glue()` to assign value of a variable to a key of your choice. `glue` will store all of the information that is normally used to **display** @@ -65,7 +226,7 @@ You can then insert it into your text like. Adding `` {glue:}`cool_text` `` to your content results in the following: {glue:}`cool_text`. -### Gluing numbers, plots, and tables +#### Gluing numbers, plots, and tables You can glue anything in your notebook and display it later with `{glue:}`. Here we'll show how to glue and paste **numbers and images**. We'll simulate some @@ -150,7 +311,7 @@ you may wish to remove the output here, using the `remove-output` tag (glue/pasting)= -## Pasting glued variables into your page +### Pasting glued variables into your page Once you have glued variables into a notebook, you can then **paste** those variables into your text in your book anywhere you like (even on other pages). @@ -158,7 +319,7 @@ These variables can be pasted using one of the roles or directives in the `glue: +++ -### The `glue` role/directive +#### The `glue` role/directive The simplest role and directive are `glue:any`, which paste the glued output inline or as a block respectively, @@ -184,7 +345,6 @@ Here's how it looks: Or we can paste inline objects like so: - ```md Inline text; {glue:}`boot_mean`, and figure; {glue:}`boot_fig`. ``` @@ -204,7 +364,7 @@ control over how the outputs look in your pages. +++ -## Controling the pasted outputs +### Controlling the pasted outputs You can control the pasted outputs by using a sub-command of `{glue:}`. These are called like so: `` {glue:subcommand}`key` ``. These subcommands allow you to control more of the look, feel, and @@ -217,12 +377,12 @@ generic command that doesn't make many assumptions about what you are gluing. +++ -### The `glue:text` role +#### The `glue:text` role The `glue:text` role, is specific to text outputs. For example, the following text: -``` +```md The mean of the bootstrapped distribution was {glue:text}`boot_mean` (95% confidence interval {glue:text}`boot_clo`/{glue:text}`boot_chi`). ``` @@ -243,7 +403,7 @@ For example, the following: ``My rounded mean: {glue:text}`boot_mean:.2f` `` wil +++ -### The `glue:figure` directive +#### The `glue:figure` directive With `glue:figure` you can apply more formatting to figure like objects, such as giving them a caption and referencable label: @@ -283,6 +443,7 @@ A caption for a pandas table. ``` ```` + ```{glue:figure} df_tbl :figwidth: 300px :name: "tbl:df" @@ -292,7 +453,7 @@ A caption for a pandas table. +++ -### The `glue:math` directive +#### The `glue:math` directive The `glue:math` directive, is specific to latex math outputs (glued variables that contain a `text/latex` mimetype), @@ -333,11 +494,11 @@ Insert the equation here: +++ -## Advanced glue usecases +### Advanced glue use-cases Here are a few more specific and advanced uses of the `glue` submodule. -### Pasting into tables +#### Pasting into tables In addition to pasting blocks of outputs, or in-line with text, you can also paste directly into tables. This allows you to compose complex collections of structured data using outputs diff --git a/docs/content/content-blocks.md b/docs/content/content-blocks.md index 3965194619134f772a5607a33d946824a1a39455..bf04f52816cfa0b6bc165356761fea66d38bfbd6 100644 --- a/docs/content/content-blocks.md +++ b/docs/content/content-blocks.md @@ -1,60 +1,17 @@ -# Special content blocks with MyST +# Special content blocks -A common use of directives and roles is to designate "special blocks" of your -content. This allows your to include more complex information such as warnings -and notes, citations, and figures. This section covers a few common ones. - -(content-blocks-images)= -## Images - -You can add images with more control using MyST markdown. - -````{margin} -Note that the traditional way to add images with CommonMark will still work. -For example: -``` - -``` -```` - -If you'd like more control over an image, use the following -MyST markdown syntax: - -```` -```{image} ../images/cool.jpg -:alt: cool! -:height: 100px -``` -```` - -This allows you to control aspects of the image with -{ref}`directive arguments <directive-arguments>`. For example, here we have -controlled both the *alternative text* as well as the *height* of the image. -Using html to control your image appearance is discouraged. If you insist, -read more about {ref}`using raw html in Markdown <raw-html-in-markdown>`. - -For a list of all of the options you can supply to `image`, see the -[reStructured Text image documentation](https://docutils.sourceforge.io/docs/ref/rst/directives.html#image). - -````{margin} A note on nesting -You can nest admonitions (and other content blocks) inside one another. For example: - -```{note} -Here's a note block inside a margin block -``` - -See {ref}`markdown/nexting` for instructions to do this. -```` +A common use of directives and roles is to designate "special blocks" of your content. +This allows your to include more complex information such as warnings and notes, citations, and figures. +This section covers a few common ones. ## Notes, warnings, and other admonitions -Let's say you wish to highlight a particular block of -text that exists slightly apart from the narrative of your page. You can -use the **`{note}`** directive for this. +Let's say you wish to highlight a particular block of text that exists slightly apart from the narrative of your page. +You can use the **`{note}`** directive for this. For example, the following text: -```` +````md ```{note} Here is a note! ``` @@ -66,6 +23,16 @@ Results in the following output: Here is a note! ``` +````{margin} A note on nesting +You can nest admonitions (and other content blocks) inside one another. For example: + +:::{note} +Here's a note block inside a margin block +::: + +See {ref}`markdown/nexting` for instructions to do this. +```` + There are a number of similarly-styled blocks of text. For example, here is a `{warning}` block: @@ -84,7 +51,7 @@ For a complete list of options, see [the `sphinx-book-theme` documentation](http You can also choose the title of your message box by using the **`{admonition}`** directive. For example, the following text: -```` +````md ```{admonition} Here's your admonition Here's the admonition content ``` @@ -110,8 +77,74 @@ My content ```` ````` +(admonitions:colons)= +### New Style Admonitions + +The admonition syntax above utilises the general [directives syntax](content:myst/directives). +This has the advantage of making it consistent with every other directive. +However, a big disadvantage is that, when working in any standard Markdown editor (or the Jupyter Notebook interfaces), +the text they contain will not render nicely as standard Markdown (for Markdown previews). + +By enabling extended syntax in your `_config.yml`, you will gain access to an alternative syntax for admonitions: + +```yaml +parse: + myst_extended_syntax: true +``` + +The key differences is that, instead of back-ticks (`` ` ``), colons (`:`) are used, +and thus **the content renders as regular Markdown**. + +For example: + +```md +:::{note} +This text is **standard** _Markdown_ +::: +``` + +:::{note} +This text is **standard** _Markdown_ +::: + +Similar to normal directives, these admonitions can also be nested: + +```md +::::{important} +:::{note} +This text is **standard** _Markdown_ +::: +:::: +``` + +::::{important} +:::{note} +This text is **standard** _Markdown_ +::: +:::: + +:::{note} +This syntax only supports a select subset of directives: + +> admonition, attention, caution, danger, error, important, hint, note, seealso, tip and warning. +::: + +These directives do **not** currently allow for parameters to be set, but you can add additional CSS classes to the admonition as comma-delimited arguments after the directive name. +Also, `admonition` can have a custom title. +For example: + +```md +:::{admonition,warning} This *is* also **Markdown** +This text is **standard** _Markdown_ +::: +``` + +:::{admonition,warning} This *is* also **Markdown** +This text is **standard** _Markdown_ +::: + (content/toggle-admonitions)= -### Interactive admonitions with dropdowns +### Hiding the content of admonitions You can also hide the body of your admonition blocks so that users must click a button to reveal their content. This is helpful if you'd like to make a point @@ -119,7 +152,7 @@ that isn't immediately visible to the user. To hide the body of admonition blocks, add a "dropdown" class to them, like so: -```` +````md ```{note} :class: dropdown The note body will be hidden! @@ -136,30 +169,172 @@ The note body will be hidden! You can use this in conjunction with `{admonition}` directives to include your own titles and stylings. For example: -```` -```{admonition} Click the + sign to see what's inside -:class: dropdown, tip +````md +:::{admonition,dropdown,tip} Click the + sign to see what's inside Here's what's inside! -``` +::: ```` results in: -```{admonition} Click the + sign to see what's inside -:class: dropdown, tip +:::{admonition,dropdown,tip} Click the + sign to see what's inside Here's what's inside! +::: + +:::{important} +Admonition dropdowns require JavaScript to be enabled on the browser which they are viewed. +By contrast, the [dropdown directive](content/panels) below works purely *via* HTML+CSS. +::: + +(content/panels)= +## Panels and Dropdowns + +Jupyter Book now also integrates the [sphinx-panels](https://sphinx-panels.readthedocs.io) extension. +This allows you to add special blocks to your online content, for example: + +````{panels} +Content of the left panel. + +{badge}`example-badge,badge-primary` + +--- + +```{link-button} content/panels +:text: Clickable right panel +:type: ref +:classes: stretched-link +``` + +```` + +```{dropdown} Click on me to see my content! +I'm the content which can be **anything** {fa}`check,text-success ml-1` + +:::{note} +Even other blocks. +::: +``` + +Which was created from: + +`````md +````{panels} +Content of the left panel. + +{badge}`example-badge,badge-primary` + +--- + +```{link-button} content/panels +:text: Clickable right panel +:type: ref +:classes: stretched-link +``` + +```` + +```{dropdown} Click on me to see my content! +I'm the content which can be **anything** {fa}`check,text-success ml-1` + +:::{note} +Even other blocks. +::: +``` +````` + +(content/definition-lists)= + +## Definition Lists + +Definition lists are enabled by setting in your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +Definition lists utilise the [markdown-it-py deflist plugin](https://markdown-it-py.readthedocs.io/en/latest/plugins.html), which itself is based on the [Pandoc definition list specification](http://johnmacfarlane.net/pandoc/README.html#definition-lists). + +This syntax can be useful, for example, as an alternative to nested bullet-lists: + +- Term 1 + - Definition +- Term 2 + - Definition + +Using instead: + +```md +Term 1 +: Definition + +Term 2 +: Definition +``` + +Term 1 +: Definition + +Term 2 +: Definition + +From the [Pandoc documentation](https://pandoc.org/MANUAL.html#definition-lists): + +> Each term must fit on one line, which may optionally be followed by a blank line, and must be followed by one or more definitions. +> A definition begins with a colon or tilde, which may be indented one or two spaces. + +> A term may have multiple definitions, and each definition may consist of one or more block elements (paragraph, code block, list, etc.) + +Here is a more complex example, demonstrating some of these features: + +Term *with Markdown* +: Definition [with reference](content/definition-lists) + + A second paragraph +: A second definition + +Term 2 + ~ Definition 2a + ~ Definition 2b + +Term 3 +: A code block +: > A quote +: A final definition, that can even include images: + + <img src="../images/fun-fish.png" alt="fishy" width="200px"> + +This was created from: + +```md +Term *with Markdown* +: Definition [with reference](ontent/definition-lists) + + A second paragraph + +Term 2 + ~ Definition 2a + ~ Definition 2b + +Term 3 +: A code block + +: > A quote + +: A final definition, that can even include images: + + <img src="../images/fun-fish.png" alt="fishy" width="200px"> ``` ### Insert code cell outputs into admonitions If you'd like to insert the outputs of running code *inside* admonition -blocks, we recommend using {doc}`Glue functionality <glue>`. For example, -we'll insert one of the outputs that was glued into the book from the page -{doc}`glue`. +blocks, we recommend using [Glue functionality](content:code-outputs:glue). +For example, we'll insert one of the outputs that was glued into the book from the [code outputs page](./code-outputs.md). The below code: -```` +````md ```{note} Here's my figure: {glue:figure}`sorted_means_fig` @@ -173,13 +348,12 @@ Here's my figure: {glue:}`sorted_means_fig` ``` -See {doc}`glue` for more information on how to use Glue to insert your outputs -directly into your content. +See [](content:code-outputs:glue) for more information on how to use Glue to insert your outputs directly into your content. -```{tip} +:::{tip} To hide code input and output that generated the variable you are inserting, use the `remove_cell` tag. -See {doc}`../interactive/hiding` for more information and other tag options. -``` +See [](../interactive/hiding.md) for more information and other tag options. +::: ## Quotations and epigraphs @@ -196,7 +370,7 @@ the following quotation: Was created with this text: -``` +```md > Here is a cool quotation. > > From me, Jo the Jovyan @@ -214,7 +388,7 @@ From me, Jo the Jovyan Was generated with this markdown: -```` +````md ```{epigraph} Here is a cool quotation. @@ -233,7 +407,7 @@ Here is a cool quotation. Was generated with this markdown: -```` +````md ```{epigraph} Here is a cool quotation. @@ -241,36 +415,13 @@ Here is a cool quotation. ``` ```` -## Footnotes - -You can include footnotes in your book's content using a standard markdown syntax. -This will include a numbered reference to the footnote in-line, and insert the footnote -to a list of footnotes at the bottom of the page. - -To create a footnote, first insert a reference in-line with this syntax: `[^mylabel]`. -Then, define the text for that label like so: - -``` -[^mylabel]: My footnote text. -``` - -You can define `[^mylabel]` anywhere in the page, though its definition will always -be placed at the bottom of your built page. For example, here's a footnote [^mynote] -and here's another one [^mynote2]. You can click either of them to see the footnotes -at the bottom of this page. - -[^mynote]: Here's the text of my first note. -[^mynote2]: And the text of my second note. - Note that - [you can include markdown footnote definitions](https://executablebooks.org). - ## Glossaries Glossaries allow you to define terms in a glossary, and then link back to the glossary throughout your content. You can create a glossary with the following syntax: -```` +````md ```{glossary} term one An indented explanation of term 1 @@ -309,5 +460,25 @@ more information. You can also use MyST to control various aspects of the page layout. For more information on this, see {doc}`layout`. +## Footnotes + +You can include footnotes in your book's content using a standard markdown syntax. +This will include a numbered reference to the footnote in-line, and insert the footnote +to a list of footnotes at the bottom of the page. + +To create a footnote, first insert a reference in-line with this syntax: `[^mylabel]`. +Then, define the text for that label like so: + +```md +[^mylabel]: My footnote text. +``` + +You can define `[^mylabel]` anywhere in the page, though its definition will always +be placed at the bottom of your built page. For example, here's a footnote [^mynote] +and here's another one [^mynote2]. You can click either of them to see the footnotes +at the bottom of this page. -[myst-parser]: https://myst-parser.readthedocs.io/en/latest/ +[^mynote]: Here's the text of my first note. +[^mynote2]: And the text of my second note. + Note that + [you can include markdown footnote definitions](https://executablebooks.org). diff --git a/docs/content/execute.md b/docs/content/execute.md index 1c78181f5804b0c08a77bda89fbafd86681bde2c..a8af3cfeb795834f45c680daa21ab85c1e805207 100644 --- a/docs/content/execute.md +++ b/docs/content/execute.md @@ -5,8 +5,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.2 kernelspec: display_name: Python 3 language: python @@ -15,33 +13,31 @@ kernelspec: # Execute and cache your pages -Jupyter Book can automatically run and cache any notebook pages. Notebooks can either -be run each time the documentation is built, or cached locally so that notebooks -will only be re-run when the code cells in a notebook have changed. +Jupyter Book can automatically run and cache any notebook pages. +Notebooks can either be run each time the documentation is built, +or cached locally so that notebooks will only be re-run when the code cells in a notebook have changed. -Cacheing behavior is controlled with the `execute:` section -[in your `_config.yml` file](../customize/config). See +Caching behaviour is controlled with the `execute:` section [in your `_config.yml` file](../customize/config.md). See the sections below for each configuration option and its effect. -```{tip} -If you'd like to execute code that is in your markdown files, you can use the -`{code-cell}` directive in MyST markdown. See {doc}`../content-types/myst-notebooks` -for more information. -``` +:::{tip} +If you'd like to execute code that is in your markdown files, +you can use the `{code-cell}` directive in MyST markdown. +See [](../file-types/myst-notebooks.md) for more information. +::: ## Trigger notebook execution By default, Jupyter Book will execute any content files that have a notebook structure, -and that are missing at least one output. This is equivalent to the following -configuration in _config.yml`: +and that are missing at least one output. This is equivalent to the following configuration in _config.yml`: ```yaml execute: execute_notebooks: auto ``` -This will only execute notebooks that are missing at least one output. If -the notebook has *all* of its outputs populated, then it will not be executed. +This will only execute notebooks that are missing at least one output. +If the notebook has *all* of its outputs populated, then it will not be executed. **To force the execution of all notebooks, regardless of their outputs**, change the above configuration value to: @@ -60,13 +56,13 @@ execute: See {ref}`execute/cache` for more information. -**To turn off notebook execution**,change the -above configuration value to: +**To turn off notebook execution**,change the above configuration value to: ```yaml execute: execute_notebooks: 'off' ``` + (execute/exclude)= ## Exclude files from execution @@ -81,36 +77,34 @@ execute: - '*pattern3withwildcard' ``` -Any file that matches one of the items in `exclude_patterns` will not be -executed. +Any file that matches one of the items in `exclude_patterns` will not be executed. (execute/cache)= -## Cacheing the notebook execution +## Caching the notebook execution -You may also **cache the results of executing a notebook page** using [jupyter-cache]. In -this case, when a page is executed, its outputs will be stored in a local database. +You may also **cache the results of executing a notebook page** using [jupyter-cache]. +In this case, when a page is executed, its outputs will be stored in a local database. This allows you to be sure that the outputs in your documentation are up-to-date, -while saving time avoiding unnecessary re-execution. It also allows you to store your -`.ipynb` files in your `git` repository *without their outputs*, but still leverage -a cache to save time when building your site. +while saving time avoiding unnecessary re-execution. +It also allows you to store your `.ipynb` files in your `git` repository *without their outputs*, +but still leverage a cache to save time when building your site. When you re-build your site, the following will happen: -* Notebooks that have not seen changes to their **code cells** since the last build - will not be re-executed. Instead, their outputs will be pulled from the cache - and inserted into your site. -* Notebooks that **have any change to their code cells** will be re-executed, and the - cache will be updated with the new outputs. +* Notebooks that have not seen changes to their **code cells** since the last build will not be re-executed. + Instead, their outputs will be pulled from the cache and inserted into your site. +* Notebooks that **have any change to their code cells** will be re-executed + and the cache will be updated with the new outputs. -To enable cacheing of notebook outputs, use the following configuration: +To enable caching of notebook outputs, use the following configuration: ```yaml execute: execute_notebooks: cache ``` -By default, the cache will be placed in the parent of your build folder. Generally, -this is in `_build/.jupyter_cache`. +By default, the cache will be placed in the parent of your build folder. +Generally, this is in `_build/.jupyter_cache`. You may also specify a path to the location of a jupyter cache you'd like to use: @@ -119,32 +113,88 @@ execute: cache: path/to/mycache ``` -The path should point to an **empty folder**, or a folder where a -**jupyter cache already exists**. +The path should point to an **empty folder**, or a folder where a **jupyter cache already exists**. [jupyter-cache]: https://github.com/executablebooks/jupyter-cache "the Jupyter Cache Project" +## Execution Configuration -## Execution FAQs +You can control notebook execution, and how output content is handled at a project level (using your `_config.yml`) or also, in some cases, at a notebook and code cell level. +Below are detailed a number of ways to achieve this. -### How does execution deal with relative paths in my code? +:::{seealso} +[](jupyter-cell-tags) and [](./code-outputs.md). +::: -The behavior of relative paths in your code is slightly different depending on -whether you're executing with `auto` or with `cache`. +### The execution working directory -* **If executing with `auto`** notebooks will execute in the folder where they - exist and relative paths will work. -* **If executing with `cache`** then the notebook will execute in a temporary - folder, and relative paths will not work. Support for relative paths is planned - for the future. +:::{important} +The default behaviour of `cache` is now to run in the local directory. +This is a change from the from `v0.7`. +::: -### How can I include code that raises errors? +By default, the command working directory (cwd) in which a notebook runs will be the directory in which it is located (for both `auto` and `cache`). This means that notebooks requiring access to assets in relative paths will work. -In some cases, you may want to intentionally show code that doesn't work (e.g., to show -the error message). To do this, add a `raises-exception` tag to your code cell. This -can be done via a Jupyter interface, or via the `{code-cell}` directive like so: +Alternatively, if you wish for your notebooks to isolate your notebook execution, in a temporary folder, +you can use the `_config.yml` setting: -```` +```yaml +execute: + run_in_temp: true +``` + +### Setting execution timeout + +Execution timeout defines the maximum time (in seconds) each notebook cell is allowed to run. +If the execution takes longer an exception will be raised. +The default is 30 seconds, so in cases of long-running cells you may want to specify an higher value. +The timeout option can also be set to `None` or -1 to remove any restriction on execution time. + +You can set the timeout for all notebook execution in your `_config.yml`: + +```yaml +execute: + timeout: 100 +``` + +This global value can also be overridden per notebook by adding this to you notebooks metadata: + +```json +{ + "metadata": { + "execution": { + "timeout": 30 + } +} +``` + +### Dealing with code that raises errors + +In some cases, you may want to intentionally show code that doesn't work +(e.g., to show the error message). + +You can allow errors for all notebooks in your `_config.yml`: + +```yaml +execute: + allow_errors: true +``` + +This global value can also be overridden per notebook by adding this to you notebooks metadata: + +```json +{ + "metadata": { + "execution": { + "allow_errors": false + } +} +``` + +Lastly, you can allow errors at a cell level, by adding a `raises-exception` tag to your code cell. +This can be done via a Jupyter interface, or via the `{code-cell}` directive like so: + +````md ```{code-cell} --- tags: [raises-exception] @@ -161,3 +211,85 @@ tags: [raises-exception] --- print(thisvariabledoesntexist) ``` + +### Dealing with code that produces stderr + +You may also wish to control how stderr outputs are dealt with. + +Alternatively, you can configure how stdout is dealt with at a global configuration level, using the `nb_output_stderr` configuration value. +This can be set to: + +You can configure the default behaviour for all notebooks in your `_config.yml`: + +```yaml +execute: + stderr_output: show +``` + +Where the value is one of: + +* `"show"` (default): show all stderr (unless a `remove-stderr` tag is present) +* `"remove"`: remove all stderr +* `"remove-warn"`: remove all stderr, but log a warning if any found +* `"warn"`, `"error"` or `"severe"`: log the stderr at a certain level, if any found. + +You can also remove stderr at a cell level, sing the `remove-stderr` [cell tag](https://jupyter-notebook.readthedocs.io/en/stable/changelog.html#cell-tags), like so: + +````md +```{code-cell} ipython3 +:tags: [remove-stderr] + +import sys +print("this is some stdout") +print("this is some stderr", file=sys.stderr) +``` +```` + +```{code-cell} ipython3 +:tags: [remove-stderr] + +import sys +print("this is some stdout") +print("this is some stderr", file=sys.stderr) +``` + +### Dealing with code that produces stdout + +Similar to stderr, you can remove stdout at a cell level with the `remove-stdout` tag: + +````md +```{code-cell} ipython3 +:tags: [remove-stdout] + +import sys +print("this is some stdout") +print("this is some stderr", file=sys.stderr) +``` +```` + +```{code-cell} ipython3 +:tags: [remove-stdout] + +import sys +print("this is some stdout") +print("this is some stderr", file=sys.stderr) +``` + +## Execution statistics + +As notebooks are executed, certain statistics are stored on the build environment by MyST-NB. +The simplest way to access and visualise this data is using the **`{nb-exec-table}`** directive. + +:::{seealso} +The [MyST-NB documentation](myst-nb:execute/statistics), for creating you own directives to manipulate this data. +::: + +````md +```{nb-exec-table} +``` +```` + +produces: + +```{nb-exec-table} +``` diff --git a/docs/content/figures.md b/docs/content/figures.md index 4557938dfa6bbb9fdcb57643e19102b5a9a6bce4..223ff25988c78087b3f0116a7b28bc35b7fa6a58 100644 --- a/docs/content/figures.md +++ b/docs/content/figures.md @@ -1,15 +1,101 @@ -# Figures +# Images and Figures + +(content-blocks-images)= +## Images + +MyST Markdown provides a few different syntaxes for including images in your documentation, as explained below. + +The first is the standard Markdown syntax: + +```md + +``` + + + +This will correctly copy the image to the build folder and will render it in all output formats (HTML, TeX, etc). +However, it is limited in the configuration that can be applied, for example setting a width. + +As discussed in [this section](content:myst/directives), MyST allows for directives to be used such as `image` and `figure` (see [the sphinx documentation](sphinx:rst-primer) for available options): + +````md +```{image} ../images/fun-fish.png +:alt: fishy +:class: bg-primary +:width: 200px +:align: center +``` +```` + +```{image} ../images/fun-fish.png +:alt: fishy +:class: bg-primary mb-1 +:width: 200px +``` + +This allows you to control aspects of the image with [directive arguments](directive-arguments). + +In one way, this is an improvement on the Markdown syntax, however, the drawback is that this syntax will not show the image in common Markdown viewers (for example when the files are viewed on GitHub). +The final option then is directly using HTML, which can also be parsed by MyST Markdown. +Using raw HTML is usually a bad choice (see [this explanation](raw-html-in-markdown)), +but enabling extended syntax in your `_config` enables MySt-Parser to properly handle isolated `img` tags: + +```yaml +parse: + myst_extended_syntax: true +``` + +Now you can add: + +```html +<img src="../images/fun-fish.png" alt="fishy" class="bg-primary" width="200px"> +``` + +and we correctly render: + +<img src="../images/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px"> + +This will also be output in PDF LaTeX builds! + +Allowed attributes are equivalent to the `image` directive: `src`, `alt`, `class`, `width` and `height`. +Any other attributes will be dropped. + +(content-blocks-images/formats)= +### Supported image formats + +Standard rasterized image formats, such as `.png`, `jpg`, are supported for both HTML and LaTeX/PDF output formats. +Vector formats, such as `.svg`, `.pdf` and `.eps`, by contrast are normally builder specific. +See the `supported_image_types` specification for [each sphinx builder here](sphinx:builders). + +To support multiple builders, Jupyter Book allows you to use a `*` asterisk as the extension: + +```html +<img src="../images/fun-fish.*" alt="fishy" class="bg-primary mb-1" width="200px"> +``` + +All images matching the provided pattern will then be searched for and each builder chooses the best image out of these candidates. + +<img src="../images/fun-fish.*" alt="fishy" class="bg-primary mb-1" width="200px"> + +You can use a tool such as [imagemagick](https://imagemagick.org), to pre-convert your images into multiple formats. + +Alternatively, you may wish to check out these sphinx extensions: + +- [sphinx.ext.imgconverter](sphinx:sphinx.ext.imgconverter) +- [sphinxcontrib-svg2pdfconverter](https://github.com/missinglinkelectronics/sphinxcontrib-svg2pdfconverter) + +## Figures MyST Markdown also lets you include **figures** in your page. Figures are like images, except that they are easier to reference elsewhere in your book, and they include things like captions. To include a figure, use this pattern: -```` +````md ```{figure} ../images/C-3PO_droid.png --- height: 150px -name: my-figure +name: directive-fig --- Here is my figure caption! ``` @@ -18,27 +104,88 @@ Here is my figure caption! ```{figure} ../images/C-3PO_droid.png --- height: 150px -name: my-figure +name: directive-fig --- Here is my figure caption! ``` -```{note} +:::{note} You can also include figures that were generated from your code in notebooks. -To do so, see {doc}`glue`. +To do so, see [](content:code-outputs:glue). +::: + +## Markdown Figures + +Markdown figures combine [colon style admonitions](admonitions:colons) and [HTML image parsing](content-blocks-images), to produce a "Markdown friendly" syntax for figures, +with equivalent behaviour to the `figure` directive above. + +To enable them, add in your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +The figure block must contain **only** two components; an image, in either Markdown or HTML syntax, and a single paragraph for the caption. + +As with admonitions, the figure can have additional classes set on it, but the title is now taken as the reference target of the figure: + +```md +:::{figure,myclass} markdown-fig +<img src="../images/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px"> + +This is a caption in **Markdown**! +::: ``` +:::{figure,myclass} markdown-fig +<img src="../images/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px"> + +This is a caption in **Markdown**! +::: + +As we see here, the target we set can be referenced: + +```md +[Go to the fish!](markdown-fig) +``` + +[Go to the fish!](markdown-fig) + ## Referencing figures -You can then refer to this figure using the `{ref}` role like: -`` {ref}`my-figure` ``, which will replace the reference with the figure -caption like so: {ref}`my-figure`. +You can then refer to your figures using the `{ref}` role or Markdown style references like: + +```md +- {ref}`directive-fig` +- [](markdown-fig) +``` + +which will replace the reference with the figure caption like so: + +- {ref}`directive-fig` +- [](markdown-fig) + +### Numbered references + Another convenient way to create cross-references is with the `{numref}` role, which automatically numbers the labelled objects. -For example, `` {numref}`my-figure` `` will produce a reference like: -{numref}`my-figure`. +For example, `` {numref}`directive-fig` `` will produce a reference like: {numref}`directive-fig`. -If an explicit text is provided, this caption will serve as the title of the reference. The characters "%s" and "{number}" will be replaced with a figure number, while "{name}" will be replaced with a figure caption. For example, ``{numref}`Figure {number}: {name} <my-figure>` `` will produce: {numref}`Figure {number}: {name} <my-figure>`. +If an explicit text is provided, this caption will serve as the title of the reference. + +```md +- {ref}`Fly to the droid <directive-fig>` +- [Swim to the fish](markdown-fig) +``` + +- {ref}`Fly to the droid <directive-fig>` +- [Swim to the fish](markdown-fig) + +With `numref`, you can also access the figure number and caption individually: +the characters "%s" and "{number}" will be replaced with a figure number, while "{name}" will be replaced with a figure caption. + +For example, ``{numref}`Figure {number}: {name} <directive-fig>` `` will produce: {numref}`Figure {number}: {name} <directive-fig>`. ## Margin captions and figures @@ -66,9 +213,9 @@ Here is my figure caption! ## Figure scaling and alignment -Figures can also be aligned by using the option `:align: right` or `:align: left`. By default, figures are aligned to the center (see {numref}`my-figure`). +Figures can also be aligned by using the option `:align: right` or `:align: left`. By default, figures are aligned to the center (see {numref}`directive-fig`). -```` +````md ```{figure} ../images/cool.jpg --- scale: 50% @@ -86,7 +233,7 @@ align: left Here is my figure caption! ``` -```` +````md ```{figure} ../images/cool.jpg --- scale: 50% @@ -109,32 +256,25 @@ Here is my figure caption! The following options are supported: -* `scale` : _integer percentage_ - - Uniformly scale the figure. The default is "100" which indicates no scaling. The symbol "%" is optional. - -* `width` : _length or percentage_ - - You can set the figure width in the following units: "em", "ex", "px","in" ,"cm", "mm", "pt", "pc", "%". - -* `height` : _length_ - - You can set the figure height in the following units: "em", "ex", "px", "in", "cm", "mm", "pt", "pc", "". - -* `alt` : _text_ - - Text to be displayed if the figure cannot display, or if the reader is using assistive technologies. Generally entails a short description of the figure. +`scale` : _integer percentage_ +: Uniformly scale the figure. The default is "100" which indicates no scaling. The symbol "%" is optional. -* `align` : _"left", "center", or "right"_ +`width` : _length or percentage_ +: You can set the figure width in the following units: "em", "ex", "px","in" ,"cm", "mm", "pt", "pc", "%". - Align the figure left, center, or right. Default alignment is center. +`height` : _length_ +: You can set the figure height in the following units: "em", "ex", "px", "in", "cm", "mm", "pt", "pc", "". -* `name` : _text_ +`alt` : _text_ +: Text to be displayed if the figure cannot display, or if the reader is using assistive technologies. Generally entails a short description of the figure. - A unique identifier for your figure that you can use to reference it with `{ref}` or `{numref}`. Cannot contain spaces or special characters. +`align` : _"left", "center", or "right"_ +: Align the figure left, center, or right. Default alignment is center. -* `figclass` : _text_ +`name` : _text_ +: A unique identifier for your figure that you can use to reference it with `{ref}` or `{numref}`. Cannot contain spaces or special characters. - Value of the figure's class attribute which can be used to add custom CSS or JavaScript. Predefined options include: +`figclass` : _text_ +: Value of the figure's class attribute which can be used to add custom CSS or JavaScript. Predefined options include: * _"margin"_ : Display figure on the margin * _"margin-caption"_ : Display figure caption on the margin diff --git a/docs/content/layout.md b/docs/content/layout.md index 7c750553c13e10ade1e7c52da1b5d6b002586009..c38e469919107c55130bb0d9e63293c376635794 100644 --- a/docs/content/layout.md +++ b/docs/content/layout.md @@ -3,8 +3,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -89,7 +87,7 @@ like to provide context to throughout your content. To add a sidebar to your content, use the following pattern: -```` +````md ```{sidebar} My sidebar title My sidebar content ``` @@ -99,7 +97,7 @@ My sidebar content To add margin content with myst markdown, use this syntax: -```` +````md ```{margin} An optional title My margin content ``` @@ -141,6 +139,10 @@ make_fig(figsize=(10, 5)) This can be combined with other tags like `remove-input` to **only display the figure**. +:::{seealso} +[](jupyter-cell-tags) +::: + +++ ## Scrolling cell outputs @@ -152,7 +154,6 @@ the entire page. You can trigger this behavior in Jupyter Book by adding the following tag to a cell's metadata: - ```json { "tags": [ diff --git a/docs/content/math.md b/docs/content/math.md index 0219c45230e6393e6be2977ea931ca1f5c06c24d..40319f47041f496830d8a10d4b3be048fdea23d2 100644 --- a/docs/content/math.md +++ b/docs/content/math.md @@ -3,22 +3,38 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python name: python3 --- +(myst-content/math)= # Math and Equations -Jupyter Book uses [MathJax](http://docs.mathjax.org/) for typesetting math in your -book. This allows you to have LaTeX-style mathematics in your online content. +Jupyter Book uses [MathJax](http://docs.mathjax.org/) for typesetting math in your HTML book build. +This allows you to have LaTeX-style mathematics in your online content. This page shows you a few ways to control this. -For more information about equation numbering, see the -[MathJax equation numbering documentation](http://docs.mathjax.org/en/v2.7-latest/tex.html#automatic-equation-numbering). +:::{seealso} +For more information about equation numbering, +see the [MathJax equation numbering documentation](http://docs.mathjax.org/en/v2.7-latest/tex.html#automatic-equation-numbering). +::: + +:::{tip} + +By default MathJax version 2 is currently used. +If you are using a lot of math, you may want to try using version 3, which claims to improve load speeds by 60 - 80%: + +```yaml +sphinx: + config: + mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js +``` + +See the [sphinx documentation](sphinx:sphinx.ext.mathjax) for details. + +::: ## In-line math @@ -36,31 +52,61 @@ equation, wrap the equation in either `$$` or `\begin` statements. For example, ```latex -\begin{equation} +$$ \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} -\end{equation} +$$ ``` -results in -\begin{equation} +results in: + +$$ \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} -\end{equation} +$$ +++ -and +or, by adding to your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +you can use: ```latex -$$ - \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} -$$ +\begin{gather*} +a_1=b_1+c_1\\ +a_2=b_2+c_2-d_2+e_2 +\end{gather*} + +\begin{align} +a_{11}& =b_{11}& + a_{12}& =b_{12}\\ +a_{21}& =b_{21}& + a_{22}& =b_{22}+c_{22} +\end{align} ``` -results in +which results in: -$$ - \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15} -$$ +\begin{gather*} +a_1=b_1+c_1\\ +a_2=b_2+c_2-d_2+e_2 +\end{gather*} + +\begin{align} +a_{11}& =b_{11}& + a_{12}& =b_{12}\\ +a_{21}& =b_{21}& + a_{22}& =b_{22}+c_{22} +\end{align} + +:::{seealso} +The myst-parser guides to [dollar math syntax](myst-parser:syntax/math), [LaTeX math syntax](myst-parser:syntax/amsmath), and [how MyST-Parser works with MathJax](myst-parser:syntax/mathjax). + +For advanced use, also see how to [define MathJax TeX Macros](sphinx/tex-macros). +::: +++ @@ -69,7 +115,7 @@ $$ If you'd like to number equations so that you can refer to them later, use the **math directive**. It looks like this: -```` +````md ```{math} :label: my_label my_math @@ -78,8 +124,7 @@ my_math For example, the following code: - -```` +````md ```{math} :label: my_label w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1} @@ -88,18 +133,28 @@ w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1} Will generate this: - ```{math} :label: my_label w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1} ``` +or you can use the dollar math syntax with a prefixed label: -```{note} +```md +$$ + w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1} +$$ (my_other_label) +``` + +$$ + w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1} +$$ (my_other_label) + +:::{note} Labels cannot start with an integer, or they won't be able to be referenced and will throw a warning message if referenced. For example, `:label: 1` and `:label: 1eq` cannot be referenced. -``` +::: ### Linking to equations @@ -107,9 +162,17 @@ If you've created an equation with a label, you can link to it from within your (and across pages!). You can refer to the equation using the label that you've provided by using -the **`{eq}` role**. For example, putting `` {eq}`my_label` `` in-line will -result in this: {eq}`my_label`. - -```{code-cell} ipython3 +the **`{eq}` role**. For example: +```md +- A link to an equation directive: {eq}`my_label` +- A link to a dollar math block: {eq}`my_other_label` ``` + +- A link to an equation directive: {eq}`my_label` +- A link to a dollar math block: {eq}`my_other_label` + +:::{note} +`\labels` inside LaTeX environment are not currently identified, and so cannot be referenced. +We hope to implement this in a future update (see [executablebooks/MyST-Parser#202](https://github.com/executablebooks/MyST-Parser/issues/202))! +::: diff --git a/docs/content/myst.md b/docs/content/myst.md index 7182aa48de65e6064800aaf98eae72c3262fd831..53a02dd6ef8fa09d91d5e500ea690f95fd64402b 100644 --- a/docs/content/myst.md +++ b/docs/content/myst.md @@ -1,55 +1,52 @@ # MyST Markdown Overview -In addition to [Jupyter Notebook markdown](../content-types/notebooks), Jupyter Book also supports -a special flavor of markdown called **MyST (or -Markedly Structured Text)**. It was designed to make it easier to create -publishable computational documents in Markdown. It is a superset of -[CommonMark markdown](https://commonmark.org/) and draws heavy inspiration -from the fantastic [RMarkdown language from RStudio](https://rmarkdown.rstudio.com/). +In addition to [Jupyter Notebook markdown](../file-types/notebooks.ipynb), +Jupyter Book also supports a special flavour of markdown called **MyST (or +Markedly Structured Text)**. +It was designed to make it easier to create publishable computational documents in Markdown. +It is a superset of [CommonMark markdown](https://commonmark.org/) and draws heavy inspiration from the fantastic [RMarkdown language from RStudio](https://rmarkdown.rstudio.com/). ```{margin} For those who are familiar with Sphinx, MyST markdown is basically -CommonMark + Sphinx roles and directives +CommonMark + Markdown Extensions + Sphinx roles and directives ``` -Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or -in regular markdown files (`.md`), you'll write in the same flavor of -**MyST Markdown**. Jupyter Book will know how to parse both of them. +Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or in regular markdown files (`.md`), +you'll write in the same flavour of **MyST Markdown**. Jupyter Book will know how to parse both of them. -This page contains a few pieces of information about MyST markdown and how it -relates to Jupyter Book. You can -find much more information about this flavor of markdown at -[The Myst Parser documentation][myst-parser]. +This page contains a few pieces of information about MyST markdown and how it relates to Jupyter Book. +You can find much more information about this flavour of markdown at +[The Myst Parser documentation](myst-parser:example_syntax). -[myst-parser]: https://myst-parser.readthedocs.io/en/latest/ +:::{admonition,tip} Want to use RMarkdown directly? +See [](../file-types/jupytext.md) +::: ## Directives and roles -Roles and directives are two of the most powerful tools in Jupyter Book. They -are kind of like *functions*, but written in a markup language. They both -serve a similar purpose, but **roles are written in one line**, whereas -**directives span many lines**. They both accept different kinds of inputs, -and what they do with those inputs depends on the specific role or directive -that is being called. +Roles and directives are two of the most powerful tools in Jupyter Book. +They are kind of like *functions*, but written in a markup language. +They both serve a similar purpose, but **roles are written in one line**, whereas **directives span many lines**. +They both accept different kinds of inputs, and what they do with those inputs depends on the specific role or directive that is being called. +(content:myst/directives)= ### Directives -Directives customize the look, feel, and behavior of your book. They are -kind of like *functions*, and come in a variety of names -with different behavior. This section covers how to structure and use them. +Directives customize the look, feel, and behaviour of your book. +They are kind of like *functions*, and come in a variety of names with different behaviour. +This section covers how to structure and use them. At its simplest, you can insert a directive into your book's content like so: -```` +````md ```{mydirectivename} My directive content ``` ```` -This will only work if a directive with name `mydirectivename` already exists -(which it doesn't). There are many pre-defined directives associated with -Jupyter Book. For example, to insert a note box into your content, you can -use the following directive: +This will only work if a directive with name `mydirectivename` already exists (which it doesn't). +There are many pre-defined directives associated with Jupyter Book. +For example, to insert a note box into your content, you can use the following directive: ```` ```{note} @@ -65,13 +62,12 @@ Here is a note In your built book. -For more information on writing directives, see the -[MyST documentation](https://myst-parser.readthedocs.io/). +For more information on writing directives, see the [MyST documentation](myst-parser:syntax/directives). (directive-arguments)= #### More arguments and metadata in directives -Many directives allow you to control their behavior with extra pieces of +Many directives allow you to control their behaviour with extra pieces of information. In addition to the directive name and the directive content, directives allow two other configuration points: @@ -79,7 +75,7 @@ directives allow two other configuration points: Here's an example use of directive arguments: -```` +````md ```{directivename} arg1 arg2 My directive content. ``` @@ -93,7 +89,7 @@ as `key: val` pairs inside of `---` lines. They both work the same way: Here's an example of directive keywords using `:key: val` pairs: -```` +````md ```{directivename} :key1: metadata1 :key2: metadata2 @@ -103,7 +99,7 @@ My directive content. and here's an example of directive keywords using `---` lines: -```` +````md ```{directivename} --- metadata1: metadata2 @@ -113,57 +109,77 @@ My directive content. ``` ```` -```{tip} +:::{tip} Remember, specifying directive keywords with `:key:` or `---` will both work the same. We recommend using `---` if you have many keywords you wish to specify, or if some values will span multiple lines. Use the `:key: val` syntax as a short-hand for just one or two keywords. -``` +::: For examples of how this is used, see the sections below. +(content:myst/roles)= ### Roles Roles are very similar to directives, but they are less-complex and written entirely on one line. You can insert a role into your book's content with this pattern: -``` +```md Some content {rolename}`and here is my role's content!` ``` -Again, roles will only work if `rolename` is a valid role's name. For example, -the `doc` role can be used to refer to another page in your book. You can -refer directly to another page by its relative path. For example, the -role syntax `` {doc}`../intro` `` will result in: {doc}`../intro`. +Again, roles will only work if `rolename` is a valid role's name. +For example, the `doc` role can be used to refer to another page in your book. +You can refer directly to another page by its relative path. +For example, the role syntax `` {doc}`../intro` `` will result in: {doc}`../intro`. -For more information on writing roles, see the -[MyST documentation](https://myst-parser.readthedocs.io/). +For more information on writing roles, see the [MyST documentation](myst-parser:syntax/roles). ## What roles and directives are available? -There is currently no single list of roles / directives to use as a refernece, but this +There is currently no single list of roles / directives to use as a reference, but this section tries to give as much as information as possible. For those who are familiar with the Sphinx ecosystem, **you may use any directive / role that is available in Sphinx**. This is because Jupyter Book uses Sphinx to build your book, and MyST Markdown supports all syntax that Sphinx supports (think of it as a markdown version of reStructuredText). -```{caution} +:::{caution} If you search the internet (and the links below) for information about roles and directives, the documentation will generally be written with reStructuredText in mind. MyST markdown -is different from reStructuredText, but all of the functionality should be the same. See -[the MyST Sphinx parser documentation](https://myst-parser.readthedocs.io/en/latest/) for -more information about MyST vs. rST. -``` +is different from reStructuredText, but all of the functionality should be the same. +See [the MyST Sphinx parser documentation](myst-parser:intro/get-started) for more information about MyST vs. rST. +::: For a list of directives that are available to you, there are three places to check: -1. [The Sphinx directives page](https://www.sphinx-doc.org/en/2.0/usage/restructuredtext/directives.html) +1. [The Sphinx directives page](sphinx:usage/restructuredtext/directives) has a list of directives that are available by default in Sphinx. 2. [The reStructuredText directives page](https://docutils.sourceforge.io/docs/ref/rst/directives.html) has a list of directives in the Python "docutils" module. 3. This documentation has several directives that are specific to Jupyter Book in addition. +In some unusual cases, MyST-Parser may be incompatible with a certain role or directive. +In this case, you can use the special `eval-rst` directive, to directly parse reStructuredText: + +````md +```{eval-rst} +.. note:: + + A note written in reStructuredText. +``` +```` + +```{eval-rst} +.. note:: + + A note written in reStructuredText. +``` + +:::{seealso} +The MyST-Parser documentation on [how directives parse content](myst-parser:syntax/directives/parsing), and its use for [including rST files into a Markdown file](myst-parser:howto/include-rst), and [using `sphinx.ext.autodoc` in Markdown files](myst-parser:howto/autodoc). +::: + (markdown/nexting)= ## Nesting content blocks in markdown @@ -174,7 +190,7 @@ code blocks as well. For example, the following syntax: -````` +`````md ```` ``` ``` @@ -183,7 +199,7 @@ For example, the following syntax: Yields: -```` +````md ``` ``` ```` @@ -191,7 +207,7 @@ Yields: Thus, if you'd like to nest directives inside one another, you can take the same approach, for example, the following syntax: -````` +`````md ````{margin} ```{note} Here's my note! @@ -209,26 +225,31 @@ Here's my note! ## Other MyST Markdown syntax -In addition to roles and directives, there are several other kinds of syntax -that it supports. MyST supports all syntax in CommonMark markdown (the kind of -markdown that Jupyter Notebooks use), as well as an extended syntax that is used -for scientific publishing. +In addition to roles and directives, there are numerous other kinds of syntax +that it supports. +MyST supports all syntax in CommonMark markdown (the kind of markdown that Jupyter Notebooks use), as well as an extended syntax that is used for scientific publishing. + +The [MyST-Parser](myst-parser:intro/get-started) is the tool that Jupyter Book uses to allow you to write your book content in MyST. +It is also a good source of information about the MyST syntax. +Here are some links you can use as a reference: + +* [CommonMark block syntax](myst-parser:commonmark-block-tokens) +* [Extended MyST block syntax in MyST](myst-parser:extended-block-tokens) +* [CommonMark in-line syntax](myst-parser:commonmark-span-tokens) +* [Extended in-line syntax in MyST](myst-parser:extended-span-tokens) -The [MyST-Parser](https://myst-parser.readthedocs.io/en/latest/) is the tool -that Jupyter Book uses to allow you to write your book content in MyST. It is -also a good source of information about the MyST syntax. Here are some links -you can use as a reference: +As a shorthand, Jupyter Book offers a single configuration, to enable the MyST [extended syntaxes](myst-parser:syntax-optional): -* [CommonMark block syntax](https://myst-parser.readthedocs.io/en/latest/using/syntax.html#commonmark-tokens) -* [Extended MyST block syntax in MyST](https://myst-parser.readthedocs.io/en/latest/using/syntax.html#extended-block-tokens) -* [CommonMark in-line syntax](https://myst-parser.readthedocs.io/en/latest/using/syntax.html#commonmark-inline-tokens) -* [Extended in-line syntax in MyST](https://myst-parser.readthedocs.io/en/latest/using/syntax.html#extended-inline-tokens) +```yaml +parse: + myst_extended_syntax: true +``` ## What can I create with MyST markdown? -See {doc}`content-blocks` for an introduction to what you can do with MyST markdown -in Jupyter Book. In addition, the other pages in this site cover many more use-cases -for how to use directives with MyST. +See [](./content-blocks.md) for an introduction to what you can do with MyST markdown +in Jupyter Book. +In addition, the other pages in this site cover many more use-cases for how to use directives with MyST. ## Tools for writing MyST markdown @@ -247,6 +268,12 @@ For working between Jupyter notebook and markdown files, we recommend [jupytext] an open source tool for two-way conversion between `.ipynb` and text files. Jupytext [supports the MyST markdown format](https://jupytext.readthedocs.io/en/latest/formats.html#myst-markdown). +:::{note} +For full compatibility with `myst-parser`, it is necessary to use `jupytext==1.6.0rc0` or later. + +See also [](file-types:custom:jupytext). +::: + ### VSCode If editing the markdown files using VS Code, the diff --git a/docs/contribute/intro.md b/docs/contribute/intro.md index b14d307e8d4813770128b667e9d50631a365564c..777b2db68fbd5f504b5bcdf3e7be3d25ecb94942 100644 --- a/docs/contribute/intro.md +++ b/docs/contribute/intro.md @@ -44,27 +44,27 @@ code looks correct according to a few checks. ### Run the tests -For information about running tests, see {ref}`developer/tests`. +For code tests, jupyter-book uses [pytest](https://docs.pytest.org)). +You may run all the tests, or only ones that do not require additional installations, with the following command: -(developer/tests)= -## Testing infrastructure +```shell +>> pytest -m 'not requires_chrome and not requires_tex' +``` -Jupyter Book uses [**`pytest`**](https://docs.pytest.org/en/latest/) for its testing -infrastructure. You may run the tests with the following command: +You can alternatively use [tox](https://tox.readthedocs.io), to run the tests in multiple isolated environments, and also without the need for the initial dependencies install (see the `tox.ini` file for available test environments and further explanation): -```bash -pytest --ignore=tests/test_pdf.py +```shell +>> tox -e py38-sphinx3 -- -m 'not requires_chrome and not requires_tex' ``` -This will run the Jupyter Book test suite, *except for the PDF tests*. This is because -running the PDF generation tests require a full Latex environment, which you may not have -set up. +Either will both run the Jupyter Book test suite, *except for the PDF tests*. +This is because running the PDF generation tests require a full Latex environment, which you may not have set up. -```{note} +:::{note} Jupyter Book makes use of [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) for running tests in parallel. You can take advantage of this by running tests with the `-n` argument followed by the number of CPUs you would like to use. For example: `pytest -n 4`. This makes the tests run much faster. -``` +::: ### To test PDF generation @@ -82,7 +82,6 @@ Do so by following the instructions in {ref}`pdf/latex`. If you have installed the requirements for both HTML and Latex generation, you should be able to run the full test suite with pytest. - ## Repository Structure of Jupyter Book This section covers the general structure of the @@ -113,8 +112,8 @@ module docstrings for more information. ### The template Jupyter Book Jupyter Book comes bundled with a small template book to show off content. This can -be immediately built with `jupyter-book build`. It can be found at -[`jupyter_book/book_template`](https://github.com/executablebooks/jupyter-book/tree/master/jupyter_book/book_template). +be immediately built with `jupyter-book build`. +It can be found at [`jupyter_book/book_template`](https://github.com/executablebooks/jupyter-book/tree/master/jupyter_book/book_template). ### An example @@ -139,8 +138,8 @@ what kinds of functionality they support: * {term}`MyST markdown<MyST>` is parsed into Sphinx by {term}`the MyST Parser<MyST-Parser>`. * {term}`The MyST-NB package<MyST-NB>` parses Jupyter Notebooks into Sphinx and also - controls some parts of notebook execution. It also provdes - {doc}`inserting code outputs into content <../content/glue>`. + controls some parts of notebook execution. + It also provides [inserting code outputs into content](content:code-outputs:glue). * {term}`Jupyter-Cache` manages the execution and cacheing of notebook content at build time. It is controlled by {term}`MyST-NB`. * The {term}`Sphinx-Book-Theme` defines the look and feel of Jupyter Book, and is diff --git a/docs/customize/config.md b/docs/customize/config.md index f719af0d4945dd654a03cc0d8637625a7cf0524a..d365bb97e06c58fc30d1c3daa239a9b3846a2ff1 100644 --- a/docs/customize/config.md +++ b/docs/customize/config.md @@ -1,19 +1,18 @@ # Configure book settings -In Jupyter Book, most configuration is controlled with a -**Configuration YAML file (`_config.yml`)**. This file controls a number -of options that you may use to configure your book. +In Jupyter Book, most configuration is controlled with a **Configuration YAML file (`_config.yml`)**. +This file controls a number of options that you may use to configure your book +(the [defaults](config:defaults) can be found at the bottom of this page). -This page describes the general structure of `_config.yml`, and how -you can use it to control some basic parts of your book. The rest of the -pages in this section describe more detail for specific features. +This page describes the general structure of `_config.yml`, +and how you can use it to control some basic parts of your book. +The rest of the pages in this section describe more detail for specific features. ## Structure of `_config.yml` Here is a very simple `_config.yml` configuration (it is taken from {download}`the demo book config file <../../jupyter_book/book_template/_config.yml>`): - ```yaml # Book settings title: My sample book @@ -21,8 +20,8 @@ author: The Jupyter Book Community logo: 'images/logo.png' ``` -As you can see, keys correspond to configuration options, and their values are how -you control behavior of the book. In this case: +As you can see, keys correspond to configuration options, and their values are how you control behaviour of the book. +In this case: * **title**: controls the title of your book. In the HTML output, it is displayed in the left sidebar. @@ -31,8 +30,8 @@ you control behavior of the book. In this case: * **logo**: controls the logo of the book, relative to the book root. In the HTML output, it is displayed above the title in the left sidebar. -There are also some configuration options that are nested. For example, to configure -your book to include **binder links** with any section built from a Jupyter Notebook, +There are also some configuration options that are nested. +For example, to configure your book to include **binder links** with any section built from a Jupyter Notebook, you may use the following configuration: ```yaml @@ -48,13 +47,13 @@ launch_buttons: Look throughout this book's documentation for different ways that you can configure your book. -```{caution} +:::{caution} YAML can be tricky when it comes to how it treats certain kinds of values. For example, when using strings in YAML it is usually fine to omit quotes around them. However, there are certain values that will be *converted* to boolean values if they do not have strings around them. For example, `false`, `true`, `off`, etc. In addition, pure numbers will be converted to `float` or `int` unless you put strings around them. -``` +::: ## Add source repository buttons @@ -94,8 +93,8 @@ html: ### Add a button to suggest edits You can add a button to each page that will allow users to edit the page text -directly and submit a pull request to update the documentation. To include this -button, use the following configuration: +directly and submit a pull request to update the documentation. +To include this button, use the following configuration: ```yaml repository: @@ -106,12 +105,51 @@ html: use_edit_page_button: true ``` -## Configuration reference +(config:sphinx)= +### Advance configuration (with sphinx) + +Users who are familiar with [Sphinx configuration](sphinx:build-config), may wish to directly add extensions or set options to parse to the underlying sphinx application. + +To add extensions, use: + +```yaml +sphinx: + extra_extensions: [my_extension] +``` + +This will **append** to the list of extensions already loaded by Jupyter Book. + +To set additional sphinx configuration: + +```yaml +sphinx: + config: + my_option: my_value +``` + +:::{warning} +Any options set in this section will **override** default configurations set by Jupyter Book. +Use at your own risk! +::: + +If you wish to inspect a `conf.py` representation of the generated configuration, +which Jupyter Book will pass to Sphinx, you can run from the command-line: + +```bash +jb config sphinx mybookname/ +``` + +:::{seealso} +The advanced section on [sphinx configuration](advanced/sphinx-config). +::: + +(config:defaults)= +## Configuration Defaults -For a reference example of *all* the possible Binder links and their default values, see the -section below: +Below is the full default configuration file. +Anything you set in your own `_config.yml` will be merged into these defaults, before they are used to configure the build. ```{literalinclude} ../../jupyter_book/default_config.yml - :language: yaml - :class: full-width +:language: yaml +:class: full-width ``` diff --git a/docs/customize/toc.md b/docs/customize/toc.md index bf0fd7c7667f966727f8199964625aee318aefb1..7f48ba122c0399e303933eaf4069c0e5e81da03f 100644 --- a/docs/customize/toc.md +++ b/docs/customize/toc.md @@ -94,11 +94,11 @@ Below the first entry, you have two options for defining the structure of your b Note that **chapters do not continue between parts**. Think of each part as a self-contained collection of chapters (e.g., for the purposes of numbering). -```{admonition} Don't mix these two structures +:::{admonition,warning} Don't mix these two structures! When designing the top-level sections of your `_toc.yml` file, you must pick *either* a list of chapters via `- file:` entries, or a list of parts via `- part:` entries with chapters inside of them. You cannot intermix them both. -``` +::: (toc/files)= ### Files @@ -142,7 +142,6 @@ you may do so with the `title:` key. For example: Note that this only applies to the sidebar in the table of contents, it does not change the actual chapter/section title. - (toc/numbering)= ## Number your book's chapters and sections @@ -198,7 +197,6 @@ few quirks to it. Here are a few gotchas: them. That means if you have two `- part:` entries with 2 pages each, you will have two sets of `1.` and `2.` sections, one for each part. - (toc/structure)= ## How headers and sections map onto to book structure @@ -296,6 +294,7 @@ If however `chapter1.md` had an extra third-level header, like so: ## Chapter 1 section second header ``` ```` + Then your book will treat them like so: ```md @@ -333,7 +332,7 @@ it finds a file that isn't listed there). If you'd like Jupyter Book to skip a file entirely, you can do so with the following configuration in `_config.yml`: -``` +```yaml exclude_patterns: [pattern1/*, path/to/myfile.ipynb] ``` @@ -377,23 +376,23 @@ If you'd like to add a table of contents for the sub-sections of a page *within the page content* (in-line with the other content on the page), you may do so by using the `{tableofcontents}` directive. You can use it like so: -```` +````md ```{tableofcontents} ``` ```` -For an example, see the source of [the content types page](../content-types/index.md). +For an example, see the source of [the content types page](../file-types/index.md). ## Automatically generate your `_toc.yml` file You can use `jupyter-book` to *generate* a Table of Contents file from your book using the filenames of your book's content. To do so, run the following command -``` +```bash jupyter-book toc mybookpath/ ``` -Jupyter Book will search `mybookpath/` for any [content files](../content-types/index) +Jupyter Book will search `mybookpath/` for any [content files](../file-types/index) and create a `_toc.yml` file out of them. There are a few considerations to keep in mind: * Each sub-folder must have at least one content file inside it diff --git a/docs/file-types/include-rst.rst b/docs/file-types/include-rst.rst new file mode 100644 index 0000000000000000000000000000000000000000..599212a127f062849acdf59af0b6e4802068ebba --- /dev/null +++ b/docs/file-types/include-rst.rst @@ -0,0 +1 @@ +Hallo I'm from an rST file, :ref:`myst-parser:with a reference about using autodoc <howto/autodoc>`. diff --git a/docs/file-types/index.md b/docs/file-types/index.md new file mode 100644 index 0000000000000000000000000000000000000000..7eb6af76bf8a3b6bfc7e70f53db5b9d045ad4e68 --- /dev/null +++ b/docs/file-types/index.md @@ -0,0 +1,51 @@ +# Types of content source files + +Jupyter Book supports many kinds of source files for your book's content. +These sections cover the major types of content, and how you can control their behavior in Jupyter Book. +See the list of sections to the left for information about each type. + +## Section table of contents + +```{tableofcontents} +``` + +## Allowed content types + +In general, these are the types of content supported in Jupyter Book (along with +links to their section in this book): + +[Markdown files](markdown) +: These are text files written in either CommonMark or in MyST Markdown. + +[Jupyter Notebooks](notebooks) +: AKA, `.ipynb` files. These files can contain markdown cells with MyST Markdown. +: A Jupyter Notebook can utilise any program kernel that implements the [Jupyter messaging protocol](http://jupyter-client.readthedocs.io/en/latest/messaging.html) for executing code. + There are kernels available for [Python](http://ipython.org/notebook.html), [Julia](https://github.com/JuliaLang/IJulia.jl), [Ruby](https://github.com/minad/iruby), [Haskell](https://github.com/gibiansky/IHaskell) and [many other languages](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels). + +[MyST markdown notebooks](myst-notebooks) +: These are markdown files (ending in `.md`) that will be *converted to a notebook and executed*. + +[reStructuredText](restructuredtext). +: These are text files used by the Sphinx documentation engine (which is used by Jupyter Book). + It is recommended to use MyST Markdown instead. + +[Custom Notebook Formats](file-types:custom) +: Any other file type can be *auto-converted* before execution, by assigning it a custom Python function, for example those provided by the Jupytext conversion tool. + +## Rules for all content types + +There are a few things that are true for all content types. Here is a short list: + +* **Files must have a title**. Generally this means that they must begin with + a line that starts with a single `#` +* **Use only one top-level header**. Because each page must have a clear + title, it must also only have one top-level header. You cannot have multiple + headers with single `#` tags in them. +* **Headers should increase linearly**. If you're inside of a section with + one `#`, then the next section lower should start with `##`. Avoid jumping straight + from `#` to `###`. + +## Two-way conversion between text-files and `.ipynb` files + +For information about how to convert between text files and `.ipynb` files for use +with Jupyter Book, see [](file-types:custom:jupytext). diff --git a/docs/file-types/jupytext.Rmd b/docs/file-types/jupytext.Rmd new file mode 100644 index 0000000000000000000000000000000000000000..aff51a187c83cfacb6dc671cc3041c02d6093104 --- /dev/null +++ b/docs/file-types/jupytext.Rmd @@ -0,0 +1,121 @@ +--- +jupyter: + kernelspec: + display_name: Python + language: python + name: python3 +--- + +(file-types:custom)= +# Custom notebook formats and Jupytext + +You can designate additional file types to be converted to Notebooks, and then executed / parsed in the same manner as regular Notebooks. + +:::{tip} +This page itself is written as an [RMarkdown](https://rmarkdown.rstudio.com/) notebook! +::: + +```yaml +sphinx: + config: + nb_custom_formats: + .mysuffix: mylibrary.converter_function +``` + +- The string should be a Python function that will be loaded by `import mylibrary.converter_function` +- The function should take a file's contents (as a `str`) and return an [nbformat.NotebookNode](nbformat:api) + +If the function takes additional keyword arguments, then you can specify these as dictionary in a second argument. +For example this is what the default conversion would look like: + +```yaml +sphinx: + config: + nb_custom_formats: + .ipynb: + - nbformat.reads + - as_version: 4 +``` + +:::{important} + +By default, Markdown cells in the Notebook will be parsed using the same MyST parser configuration as for other Markdown files. + +But, if this is incompatible with your file format, then you can specify for the Markdown to be parsed as **strictly CommonMark**, using a third argument: + +```yaml +sphinx: + config: + nb_custom_formats: + .ipynb: + - nbformat.reads + - as_version: 4 + - true +``` + +::: + +Finally, for text-based formats, MyST-NB also searches for an optional `source_map` key in the output Notebook's metadata. +This key should be a list mapping each cell to the starting line number in the original source file, for example for a notebook with three cells: + +```json +{ + "metadata": { + "source_map": [10, 21, 53] + } +} +``` + +This mapping allows for "true" error reporting, as described in [](myst-nb:start/error-reporting). + +## Using Jupytext + +[Jupytext](https://jupytext.readthedocs.io/en/latest/) is an excellent Python tool for two-way conversion, +between Jupyter Notebook `.ipynb` files and +[a variety of text-based files](https://jupytext.readthedocs.io/en/latest/formats.html). + +Jupyter Book natively supports the Jupytext file format: [notebooks with MyST Markdown](./myst-notebooks.md), but you can add other formats like [RMarkdown](https://rmarkdown.rstudio.com/) or Python files + +The configuration looks like: + +```yaml +sphinx: + config: + nb_custom_formats: + .ipynb: + - jupytext.read + - fmt: Rmd +``` + +Now you can use RMarkdown blocks: + + ```{python echo=TRUE} + print("Hallo I'm an RMarkdown block!") + ``` + +```{python echo=TRUE} +print("Hallo I'm an RMarkdown block!") +``` + +:::{important} +For full compatibility with `myst-parser`, it is necessary to use `jupytext==1.6.0rc0` or later. +::: + +(file-types:custom:jupytext)= +## Convert a Jupytext file into a MyST notebook + +Alternatively If you'd like to convert your pre-existing Jupytext files into the MyST Notebook format, +to use directly in your book, install Jupytext and then run the following command: + +```bash +jupytext --to myst path/to/yourfile +``` + +Note that you may also pass a wildcard that will be used to convert multiple +files. For example: + +```bash +jupytext --to myst ./*.py +``` + +See [the Jupytext CLI documentation](https://jupytext.readthedocs.io/en/latest/using-cli.html) for more information. diff --git a/docs/content-types/markdown.md b/docs/file-types/markdown.md similarity index 65% rename from docs/content-types/markdown.md rename to docs/file-types/markdown.md index 5bdbd9613017b54ea38be04949c73557812718e9..23297b38fb4f1bab108a6906822f56cbcafb569b 100644 --- a/docs/content-types/markdown.md +++ b/docs/file-types/markdown.md @@ -2,9 +2,9 @@ You can write content in regular markdown files (e.g., files ending in `.md`). Jupyter Book supports any markdown syntax that is supported by Jupyter Notebooks. -Jupyter Notebook markdown is a slight extension of a flavor of markdown called -[CommonMark Markdown](https://commonmark.org/). It has many elements -for standard text processing, though it lacks a lot of features used for +Jupyter Notebook markdown is an extension of a flavour of markdown called +[CommonMark Markdown](https://commonmark.org/). +It has many elements for standard text processing, though it lacks a lot of features used for publishing and documentation. ```{note} @@ -17,7 +17,7 @@ include them with your book. ```{margin} Jupyter Book also supports a fancier version of markdown called **MyST Markdown**. This -is a slightly extended flavor of Jupyter Notebook markdown. It +is a slightly extended flavour of Jupyter Notebook markdown. It allows you to include citations and cross-references, and control more complex functionality like adding content to the margin. For more information, check out {doc}`../content/myst`. @@ -33,29 +33,30 @@ so long as they point to a file that's inside of the repository. Here's an image relative to the book content root - + It was generated with this code: +```md + ``` - -``` + +:::{seealso} +[](../content/figures.md) for more information +::: ### Adding movies You can even embed references to movies on the web! For example, here's a little gif for you! - + This will be included in your book when it is built. - - ## Mathematics -Jupyter Book uses the excellent [MathJax](http://docs.mathjax.org/en/latest/) library, -along with the default Jupyter Notebook configuration, for rendering mathematics from -latex-style syntax. +For HTML outputs, Jupyter Book uses the excellent [MathJax](http://docs.mathjax.org/en/latest/) library, +along with the default Jupyter Notebook configuration, for rendering mathematics from latex-style syntax. For example, here's a mathematical expression rendered with MathJax: @@ -69,12 +70,16 @@ $$ \end{align} $$ +:::{seealso} +[](../content/math.md) for more information +::: + ### Block-level math You can include block-level math by wrapping your math in `$$` characters. For example, the following block: -``` +```md $$ wow = its^{math} $$ @@ -86,10 +91,10 @@ $$ wow = its^{math} $$ -You can also include math blocks by using latex-style syntax using -`\begin{align*}`. For example, the following block: +You can also include math blocks by using latex-style syntax using `\begin{align*}`. +For example, the following block: -``` +```latex \begin{align*} yep = its_more^math \end{align*} @@ -101,12 +106,18 @@ Results in: yep = its_more^math \end{align*} -For more information about math with Jupyter Book, see {doc}`../content/math`. +:::{important} +This requires the MyST extended syntax enabled in your `_config.yml`: + +```yaml +parse: + myst_extended_syntax: true +``` + +::: ## Extended markdown with MyST Markdown -In addition to CommonMark markdown, Jupyter Book also supports a more -fully-featured version of markdown called **MyST Markdown**. This is a slight -addition to CommonMark that includes syntactic pieces that are useful for -publishing computational narratives. For more information about MyST -Markdown, see {doc}`../content/myst`. +In addition to CommonMark markdown, Jupyter Book also supports a more fully-featured version of markdown called **MyST Markdown**. +This is a slight addition to CommonMark that includes syntactic pieces that are useful for publishing computational narratives. +For more information about MyST Markdown, see [](../content/myst.md). diff --git a/docs/content-types/myst-notebooks.md b/docs/file-types/myst-notebooks.md similarity index 85% rename from docs/content-types/myst-notebooks.md rename to docs/file-types/myst-notebooks.md index d68e5cc862c21694c4bd9b771d4a555996ad743f..31e9c4dac74764265b4e49d62880fcf6c208b8a5 100644 --- a/docs/content-types/myst-notebooks.md +++ b/docs/file-types/myst-notebooks.md @@ -3,8 +3,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -17,16 +15,13 @@ It is possible to store Jupyter Notebooks in pure markdown. This allows you to define a notebook structure entirely using MyST Markdown. For more information about MyST Markdown, see {doc}`../content/myst`. -Notebooks with markdown can be read in, executed, and cached by Jupyter Book (see {doc}`../content/execute` -for information on how to cache pages). This allows you to store all of your -notebook content in a text format that is much friendlier to version control, -while still having all the functionality of a Jupyter Notebook. +Notebooks with markdown can be read in, executed, and cached by Jupyter Book (see {doc}`../content/execute` for information on how to cache pages). +This allows you to store all of your notebook content in a text format that is much friendlier to version control, while still having all the functionality of a Jupyter Notebook. -```{note} -MyST notebooks use -[jupytext to convert between ipynb and text files][jupytext]. +:::{note} +MyST notebooks uses [MyST-NB to convert between ipynb and text files][myst-nb:index]. See its documentation for more information. -``` +::: To see an example of a MyST notebook, you can look at [many of the pages of this documentation](https://github.com/executablebooks/jupyter-book/tree/master/docs). @@ -34,7 +29,7 @@ For example, see {download}`../interactive/hiding.md` and {download}`../content/ ## Create a MyST notebook with Jupytext -The easiest way to create a MyST notebook is to use [Jupytext][jupytext], a tool +The easiest way to create a MyST notebook is to use [Jupytext](https://jupytext.readthedocs.io), a tool that allows for two-way conversion between `.ipynb` and a variety of text files. You can convert an `.ipynb` file to a MyST notebook with the following command: @@ -43,8 +38,12 @@ You can convert an `.ipynb` file to a MyST notebook with the following command: jupytext mynotebook.ipynb --to myst ``` -A resulting `mynotebook.md` file will be created. This can then be used as a page -in your book. +A resulting `mynotebook.md` file will be created. +This can then be used as a page in your book. + +:::{important} +For full compatibility with `myst-parser`, it is necessary to use `jupytext==1.6.0rc0` or later. +::: Jupytext can also **automatically synchronize an `.ipynb` file with your markdown**. To do so, use a Jupyter interface such as Jupyter Lab or the classic Notebook interface @@ -70,7 +69,6 @@ If you do not specify `--kernel`, then the default kernel will be used *if there only one available*. If there are multiple kernels available, you must specify one manually. - ## Structure of MyST notebooks Let's take a look at the structure that Jupytext creates, which you may also use @@ -83,8 +81,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -120,8 +116,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -140,7 +134,7 @@ Remember that Jupyter always defines one, and only one, kernel per notebook. Code blocks in MyST Notebooks are defined with the following MyST directive: -```` +````md ```{code-cell} your-code ``` @@ -150,7 +144,7 @@ You can optionally add extra metadata to the code cell, which will be converted into cell metadata in the `.ipynb` file. For example, you can add tags to your code cell like so: -```` +````md ```{code-cell} :tags: tag1, tag2, tag3 your-code @@ -160,7 +154,7 @@ your-code You may also explicitly pass the kernel name after `{code-cell}` to make it clear which kernel you are running. For example: -```` +````md ```{code-cell} python3 your-code ``` @@ -177,7 +171,7 @@ more information about MyST markdown. To explicitly split up markdown content into two markdown cells, use the following pattern: -```` +````md ```md Content in one markdown cell @@ -190,7 +184,7 @@ Content in another markdown cell You may also attach metadata to the cell by adding a Python dictionary after the `+++`. For example, to add tags to the second cell above: -```` +````md ```md Content in one markdown cell @@ -199,5 +193,3 @@ Content in one markdown cell Content in another markdown cell ``` ```` - -[jupytext]: https://jupytext.readthedocs.io/ diff --git a/docs/content-types/notebooks.ipynb b/docs/file-types/notebooks.ipynb similarity index 90% rename from docs/content-types/notebooks.ipynb rename to docs/file-types/notebooks.ipynb index 5b24eb2903d6194c464868ecc8202b3730d0e662..bcbe5475d0e814f99284fe5fa222ba8a1835aa0e 100644 --- a/docs/content-types/notebooks.ipynb +++ b/docs/file-types/notebooks.ipynb @@ -6,20 +6,18 @@ "source": [ "# Jupyter Notebook files\n", "\n", - "You can create content with Jupyter Notebooks. For example, the content for the current page is contained\n", - "in {download}`this notebook file <./notebooks.ipynb>`. \n", + "You can create content with Jupyter Notebooks.\n", + "For example, the content for the current page is contained in {download}`this notebook file <./notebooks.ipynb>`.\n", "\n", "```{margin}\n", "If you'd like to write in pure-text files, but still keep a notebook structure, you can write\n", "Jupyter Notebooks with MyST Markdown as well instead of `.ipynb`.\n", - "See {doc}`myst-notebooks` for more details.\n", + "See [](./myst-notebooks.md) for more details.\n", "```\n", "\n", "Jupyter Book supports all markdown that is supported by Jupyter Notebooks.\n", - "This is mostly a flavor of markdown called\n", - "[CommonMark Markdown](https://commonmark.org/) with minor modifications.\n", - "For more information about writing Jupyter-flavored markdown in Jupyter Book,\n", - "see {doc}`markdown`.\n", + "This is mostly a flavour of markdown called [CommonMark Markdown](https://commonmark.org/) with minor modifications.\n", + "For more information about writing Jupyter-flavoured markdown in Jupyter Book, see [](./markdown.md).\n", "\n", "## Code blocks and image outputs\n", "\n", @@ -42,7 +40,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [ + "remove-stdout" + ] + }, "outputs": [], "source": [ "# Fixing random state for reproducibility\n", @@ -78,7 +80,8 @@ "metadata": { "tags": [ "popout", - "remove-input" + "remove-input", + "remove-stdout" ] }, "outputs": [], @@ -101,7 +104,7 @@ "fig, ax = plt.subplots(figsize=(10, 5))\n", "lines = ax.plot(data)\n", "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot'])\n", - "ax.set(title=\"Smoother linez\");" + "ax.set(title=\"Smoother linez\")" ] }, { @@ -252,7 +255,6 @@ " icon=folium.Icon(color='red', icon='info-sign')\n", ").add_to(m)\n", "\n", - "\n", "m" ] }, @@ -308,9 +310,9 @@ "source": [ "## More features with Jupyter Notebooks\n", "\n", - "There are many other features of Jupyter Notebooks to take advantage of, such as\n", - "automatically generating Binder links for notebooks or connecting your content with\n", - "a kernel in the cloud. For more information browse the pages in this site.\n" + "There are many other features of Jupyter Notebooks to take advantage of,\n", + "such as automatically generating Binder links for notebooks or connecting your content with a kernel in the cloud.\n", + "For more information browse the pages in this site, particularly [](content:code-outputs)" ] } ], diff --git a/docs/content-types/restructuredtext.md b/docs/file-types/restructuredtext.md similarity index 64% rename from docs/content-types/restructuredtext.md rename to docs/file-types/restructuredtext.md index 6fb585d4ef7c281f829d08690227f2dc40397089..1228c4cddd8616e8b62fcd7f2031eb7bda0c0454 100644 --- a/docs/content-types/restructuredtext.md +++ b/docs/file-types/restructuredtext.md @@ -4,11 +4,12 @@ In addition to writing your content in markdown, Jupyter Book also supports writing content in [reStructuredText](https://docutils.sourceforge.io/rst.html), another markup language that is common in the Python documentation community. -```{warning} +:::{warning} Writing content in reStructuredText is only recommended for users who are already -familiar with it. For others, we recommend using {doc}`MyST markdown<../content/myst>`, -which has all of the same features of rST and Sphinx, but with a markdown flavor. -``` +familiar with it. +For others, we recommend using [MyST markdown](../content/myst.md), +which has all of the same features of rST and Sphinx, but with a markdown flavour. +::: Because Jupyter Book uses Sphinx under the hood, any document that is written in rST for the Sphinx ecosystem should also work with Jupyter Book. This is particularly @@ -17,3 +18,25 @@ and you'd like to try it out with Jupyter Book. For more information on writing content with reStructuredText, we recommend reading [the Sphinx rST documentation](https://www.sphinx-doc.org/es/stable/rest.html). + +## Including reStructuredText in Markdown + +To include rST into Markdown, you can use the [eval-rst directive](myst-parser:syntax/directives/parsing): + +````md +```{eval-rst} +.. note:: + + A note written in reStructuredText. + +.. include:: ./include-rst.rst +``` +```` + +```{eval-rst} +.. note:: + + A note written in reStructuredText. + +.. include:: ./include-rst.rst +``` diff --git a/docs/images/fun-fish.png b/docs/images/fun-fish.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a49971ad34426c1fef48e4efd27fbf67527adc Binary files /dev/null and b/docs/images/fun-fish.png differ diff --git a/docs/images/metadata_edit.gif b/docs/images/metadata_edit.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0cf199b9d99227bb515ab7ae3147d16696ef8b5 Binary files /dev/null and b/docs/images/metadata_edit.gif differ diff --git a/docs/interactive/hiding.md b/docs/interactive/hiding.md index a6c19ee3cdc533b51410e6fcdb70aaef803a0b5b..9d2180c27378f51a018a73545e52d38e4d779e22 100644 --- a/docs/interactive/hiding.md +++ b/docs/interactive/hiding.md @@ -3,8 +3,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -15,8 +13,8 @@ kernelspec: It's possible to control which content shows up in your book. For example, you may want to display a complex visualization to illustrate an idea, but don't -want the page to be cluttered with a large code cell that generated the viz. In other -cases, you may want to remove a code cell entirely. +want the page to be cluttered with a large code cell that generated the viz. +In other cases, you may want to remove a code cell entirely. In this case, you have two options: @@ -31,6 +29,10 @@ There are two ways to hide content: We'll cover each below. +:::{seealso} +[](jupyter-cell-tags) +::: + ## Hide markdown using MyST markdown There are two ways to hide markdown content @@ -46,7 +48,7 @@ and hide the content. We cover each below. You can activate this behavior in markdown with the `{toggle}` directive like so: -```` +````md ```{toggle} Some hidden toggle content! @@ -65,7 +67,7 @@ Some hidden toggle content! Note that if you'd like to **show the toggle content by default**, you can add the `:show:` flag when you call `{toggle}`, like so: -```` +````md ```{toggle} Click the button to reveal! :show: Some hidden toggle content! @@ -81,7 +83,7 @@ dropdown blocks. Users will see the admonition title, but will need to click in order to reveal the content. To do so, add the `dropdown` class to any admonition. For example: -```` +````md ```{admonition} Click the button to reveal! :class: dropdown Some hidden toggle content! @@ -112,7 +114,6 @@ If an element is hidden, Jupyter Book will display a small button to the right o old location for the hidden element. If a user clicks the button, the element will be displayed. - ### Hide cell inputs If you add the tag `hide-input` to a cell, then Jupyter Book will hide the cell but diff --git a/docs/interactive/launchbuttons.ipynb b/docs/interactive/launchbuttons.ipynb index a88847f060a3e727e1dcb80e5804c474f035784c..268e9aa3da88d3e41cfefe59e56d117dcd69bae4 100644 --- a/docs/interactive/launchbuttons.ipynb +++ b/docs/interactive/launchbuttons.ipynb @@ -36,8 +36,8 @@ " notebook_interface: \"jupyterlab\" # or \"classic\"\n", "```\n", "\n", - "One thing to take into account when choosing the interface is that notebooks written in the [MyST Markdown](../content-types/myst-notebooks.md) text-based format will not be opened as notebooks out-of-the-box.\n", - "If you wish for these files to be opened as notebooks then firstly you must ensure that [`jupytext[myst]`](https://jupytext.readthedocs.io/en/latest/formats.html#myst-markdown) is installed in the Binder/JupyterHub environment for your book (no support for this feature exist in Google Colab). You then have two options:\n", + "One thing to take into account when choosing the interface is that notebooks written in the [MyST Markdown](../file-types/myst-notebooks.md) text-based format will not be opened as notebooks out-of-the-box.\n", + "If you wish for these files to be opened as notebooks then firstly you must ensure that [`jupytext>=0.16`](https://jupytext.readthedocs.io/en/latest/formats.html#myst-markdown) is installed in the Binder/JupyterHub environment for your book (no support for this feature exist in Google Colab). You then have two options:\n", "\n", "- Use the \"classic\" interface, which will then immediately open these files as notebooks.\n", "- The \"jupyterlab\" interface (at the time of writing) has not yet implemented this behaviour, and so you will need to instruct readers to right-click the markdown file and click \"Open in notebook editor\"." diff --git a/docs/intro.md b/docs/intro.md index 70dc4d636bb54f5d2f2dab3d5c54a97365a558c7..d5f23aa9d10aaa453e24aca73a15350fa0c662aa 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -5,38 +5,41 @@ publication-quality books and documents from computational material. Here are some of the features of Jupyter Book: -{fa}`check,text-success mr-1` **[Write publication-quality content in markdown](content-types/markdown)**<br /> -You can write in either Jupyter markdown, or an extended flavor of markdown with [publishing features](content/myst). -This includes support for rich syntax such as [citations and cross-references](content/citations), [math and equations](content/math), and [figures](content/figures). +{fa}`check,text-success mr-1` [Write publication-quality content in markdown](file-types/markdown) +: You can write in either Jupyter markdown, or an extended flavor of markdown with [publishing features](content/myst). + This includes support for rich syntax such as [citations and cross-references](content/citations), [math and equations](content/math), and [figures](content/figures). -{fa}`check,text-success mr-1` **[Write content in Jupyter Notebooks](content-types/notebooks)**<br /> -This allows you to include your code and outputs in your book. -You can also write notebooks [entirely in markdown](content-types/myst-notebooks) to execute when you build your book. +{fa}`check,text-success mr-1` [Write content in Jupyter Notebooks](file-types/notebooks) +: This allows you to include your code and outputs in your book. + You can also write notebooks [entirely in markdown](file-types/myst-notebooks) to execute when you build your book. -{fa}`check,text-success mr-1` **[Execute and cache your book's content](content/execute)**<br /> -For `.ipynb` and markdown notebooks, execute code and insert the latest outputs into your book. -In addition, {ref}`cache and re-use<execute/cache>` outputs to be used later. +{fa}`check,text-success mr-1` [Execute and cache your book's content](content/execute) +: For `.ipynb` and markdown notebooks, execute code and insert the latest outputs into your book. + In addition, {ref}`cache and re-use<execute/cache>` outputs to be used later. -{fa}`check,text-success mr-1` **[Insert notebook outputs into your content](content/glue)**<br /> -Generate outputs as you build your documentation, and insert them in-line with your content across pages. +{fa}`check,text-success mr-1` [Insert notebook outputs into your content](content:code-outputs) +: Generate outputs as you build your documentation, and insert them in-line with your content across pages. -{fa}`check,text-success mr-1` **[Add interactivity to your book](interactive/launchbuttons)**<br /> -You can [toggle cell visibility](interactive/hiding), include [interactive outputs](interactive/interactive) from Jupyter, and [connect with online services](interactive/launchbuttons) like Binder. +{fa}`check,text-success mr-1` [Add interactivity to your book](interactive/launchbuttons) +: You can [toggle cell visibility](interactive/hiding), include [interactive outputs](interactive/interactive) from Jupyter, and [connect with online services](interactive/launchbuttons) like Binder. -{fa}`check,text-success mr-1` **[Generate a variety of outputs](start/build)**<br /> -This includes single- and multi-page websites, as well as [PDF outputs](advanced/pdf). +{fa}`check,text-success mr-1` [Generate a variety of outputs](start/build) +: This includes single- and multi-page websites, as well as [PDF outputs](advanced/pdf). -{fa}`check,text-success mr-1` **[Build books with a simple command-line interface](reference/cli)**<br /> -You can quickly generate your books with one command, like so: `jupyter-book build mybook/` +{fa}`check,text-success mr-1` [Build books with a simple command-line interface](reference/cli) +: You can quickly generate your books with one command, like so: `jupyter-book build mybook/` This website is built with Jupyter Book! You can browse its contents to the left to see what is possible. -```{admonition} Get involved with Jupyter Book! -:class: tip +:::{admonition,tip} Get involved with Jupyter Book! + Jupyter Book is an open community that welcomes your feedback, input, and contributions! -[Open an issue](https://github.com/executablebooks/jupyter-book/issues/new/choose) with any feedback. Give an issue a 👠if you think it should be resolved and check out {ref}`our enhancement voting leaderboard <ebp:feature-note>` for the issues people want to see completed! If you'd like to contribute, [check out our contributing guide](contribute/intro.md). -``` +[Open an issue](https://github.com/executablebooks/jupyter-book/issues/new/choose) with any feedback. +Give an issue a 👠if you think it should be resolved and check out {ref}`our enhancement voting leaderboard <ebp:feature-note>` for the issues people want to see completed! +If you'd like to contribute, [check out our contributing guide](contribute/intro.md). + +::: ## Get started @@ -47,18 +50,18 @@ To get started with Jupyter Book, you can either on a laptop), or * review the example project shown immediately below (if you like learning from examples). -```{warning} +:::{warning} Jupyter Book `0.7` is a total re-write from `0.6`, and some things have changed. See [the legacy upgrade guide](https://github.com/executablebooks/jupyter-book/wiki/The-Jupyter-Book-Wiki) for how to upgrade, and [legacy.jupyterbook.org](https://legacy.jupyterbook.org) for legacy documentation. In addition, note that Jupyter Book is pre-1.0. Its API may change! -``` +::: To install the `jupyter-book` pre-release from pip, run the following command: -``` +```bash pip install -U jupyter-book ``` @@ -88,7 +91,7 @@ You can build this book locally on the command line via the following steps: 2. Clone the repository containing the demo book source files - ``` + ```bash git clone https://github.com/executablebooks/quantecon-mini-example cd quantecon-mini-example ``` @@ -101,25 +104,25 @@ You can build this book locally on the command line via the following steps: ``` See [the getting started page](start/overview) for more information. ```` + 3. Install the Python libraries needed to run the code in this particular example from [the `environment.yml` file](https://github.com/executablebooks/quantecon-mini-example/blob/master/environment.yml). This includes the latest version of Jupyter Book: - ``` + ```bash conda env create -f environment.yml conda activate qe-mini-example ``` 4. Run Jupyter Book over the source files - ``` + ```bash jupyter-book build ./mini_book ``` 5. View the result through a browser --- try (with, say, firefox) - - ``` + ```bash firefox mini_book/_build/html/index.html ``` @@ -128,32 +131,32 @@ You can build this book locally on the command line via the following steps: Now you might like to try editing the files in ``mini_book/docs`` and then rebuilding. -**Further Reading** +### Further Reading See [the full QuantEcon example](https://executablebooks.github.io/quantecon-example/docs/index.html) for a longer Jupyter Book use case, drawn from the same source material. For more information on how to use Jupyter Book, see {doc}`start/overview`. +(intro/jupyter-book-components)= ## Under the hood - the components of Jupyter Book Jupyter Book is a wrapper around a collection of tools in the Python -ecosystem that make it easier to publish computational documents. Here are +ecosystem that make it easier to publish computational documents. +Here are a few key pieces: * It uses {term}`the MyST markdown language<MyST>` in - markdown and notebook documents. This allows users to write rich, - publication-quality markup in their documents. + markdown and notebook documents. + This allows users to write rich, publication-quality markup in their documents. * It uses {term}`the MyST-NB package<MyST-NB>` to parse and read-in notebooks so they are built into your book. * It uses {term}`the Sphinx documentation engine<Sphinx>` to build outputs from your book's content. -* It uses a slightly modified version of the [PyData Sphinx theme](https://pydata-sphinx-theme.readthedocs.io/en/latest/) - for beautiful HTML output. +* It uses a slightly modified version of the [PyData Sphinx theme](https://pydata-sphinx-theme.readthedocs.io/en/latest/) for beautiful HTML output. * It uses a collection of Sphinx plugins and tools to add new functionality. -For more information about the project behind many of these tools, see -[The Executable Book Project](https://ebp.jupyterbook.org/) documentation. +For more information about the project behind many of these tools, see [The Executable Book Project](https://ebp.jupyterbook.org/) documentation. ## Contribute to Jupyter Book diff --git a/docs/publish/gh-pages.md b/docs/publish/gh-pages.md index 41d778ec3ceb40136a463463071e5f9a01f08cf4..18910ace2f8e3ab39f368c109a4f877af89cfc95 100644 --- a/docs/publish/gh-pages.md +++ b/docs/publish/gh-pages.md @@ -1,3 +1,4 @@ +(publish/gh-pages)= # GitHub Pages and Actions Once your content is on GitHub, you can easily host it as a [GitHub Pages](https://docs.github.com/en/github/working-with-github-pages) website. This is a service where GitHub hosts your static files as if they were a standalone website. @@ -23,12 +24,13 @@ files in your book's `_build/html` folder. 1. Install `ghp-import` - ``` + ```bash pip install ghp-import ``` + 2. From the `master` branch of your book's root directory (which should contain the `_build/html` folder) call `ghp-import` and point it to your HTML files, like so: - ``` + ```bash ghp-import -n -p -f mylocalbook/_build/html ``` @@ -54,7 +56,7 @@ It is used for a variety of things, such as testing, publishing packages, and co Note that if you're not hosting your book on GitHub, or if you'd like another, user-friendly service to build it automatically, -see the {doc}`guide to publishing your book on Netlify <../netlify>`. +see the [guide to publishing your book on Netlify](publish/netlify). ```{note} You should be familiar with GitHub Actions before using them diff --git a/docs/publish/netlify.md b/docs/publish/netlify.md index 15b7d48e4b635f182552c1624c5f2a40781ec63a..43977252eb4c71c8b509a7a27b15a715dfa7211c 100644 --- a/docs/publish/netlify.md +++ b/docs/publish/netlify.md @@ -1,3 +1,4 @@ +(publish/netlify)= # Publish with Netlify [Netlify](https://www.netlify.com/) is a continuous deployment service that can @@ -7,9 +8,9 @@ It can be used across git clients including GitHub, GitLab, and Bitbucket. Note that these instructions assume you're keeping your source files under version-control, rather than the built Jupyter Book HTML. If you're pushing your HTML to GitHub, -you'll want to {doc}`host your book on GitHub Pages <../gh-pages>` instead. +you'll want to [host your book on GitHub Pages](publish/gh-pages) instead. -Although Netlify has both free and paid tiers, the build process is the same across both. +Although Netlify has both free and paid tiers, the build process is the same across both Importantly, the free tier only allows for 100GB of bandwidth usage per month across all of your Netlify built projects. ```{margin} @@ -46,7 +47,7 @@ For the purposes of this tutorial, we'll assume that your book is hosted on GitH When you select the "GitHub" option, you'll be asked to grant permission for Netlify to access your GitHub account. Authorizing access will take you to the next step of the build process, where you can select your Jupyter Book repository. - + ## Step 2: Add the command to install and build your book diff --git a/docs/reference/_changelog.md b/docs/reference/_changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..d24e7ac0718f3e54c31bd9bbe1d98fb63ae03f2d --- /dev/null +++ b/docs/reference/_changelog.md @@ -0,0 +1,4 @@ +```{include} ../../CHANGELOG.md +:relative-docs: docs/ +:relative-images: +``` diff --git a/docs/reference/cheatsheet.md b/docs/reference/cheatsheet.md index b0139751fad66d1781809cc7e2d62e191ad3230f..058593dae242a56eab789f9e42236851fb5a534d 100644 --- a/docs/reference/cheatsheet.md +++ b/docs/reference/cheatsheet.md @@ -4,8 +4,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -33,7 +31,7 @@ kernelspec: ###### Heading level 6 ``` - ```md - # MyST Cheat Sheet + # MyST Cheat Sheet ``` - Level 1-6 headings, denoted by number of `#` `````` @@ -61,14 +59,19 @@ kernelspec: ### Referencing Target Headers Targets can be referenced with the [ref inline role](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-ref) which by default uses the section title: + ```md {ref}`myst_cheatsheet` ``` + You can specify the text of the target: + ```md {ref}`MyST syntax lecture <myst_cheatsheet>` ``` + Another alternative is to use markdown syntax: + ```md [MyST syntax lecture](myst_cheatsheet) ``` @@ -277,7 +280,6 @@ Another alternative is to use markdown syntax: 2. Second subitem `````` - ## Tables ``````{list-table} @@ -612,7 +614,7 @@ Content is not permitted in image directive. ``` `````` -See {doc}`../content/figures` and {doc}`../content-types/markdown` for more information. +See {doc}`../content/figures` and {doc}`../file-types/markdown` for more information. ### Referencing Figures @@ -764,6 +766,7 @@ See {doc}`../content/math` for more information. ### Inline Code **Example**: + ```md Wrap inline code blocks in backticks: `boolean example = true;`. ``` @@ -775,13 +778,16 @@ Wrap inline code blocks in backticks: `boolean example = true;`. ### Code and Syntax Highlighting **Example**: + ````md ```python note = "Python syntax highlighting" print(node) ``` ```` + or + ````md ``` No syntax highlighting if no language @@ -790,11 +796,14 @@ is indicated. ```` **Result**: + ```python note = "Python syntax highlighting" print(node) ``` + or + ``` No syntax highlighting if no language is indicated. @@ -811,8 +820,6 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: 1.4.1+dev kernelspec: display_name: Python 3 language: python @@ -822,6 +829,7 @@ kernelspec: ```` **Example**: + ````md ```{code-cell} ipython3 note = "Python syntax highlighting" @@ -830,12 +838,13 @@ print(note) ```` **Result**: + ```{code-cell} ipython3 note = "Python syntax highlighting" print(note) ``` -See {doc}`../content-types/myst-notebooks` for more information. +See {doc}`../file-types/myst-notebooks` for more information. #### Tags @@ -934,6 +943,7 @@ The following `tags` can be applied to code cells by introducing them as options ### Gluing Variables **Example**: + ``````md ```{code-cell} ipython3 from myst_nb import glue @@ -945,6 +955,7 @@ Here is an example of how to glue text: {glue:}`glued_text` `````` **Result**: + ```{code-cell} ipython3 from myst_nb import glue my_variable = "here is some text!" @@ -953,12 +964,12 @@ glue("glued_text", my_variable) Here is an example of how to glue text: {glue:}`glued_text` - See {ref}`glue/gluing` for more information. ### Gluing Numbers **Example**: + ``````md ```{code-cell} ipython3 from myst_nb import glue @@ -976,6 +987,7 @@ Here is an example of how to glue numbers: {glue:}`ss_mean` and {glue:}`ns_mean` `````` **Result**: + ```{code-cell} ipython3 from myst_nb import glue import numpy as np @@ -990,12 +1002,12 @@ glue("ns_mean", ns.mean(), display=False) Here is an example of how to glue numbers: {glue:}`ss_mean` and {glue:}`ns_mean`. - See {ref}`glue/gluing` for more information. ### Gluing Visualizations **Example**: + ``````md ```{code-cell} ipython3 from myst_nb import glue @@ -1017,6 +1029,7 @@ This is an example of pasting a glued output as a block: `````` **Result**: + ```{code-cell} ipython3 from myst_nb import glue import matplotlib.pyplot as plt @@ -1032,6 +1045,7 @@ glue("glued_fig", fig, display=False) This is an inline glue example of a figure: {glue:}`glued_fig`. This is an example of pasting a glued output as a block: + ```{glue:} glued_fig ``` @@ -1040,6 +1054,7 @@ See {ref}`glue/gluing` for more information. ### Gluing Math **Example**: + ``````md ```{code-cell} ipython3 import sympy as sym @@ -1056,6 +1071,7 @@ To glue a math equation try `````` **Result**: + ```{code-cell} ipython3 import sympy as sym x, y = sym.symbols('x y') @@ -1064,12 +1080,13 @@ z = sym.sqrt(x**2+y**2) glue("example_eq", z, display=False) ``` -To glue a math equation try +To glue a math equation try: + ```{glue:math} example_eq :label: glue-eq-example ``` -See {doc}`../content/glue` for more information. +See [](content:code-outputs:glue) for more information. ## Reference Documents @@ -1161,6 +1178,7 @@ or view a {download}`references.bib <../references.bib>` example. `````` To include a list of citations mentioned in the document, introduce the `bibliography` directive + ``````md ```{bibliography} ../references.bib :filter: docname in docnames diff --git a/docs/reference/cli.rst b/docs/reference/cli.md similarity index 60% rename from docs/reference/cli.rst rename to docs/reference/cli.md index fa5534e7331f481b4a5714d86a067d83c8eab093..58f9c1f3ff0085cfa1ba9d49ab25df5dbd568d2a 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.md @@ -1,21 +1,24 @@ -The command-line interface -========================== +# The command-line interface Jupyter Book comes with a command-line interface that makes it easy to build your books and run a few common functions. This page contains information about what you can do with the CLI. This page is a complete reference for the CLI. For newcomers who would like to -get started with the Jupyter Book CLI, we recommend starting with :doc:`../start/overview`. +get started with the Jupyter Book CLI, we recommend starting with [](../start/overview.md) -.. note:: +:::{note} - You may also use a short-hand for ``jupyter-book`` in the command-line - interface: ``jb``. For example: ``jupyter-book build mybook/` is equivalent - to ``jb build mybook/`` +You may also use a short-hand for ``jupyter-book`` in the command-line +interface: ``jb``. +For example: `jupyter-book build mybook/` is equivalent to ``jb build mybook/``. + +::: **See below for the full command-line reference** +```{eval-rst} .. click:: jupyter_book.commands:main :prog: jupyter-book - :show-nested: + :nested: full +``` diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index 24b666275f6a283d1336b7d8bb9b9b6c1f1c2abb..0d54fc70dc809282e1c7c99dbb342fd8bcd613ce 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -5,18 +5,18 @@ A glossary of common terms used throughout Jupyter Book. ```{glossary} [CommonMark](https://commonmark.org/) A standard syntax of markdown that is used across many communities and projects. - It is the base flavor of markdown for Jupyter Notebooks, and the base flavor + It is the base flavour of markdown for Jupyter Notebooks, and the base flavour for {term}`MyST Markdown <MyST>` and Jupyter Book. [ExecutableBookProject](https://executablebooks.org/en/latest/) The project that supports and develops many of the core tools used by Jupyter Book. [MyST](https://myst-parser.readthedocs.io/en/latest/using/syntax.html) - A flavor of markdown that was designed for use with the {term}`sphinx` project. + A flavour of markdown that was designed for use with the {term}`Sphinx` project. It is a combination of {term}`CommonMark Markdown <CommonMark>` and a few extra syntax pieces to support features of Sphinx, so that you can write Sphinx documentation in 100% markdown. It is one of the - core techologies that Jupyter Book uses. + core technologies that Jupyter Book uses. [MyST-Parser](https://myst-parser.readthedocs.io/en/latest/) A parser for {term}`Sphinx` that allows it to read in content that is written @@ -27,12 +27,12 @@ A glossary of common terms used throughout Jupyter Book. An extension for {term}`Sphinx` that uses the {term}`MyST Parser <MyST>` to parse Jupyter Notebooks directly into Sphinx. This also allows users to write MyST Markdown inside of notebooks that are parsed with Sphinx. It is one of the - core techologies that Jupyter Book uses. + core technologies that Jupyter Book uses. [Sphinx](https://www.sphinx-doc.org/en/master/) A documentation engine written in Python. Sphinx supports many features that are necessary for scientific and scholarly publishing. It is one of the - core techologies that Jupyter Book uses. + core technologies that Jupyter Book uses. [Binder](https://mybinder.org) A free, public service for running reproducible interactive computing environments. @@ -54,7 +54,7 @@ A glossary of common terms used throughout Jupyter Book. users. It can be deployed in the cloud, or on your own hardware. [Jupyter-Cache](https://github.com/executablebooks/jupyter-cache) - An open source tool for executing and cacheing the outputs of Jupyter Notebook + An open source tool for executing and caching the outputs of Jupyter Notebook content. Outputs are cached in a hidden folder so that they do not need to be included directly with your source files. diff --git a/docs/requirements.txt b/docs/requirements.txt index 9272d9c0071270adf7d9d94b647231022f9bb203..c942984df8fec33e924a099a7896f8d47e28c051 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -11,5 +11,5 @@ bokeh altair sphinx-click sphinx_tabs -sphinx-panels -jupytext[myst] +jupytext==1.6.0rc0 +sphinxext-rediraffe~=0.2.1 diff --git a/docs/start/build.md b/docs/start/build.md index 1180ab882ad11292908a48539aaf5301bbf10d53..3a4d6efd0ef61d73a6308c59ff130d7963623bfa 100644 --- a/docs/start/build.md +++ b/docs/start/build.md @@ -1,8 +1,8 @@ # Build your book Once you've added content and configured your book, it's time to -build outputs for your book. We'll use the -`jupyter-book build` command-line tool for this. +build outputs for your book. +We'll use the `jupyter-book build` command-line tool for this. Currently, there are two kinds of supported outputs: an HTML website for your book, and a PDF that contains all of the pages of your book that is built @@ -11,16 +11,14 @@ from the book HTML. ## Prerequisites In order to build the HTML for each page, you should have followed the steps -in {doc}`creating your Jupyter Book structure <overview>`. You should have -a collection of notebook/markdown files in your `mybookname/` folder, a `_toc.yml` file +in [creating your Jupyter Book structure](./overview.md). +You should have a collection of notebook/markdown files in your `mybookname/` folder, a `_toc.yml` file that defines the structure of your book, and any configuration you'd like in the `_config.yml` file. ## Build your book's HTML -Now that your book's content is in your book folder and you've -defined your book's structure in `_toc.yml`, you can build -the HTML for your book. +Now that your book's content is in your book folder and you've defined your book's structure in `_toc.yml`, you can build the HTML for your book. **Note:** HTML is the default builder. @@ -35,42 +33,60 @@ The site will be placed in the `_build/html` folder. You can then open the pages in the site by entering that folder and opening the `html` files with your web browser. -```{note} +:::{note} You can also use the short-hand `jb` for `jupyter-book`. E.g.,: `jb build mybookname/`. +::: + +:::{tip} +When debugging your book build, the following options can be helpful: + +```bash +jupyter-book build -W -n --keep-going mybookname/ ``` +This will check for missing references (`-n`), turning them into errors (`-W`), +but will still attempt to run the full build (`--keep-going`), +so that you can see all errors in one run. + +You can also use `-v` or `-vvv` to increase verbosity. +::: + ## Build a standalone page -Sometimes you'd like to build a single page of content rather than an -entire book. For example, if you'd like to generate a web-friendly HTML -page from a Jupyter Notebook for a report or publication. +Sometimes you'd like to build a single page of content rather than an entire book. +For example, if you'd like to generate a web-friendly HTML page from a Jupyter Notebook for a report or publication. -You can generate a standalone HTML file for a single page of the Jupyter Book using the same command : +You can generate a standalone HTML file for a single page of the Jupyter Book using the same command: -``` +```bash jupyter-book build path/to/mypage.ipynb ``` This will execute your content and output the proper HTML in a `_build/_page/html/<mypage>` folder. If the file is in a subdirectory relative to the `_build` folder, the HTML will be in a `_build/_page/html/<subdirectory-mypage>` folder. -Your page will be called `mypage.html`. This will work -for any {doc}`content source file <../content-types/index>` that is supported by Jupyter Book. +Your page will be called `mypage.html`. +This will work for any [content source file](../file-types/index.md) that is supported by Jupyter Book. -```{note} +:::{note} Users should note that building **single pages** in the context of a larger project, can trigger warnings and incomplete links. -For example, building `docs/start/overview.md` will issue a bunch of `unknown document`,`term not in glossary`, and `undefined links` warnings. -``` +For example, building `docs/start/overview.md` will issue a number of `unknown document`,`term not in glossary`, and `undefined links` warnings. +::: ## Page caching By default, Jupyter Book will only build the HTML for pages that have -been updated since the last time you built the book. This helps reduce the -amount of unnecessary time needed to build your book. If you'd like to -force Jupyter Book to re-build a particular page, you can either edit the -corresponding file in your book's folder, or delete that page's HTML -in the `_build/html` folder. +been updated since the last time you built the book. +This helps reduce the amount of unnecessary time needed to build your book. +If you'd like to force Jupyter Book to re-build a particular page, you can either edit the +corresponding file in your book's folder, or delete that page's HTML in the `_build/html` folder. + +You can also signal a full re-build using the `--all` option: + +```bash +jupyter-book build --all mybookname/ +``` ## Local preview @@ -81,5 +97,5 @@ path to the file in your browser navigation bar adding `file://` at the beginnin ## Next step: publish your book -Now that you've created the HTML for your book, it's time -to publish it online. That's covered in the next section. +Now that you've created the HTML for your book, it's time to publish it online. +That's covered in the [next section](./publish.md). diff --git a/docs/start/overview.md b/docs/start/overview.md index a3b40750db088f3de74435af9e0097efe51b5795..a90dfdf1aa5c5846cbe0366795d1d814276c71cb 100644 --- a/docs/start/overview.md +++ b/docs/start/overview.md @@ -15,14 +15,15 @@ Jupyter Book. You can install it via pip with the following command: pip install -U jupyter-book ``` -```{admonition} A note for windows users -:name: warning -Currently, Jupyter Book is tested in a unix-like environment, and it is highly -recommended that you use a unix environment to build your books. If you're running -a recent version of Windows 10, we recommend -[installing Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). -If you'd like to help with adding Windows support, please [say hello in this issue](https://github.com/executablebooks/jupyter-book/issues/575). -``` +:::{admonition,warning} A note for Windows users + +Jupyter Book is now also tested against Windows OS 😀 + +However, there is a known incompatibility for notebook execution, when using Python 3.8. + +See [](working-on-windows) + +::: ## The book building process @@ -51,10 +52,10 @@ the pieces that you'll modify for your own book. Running the following command will create a new Jupyter Book with a few content pages and a Table of Contents to get you started: -```{note} +:::{note} Jupyter Book uses a command-line interface to generate books. For more complete -information about the CLI, see {doc}`../reference/cli`. -``` +information about the CLI, see [](../reference/cli.md). +::: ### Create a template Jupyter Book @@ -188,7 +189,6 @@ You can store these files in whatever collection of folders you'd like, note tha the *structure* of your book when it is built will depend solely on the order of items in your `_toc.yml` file (see below section) - ### Book bibliography for citations If you'd like to build a bibliography for your book, you can do so by including @@ -200,8 +200,7 @@ mybookname/ ``` This BiBTex file can be used to insert citations into your book's pages. For more information, -see {doc}`../content/citations`. - +see [](../content/citations.md). ## Next step: build your book diff --git a/jupyter_book/__init__.py b/jupyter_book/__init__.py index c8f5aad878b963dc60a89882ae78ce0b20e26df9..49a597ca855fa1b8286a21494a3a0fffb4349e85 100644 --- a/jupyter_book/__init__.py +++ b/jupyter_book/__init__.py @@ -5,7 +5,7 @@ from .toc import add_toc_to_sphinx, add_toctree from .directive.toc import TableofContents, SwapTableOfContents -__version__ = "0.7.5" +__version__ = "0.8.0" def add_static_files(app, config): @@ -14,9 +14,9 @@ def add_static_files(app, config): for path in static_paths: path = Path(app.confdir).joinpath(path) for path_css in path.rglob("*.css"): - app.add_css_file(str(path_css.relative_to(path))) + app.add_css_file((path_css.relative_to(path)).as_posix()) for path_js in path.rglob("*.js"): - app.add_js_file(str(path_js.relative_to(path))) + app.add_js_file((path_js.relative_to(path)).as_posix()) # We connect this function to the step after the builder is initialized diff --git a/jupyter_book/commands/__init__.py b/jupyter_book/commands/__init__.py index 4c24043a754d8ecd2f780372cdf8fa378e2e0c40..f81aa79638f960634f6d092ceb632c28311f6ba3 100644 --- a/jupyter_book/commands/__init__.py +++ b/jupyter_book/commands/__init__.py @@ -1,39 +1,56 @@ -"""Defines the commands that the CLI will use.""" -import sys +"""Defines the commands that the CLI will use. + +IMPORTANT: Top-level imports should be minimised here, to improve CLI responsiveness +""" +from glob import glob import os import os.path as op from pathlib import Path -import click -from glob import glob import shutil as sh import subprocess +import sys from textwrap import dedent -from sphinx.util.osutil import cd -from sphinx.util import logging - -from ..sphinx import build_sphinx, REDIRECT_TEXT -from ..toc import build_toc -from ..pdf import html_to_pdf -from ..utils import _message_box, _error, init_myst_file -from .. import __version__ as jbv -from sphinx_book_theme import __version__ as sbtv -from myst_nb import __version__ as mnbv -from myst_parser import __version__ as mpv -from jupyter_cache import __version__ as jcv - -versions = { - "Jupyter Book": jbv, - "MyST-NB": mnbv, - "Sphinx Book Theme": sbtv, - "MyST-Parser": mpv, - "Jupyter-Cache": jcv, -} -versions_string = "\n".join(f"{tt}: {vv}" for tt, vv in versions.items()) -logger = logging.getLogger(__name__) +from typing import Tuple + +import click +from ..utils import _message_box, _error + + +def version_callback(ctx, param, value): + """Callback for supplying version information""" + if not value or ctx.resilient_parsing: + return -@click.group() -@click.version_option(message=versions_string) + from .. import __version__ as jbv + from sphinx_book_theme import __version__ as sbtv + from myst_nb import __version__ as mnbv + from myst_parser import __version__ as mpv + from jupyter_cache import __version__ as jcv + from nbclient import __version__ as ncv + + versions = { + "Jupyter Book": jbv, + "MyST-NB": mnbv, + "Sphinx Book Theme": sbtv, + "MyST-Parser": mpv, + "Jupyter-Cache": jcv, + "NbClient": ncv, + } + versions_string = "\n".join(f"{tt}: {vv}" for tt, vv in versions.items()) + click.echo(versions_string) + ctx.exit() + + +@click.group(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "--version", + is_flag=True, + expose_value=False, + is_eager=True, + help="Show the version and exit.", + callback=version_callback, +) def main(): """Build and manage books with Jupyter.""" pass @@ -50,26 +67,79 @@ BUILDER_OPTS = { @main.command() @click.argument("path-source", type=click.Path(exists=True, file_okay=True)) @click.option("--path-output", default=None, help="Path to the output artifacts") -@click.option("--config", default=None, help="Path to the YAML configuration file") -@click.option("--toc", default=None, help="Path to the Table of Contents YAML file") +@click.option( + "--config", + default=None, + help="Path to the YAML configuration file (default: PATH_SOURCE/_config.yml)", +) +@click.option( + "--toc", + default=None, + help="Path to the Table of Contents YAML file (default: PATH_SOURCE/_toc.yml)", +) @click.option("-W", "--warningiserror", is_flag=True, help="Error on warnings.") +@click.option( + "-n", + "--nitpick", + is_flag=True, + help="Run in nit-picky mode, to generates warnings for all missing references.", +) +@click.option( + "--keep-going", + is_flag=True, + help="With -W, do not stop the build on the first warning, " + "instead error on build completion", +) +@click.option( + "--all", + "freshenv", + is_flag=True, + help="Re-build all pages. " + "The default is to only re-build pages that are new/changed since the last run.", +) @click.option( "--builder", default="html", help="Which builder to use.", type=click.Choice(list(BUILDER_OPTS.keys())), ) -def build(path_source, path_output, config, toc, warningiserror, builder): +@click.option( + "-v", "--verbose", count=True, help="increase verbosity (can be repeated)" +) +@click.option( + "-q", + "--quiet", + count=True, + help="-q means no sphinx status, -qq also turns off warnings ", +) +def build( + path_source, + path_output, + config, + toc, + warningiserror, + nitpick, + keep_going, + freshenv, + builder, + verbose, + quiet, + get_config_only=False, +): """Convert your book's or page's content to HTML or a PDF.""" + from .. import __version__ as jbv + from ..sphinx import build_sphinx + + if not get_config_only: + click.secho(f"Running Jupyter-Book v{jbv}", bold=True, fg="green") + # Paths for the notebooks PATH_SRC_FOLDER = Path(path_source).absolute() config_overrides = {} - freshenv = False - BUILD_PATH = ( - path_output if path_output is not None else find_config_path(PATH_SRC_FOLDER) - ) + found_config = find_config_path(PATH_SRC_FOLDER) + BUILD_PATH = path_output if path_output is not None else found_config[0] if not PATH_SRC_FOLDER.is_dir(): # it is a single file build_type = "page" @@ -131,29 +201,47 @@ def build(path_source, path_output, config, toc, warningiserror, builder): # If the toc file has been modified after the build we need to force rebuild freshenv = toc_modified > build_modified - config_overrides["globaltoc_path"] = str(toc) + config_overrides["globaltoc_path"] = toc.as_posix() # Builder-specific overrides if builder == "pdfhtml": config_overrides["html_theme_options"] = {"single_page": True} - # Configuration file - path_config = config - if path_config is None: - # Check if there's a `_config.yml` file in the source directory - if PATH_SRC_FOLDER.joinpath("_config.yml").exists(): - path_config = str(PATH_SRC_FOLDER.joinpath("_config.yml")) - if path_config: - if not Path(path_config).exists(): - raise ValueError(f"Config file path given, but not found: {path_config}") + # Use the specified configuration file, or one found in the root directory + path_config = config or ( + found_config[0].joinpath("_config.yml") if found_config[1] else None + ) + if path_config and not Path(path_config).exists(): + raise IOError(f"Config file path given, but not found: {path_config}") if builder in ["html", "pdfhtml"]: OUTPUT_PATH = BUILD_PATH.joinpath("html") elif builder in ["latex", "pdflatex"]: OUTPUT_PATH = BUILD_PATH.joinpath("latex") + if nitpick: + config_overrides["nitpicky"] = True + + # If we only wan config (e.g. for printing/validation), stop here + if get_config_only: + return (path_config, PATH_SRC_FOLDER, config_overrides) + + # print information about the build + click.echo( + click.style("Source Folder: ", bold=True, fg="blue") + + click.format_filename(f"{PATH_SRC_FOLDER}") + ) + click.echo( + click.style("Config Path: ", bold=True, fg="blue") + + click.format_filename(f"{path_config}") + ) + click.echo( + click.style("Output Path: ", bold=True, fg="blue") + + click.format_filename(f"{OUTPUT_PATH}") + ) + # Now call the Sphinx commands to build - exc = build_sphinx( + result = build_sphinx( PATH_SRC_FOLDER, OUTPUT_PATH, noconfig=True, @@ -161,10 +249,16 @@ def build(path_source, path_output, config, toc, warningiserror, builder): confoverrides=config_overrides, builder=BUILDER_OPTS[builder], warningiserror=warningiserror, + keep_going=keep_going, freshenv=freshenv, + verbosity=verbose, + quiet=quiet > 0, + really_quiet=quiet > 1, ) - builder_specific_actions(exc, builder, OUTPUT_PATH, build_type, PAGE_NAME) + builder_specific_actions( + result, builder, OUTPUT_PATH, build_type, PAGE_NAME, click.echo + ) @main.command() @@ -207,6 +301,8 @@ def toc(path, filename_split_char, skip_text, output_folder, add_titles): chosen as the first file. Note that each folder must have at least one content file in it. """ + from ..toc import build_toc + out_yaml = build_toc(path, filename_split_char, skip_text, add_titles) if output_folder is None: output_folder = path @@ -296,114 +392,177 @@ def myst(): def init(path, kernel): """Add Jupytext metadata for your markdown file(s), with optional Kernel name. """ + from ..utils import init_myst_file + for ipath in path: init_myst_file(ipath, kernel, verbose=True) +@main.group() +def config(): + """Inspect your _config.yml file.""" + pass + + +@config.command() +@click.argument("path-source", type=click.Path(exists=True, file_okay=True)) +@click.option( + "--config", + default=None, + help="Path to the YAML configuration file (default: PATH_SOURCE/_config.yml)", +) +@click.option( + "--toc", + default=None, + help="Path to the Table of Contents YAML file (default: PATH_SOURCE/_toc.yml)", +) +@click.pass_context +def sphinx(ctx, path_source, config, toc): + """Generate a Sphinx conf.py representation of the build configuration.""" + from ..config import get_final_config + + path_config, path_src, config_overrides = ctx.invoke( + build, path_source=path_source, config=config, toc=toc, get_config_only=True + ) + + sphinx_config, config_meta = get_final_config( + user_yaml=Path(path_config) if path_config else None, + sourcedir=Path(path_src), + cli_config=config_overrides, + ) + lines = [] + for key in sorted(sphinx_config): + lines.append(f"{key} = {sphinx_config[key]!r}") + + click.echo("\n".join(lines)) + + # utility functions -def find_config_path(path): +def find_config_path(path: Path) -> Tuple[Path, bool]: """ checks for any _config.yml file in current/parent dirs. - if found then returns the path which has _config.yml. - else returns the present dir as the path.""" + if found then returns the path which has _config.yml, + else returns the present dir as the path. + """ if path.is_dir(): current_dir = path else: current_dir = path.parent - root_dir = current_dir.root - while str(current_dir) != root_dir: - config_file = str(current_dir) + "/_config.yml" - if os.path.isfile(config_file): - return current_dir + if (current_dir / "_config.yml").is_file(): + return (current_dir, True) + + while current_dir != current_dir.parent: + if (current_dir / "_config.yml").is_file(): + return (current_dir, True) current_dir = current_dir.parent + if not path.is_dir(): - return path.parent - return path + return (path.parent, False) + return (path, False) + + +def builder_specific_actions( + result, builder, output_path, cmd_type, page_name=None, print_func=print +): + """Run post-sphinx-build actions. + + :param result: the result of the build execution; a status code or and exception + """ + + from sphinx.util.osutil import cd + from ..pdf import html_to_pdf + from ..sphinx import REDIRECT_TEXT -def builder_specific_actions(exc, builder, output_path, cmd_type, page_name=None): - if exc: - _error( + if isinstance(result, Exception): + msg = ( f"There was an error in building your {cmd_type}. " - "Look above for the error message." + "Look above for the cause." ) - else: - # Builder-specific options - if builder == "html": - path_output_rel = Path(op.relpath(output_path, Path())) - if cmd_type == "page": - path_page = path_output_rel.joinpath(f"{page_name}.html") - # Write an index file if it doesn't exist so we get redirects - path_index = path_output_rel.joinpath("index.html") - if not path_index.exists(): - path_index.write_text( - REDIRECT_TEXT.format(first_page=path_page.name) - ) - - _message_box( - dedent( - f""" - Page build finished. - Your page folder is: {path_page.parent}{os.sep} - Open your page at: {path_page} - """ - ) - ) + # TODO ideally we probably only want the original traceback here + raise RuntimeError(_message_box(msg, color="red", doprint=False)) from result + elif result: + msg = ( + f"Building your {cmd_type}, returns a non-zero exit code ({result}). " + "Look above for the cause." + ) + _message_box(msg, color="red", print_func=click.echo) + sys.exit(result) + + # Builder-specific options + if builder == "html": + path_output_rel = Path(op.relpath(output_path, Path())) + if cmd_type == "page": + path_page = path_output_rel.joinpath(f"{page_name}.html") + # Write an index file if it doesn't exist so we get redirects + path_index = path_output_rel.joinpath("index.html") + if not path_index.exists(): + path_index.write_text(REDIRECT_TEXT.format(first_page=path_page.name)) - elif cmd_type == "book": - path_output_rel = Path(op.relpath(output_path, Path())) - path_index = path_output_rel.joinpath("index.html") - _message_box( - f"""\ - Finished generating HTML for {cmd_type}. - Your book's HTML pages are here: - {path_output_rel}{os.sep} - You can look at your book by opening this file in a browser: - {path_index} - Or paste this line directly into your browser bar: - file://{path_index.resolve()}\ - """ + _message_box( + dedent( + f""" + Page build finished. + Your page folder is: {path_page.parent}{os.sep} + Open your page at: {path_page} + """ ) - if builder == "pdfhtml": - print(f"Finished generating HTML for {cmd_type}...") - print(f"Converting {cmd_type} HTML into PDF...") - path_pdf_output = output_path.parent.joinpath("pdf") - path_pdf_output.mkdir(exist_ok=True) - if cmd_type == "book": - path_pdf_output = path_pdf_output.joinpath("book.pdf") - html_to_pdf(output_path.joinpath("index.html"), path_pdf_output) - elif cmd_type == "page": - path_pdf_output = path_pdf_output.joinpath(page_name + ".pdf") - html_to_pdf(output_path.joinpath(page_name + ".html"), path_pdf_output) - path_pdf_output_rel = Path(op.relpath(path_pdf_output, Path())) + ) + + elif cmd_type == "book": + path_output_rel = Path(op.relpath(output_path, Path())) + path_index = path_output_rel.joinpath("index.html") _message_box( f"""\ - Finished generating PDF via HTML for {cmd_type}. Your PDF is here: - {path_pdf_output_rel}\ + Finished generating HTML for {cmd_type}. + Your book's HTML pages are here: + {path_output_rel}{os.sep} + You can look at your book by opening this file in a browser: + {path_index} + Or paste this line directly into your browser bar: + file://{path_index.resolve()}\ """ ) - if builder == "pdflatex": - print(f"Finished generating latex for {cmd_type}...") - print(f"Converting {cmd_type} latex into PDF...") - # Convert to PDF via tex and template built Makefile and make.bat - if sys.platform == "win32": - makecmd = os.environ.get("MAKE", "make.bat") - else: - makecmd = os.environ.get("MAKE", "make") - try: - with cd(output_path): - output = subprocess.run([makecmd, "all-pdf"]) - if output.returncode != 0: - _error("Error: Failed to build pdf") - return output.returncode - _message_box( - f"""\ - A PDF of your {cmd_type} can be found at: - {output_path} - """ - ) - except OSError: - _error("Error: Failed to run: %s" % makecmd) - return 1 + if builder == "pdfhtml": + print_func(f"Finished generating HTML for {cmd_type}...") + print_func(f"Converting {cmd_type} HTML into PDF...") + path_pdf_output = output_path.parent.joinpath("pdf") + path_pdf_output.mkdir(exist_ok=True) + if cmd_type == "book": + path_pdf_output = path_pdf_output.joinpath("book.pdf") + html_to_pdf(output_path.joinpath("index.html"), path_pdf_output) + elif cmd_type == "page": + path_pdf_output = path_pdf_output.joinpath(page_name + ".pdf") + html_to_pdf(output_path.joinpath(page_name + ".html"), path_pdf_output) + path_pdf_output_rel = Path(op.relpath(path_pdf_output, Path())) + _message_box( + f"""\ + Finished generating PDF via HTML for {cmd_type}. Your PDF is here: + {path_pdf_output_rel}\ + """ + ) + if builder == "pdflatex": + print_func(f"Finished generating latex for {cmd_type}...") + print_func(f"Converting {cmd_type} latex into PDF...") + # Convert to PDF via tex and template built Makefile and make.bat + if sys.platform == "win32": + makecmd = os.environ.get("MAKE", "make.bat") + else: + makecmd = os.environ.get("MAKE", "make") + try: + with cd(output_path): + output = subprocess.run([makecmd, "all-pdf"]) + if output.returncode != 0: + _error("Error: Failed to build pdf") + return output.returncode + _message_box( + f"""\ + A PDF of your {cmd_type} can be found at: + {output_path} + """ + ) + except OSError: + _error("Error: Failed to run: %s" % makecmd) + return 1 diff --git a/jupyter_book/config.py b/jupyter_book/config.py new file mode 100644 index 0000000000000000000000000000000000000000..296a1cf01151600d481984c18e6c49c1dd61a810 --- /dev/null +++ b/jupyter_book/config.py @@ -0,0 +1,306 @@ +"""A small sphinx extension to let you configure a site with YAML metadata.""" +from pathlib import Path +from functools import lru_cache +import json +from typing import Optional, Union + +import jsonschema +import yaml + +from .utils import _message_box + + +# Transform a "Jupyter Book" YAML configuration file into a Sphinx configuration file. +# This is so that we can choose more user-friendly words for things than Sphinx uses. +# e.g., 'logo' instead of 'html_logo'. +# Note that this should only be used for **top level** keys. +PATH_YAML_DEFAULT = Path(__file__).parent.joinpath("default_config.yml") +PATH_JSON_SCHEMA = Path(__file__).parent.joinpath("config_schema.json") + + +def get_default_sphinx_config(): + """Some configuration values that are really sphinx-specific.""" + return dict( + extensions=[ + "sphinx_togglebutton", + "sphinx_copybutton", + "myst_nb", + "jupyter_book", + "sphinxcontrib.bibtex", + "sphinx_thebe", + "sphinx_comments", + "sphinx.ext.intersphinx", + "sphinx_panels", + ], + language=None, + pygments_style="sphinx", + html_theme="sphinx_book_theme", + html_theme_options={"search_bar_text": "Search this book..."}, + html_add_permalinks="¶", + html_sourcelink_suffix="", + numfig=True, + panels_add_boostrap_css=False, + ) + + +@lru_cache(1) +def get_validator(): + schema = json.loads(PATH_JSON_SCHEMA.read_text("utf8")) + validator_cls = jsonschema.validators.validator_for(schema) + validator_cls.check_schema(schema) + return validator_cls(schema=schema) + + +def validate_yaml(yaml: dict, raise_on_errors=False, print_func=print): + """Validate the YAML configuration against a JSON schema.""" + errors = sorted(get_validator().iter_errors(yaml), key=lambda e: e.path) + error_msg = "\n".join( + [ + "- {} [key path: '{}']".format( + error.message, "/".join([str(p) for p in error.path]) + ) + for error in errors + ] + ) + if not errors: + return + if raise_on_errors: + raise jsonschema.ValidationError(error_msg) + return _message_box( + f"Warning: Validation errors in config:\n{error_msg}", + color="orange", + print_func=print_func, + ) + + +def get_final_config( + user_yaml: Optional[Union[dict, Path]] = None, + cli_config: Optional[dict] = None, + sourcedir: Optional[Path] = None, + validate: bool = True, + raise_on_invalid: bool = False, +): + """Create the final configuration dictionary, to parser to sphinx + + :param user_config_path: A path to a YAML file written by the user + :param cli_config: Configuration coming directly from the CLI + :param sourcedir: path to source directory. + If it contains a `_static` folder, we ad that to the final `html_static_path` + :param validate: Validate user yaml against the data schema + :param raise_on_invalid: Raise a ValidationError, or only log a warning + + Order of precedence is: + + 1. CLI Sphinx Configuration + 2. User JB(YAML) Configuration + 3. Default JB (YAML) Configuration + 4. Default Sphinx Configuration + + """ + + # get the default sphinx configuration + sphinx_config = get_default_sphinx_config() + + # get the default yaml configuration + yaml_config, default_yaml_update = yaml_to_sphinx( + yaml.safe_load(PATH_YAML_DEFAULT.read_text(encoding="utf8")) + ) + yaml_config.update(default_yaml_update) + + # if available, get the user defined configuration + user_yaml_recurse, user_yaml_update = {}, {} + if user_yaml: + if isinstance(user_yaml, Path): + user_yaml = yaml.safe_load(user_yaml.read_text(encoding="utf8")) + else: + user_yaml = user_yaml + if validate: + validate_yaml(user_yaml, raise_on_errors=raise_on_invalid) + user_yaml_recurse, user_yaml_update = yaml_to_sphinx(user_yaml) + + # first merge the user yaml into the default yaml + _recursive_update(yaml_config, user_yaml_recurse) + + # then merge this into the default sphinx config + _recursive_update(sphinx_config, yaml_config) + + # Value set in `sphinx: config: ...` are a special case, + # and completely override any defaults (sphinx and yaml) + sphinx_config.update(user_yaml_update) + + # finally merge in CLI configuration + _recursive_update(sphinx_config, cli_config or {}) + + # Add the `_static` folder to html_static_path, only if it exists + if sourcedir and Path(sourcedir).joinpath("_static").is_dir(): + paths_static = sphinx_config.get("html_static_path", []) + paths_static.append("_static") + sphinx_config["html_static_path"] = paths_static + + # This is to deal with a special case, where the override needs to be applied after + # the sphinx app is initialised (since the default is a function) + # TODO I'm not sure if there is a better way to deal with this? + config_meta = {"latex_doc_overrides": sphinx_config.pop("latex_doc_overrides")} + + return sphinx_config, config_meta + + +def yaml_to_sphinx(yaml: dict): + """Convert a Jupyter Book style config structure into a Sphinx config dict. + + :returns: (recursive_updates, override_updates) + """ + sphinx_config = {} + + # top-level, string type + YAML_TRANSLATIONS = { + "title": "html_title", + "author": "author", + "copyright": "copyright", + "logo": "html_logo", + "project": "project", + } + for key, newkey in YAML_TRANSLATIONS.items(): + if key in yaml: + val = yaml.get(key) + if val is None: + val = "" + sphinx_config[newkey] = val + + # exclude patterns + if "exclude_patterns" in yaml: + # we always include these excludes, so as not to break back-compatibility + defaults = {"_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"} + defaults.update(yaml["exclude_patterns"]) + sphinx_config["exclude_patterns"] = list(sorted(defaults)) + + # Theme + sphinx_config["html_theme_options"] = theme_options = {} + if "launch_buttons" in yaml: + theme_options["launch_buttons"] = yaml["launch_buttons"] + + repository_config = yaml.get("repository", {}) + for spx_key, yml_key in [ + ("path_to_docs", "path_to_book"), + ("repository_url", "url"), + ("repository_branch", "branch"), + ]: + if yml_key in repository_config: + theme_options[spx_key] = repository_config[yml_key] + + # HTML + html = yaml.get("html") + if html: + + for spx_key, yml_key in [ + ("html_favicon", "favicon"), + ("html_baseurl", "baseurl"), + ("comments_config", "comments"), + ]: + if yml_key in html: + sphinx_config[spx_key] = html[yml_key] + + for spx_key, yml_key in [ + ("google_analytics_id", "google_analytics_id"), + ("navbar_footer_text", "navbar_footer_text"), + ("extra_navbar", "extra_navbar"), + # Deprecate navbar_footer_text after a release cycle + ("extra_footer", "extra_footer"), + ("home_page_in_toc", "home_page_in_navbar"), + ]: + if yml_key in html: + theme_options[spx_key] = html[yml_key] + + # Pass through the buttons + btns = ["use_repository_button", "use_edit_page_button", "use_issues_button"] + use_buttons = {btn: html.get(btn) for btn in btns if btn in html} + if any(use_buttons.values()): + if not repository_config.get("url"): + raise ValueError( + "To use 'repository' buttons, you must specify the repository URL" + ) + # Update our config + theme_options.update(use_buttons) + + # Parse and Rendering + parse = yaml.get("parse") + if parse: + if parse.get("myst_extended_syntax") is True: + sphinx_config["myst_dmath_enable"] = True + sphinx_config["myst_amsmath_enable"] = True + sphinx_config["myst_deflist_enable"] = True + sphinx_config["myst_admonition_enable"] = True + sphinx_config["myst_html_img_enable"] = True + sphinx_config["myst_figure_enable"] = True + if "myst_url_schemes" in parse: + sphinx_config["myst_url_schemes"] = parse.get("myst_url_schemes") + + # Execution + execute = yaml.get("execute") + if execute: + for spx_key, yml_key in [ + ("execution_allow_errors", "allow_errors"), + ("execution_in_temp", "run_in_temp"), + ("nb_output_stderr", "stderr_output"), + ("execution_timeout", "timeout"), + ("jupyter_cache", "cache"), + ("jupyter_execute_notebooks", "execute_notebooks"), + ("execution_excludepatterns", "exclude_patterns"), + ]: + if yml_key in execute: + sphinx_config[spx_key] = execute[yml_key] + + if sphinx_config.get("jupyter_execute_notebooks") is False: + # Special case because YAML treats `off` as "False". + sphinx_config["jupyter_execute_notebooks"] = "off" + + # LaTeX + latex = yaml.get("latex") + if latex: + for spx_key, yml_key in [ + ("latex_engine", "latex_engine"), + ]: + if yml_key in latex: + sphinx_config[spx_key] = latex[yml_key] + + sphinx_config["latex_doc_overrides"] = {} + if "title" in yaml: + sphinx_config["latex_doc_overrides"]["title"] = yaml["title"] + for key, val in yaml.get("latex", {}).get("latex_documents", {}).items(): + sphinx_config["latex_doc_overrides"][key] = val + + # Sphinx Configuration + extra_extensions = yaml.get("sphinx", {}).get("extra_extensions") + if extra_extensions: + sphinx_config["extensions"] = get_default_sphinx_config()["extensions"] + if not isinstance(extra_extensions, list): + extra_extensions = [extra_extensions] + for extension in extra_extensions: + if extension not in sphinx_config["extensions"]: + sphinx_config["extensions"].append(extension) + + # items in sphinx.config will override defaults, + # rather than recursively updating them + return sphinx_config, yaml.get("sphinx", {}).get("config") or {} + + +def _recursive_update(config, update, list_extend=False): + """Update the dict `config` with `update` recursively. + This *updates* nested dicts / lists instead of replacing them. + """ + for key, val in update.items(): + if isinstance(config.get(key), dict): + # if a dict value update is set to None, + # then the entire dict will be "wiped", + # otherwise it is recursively updated. + if isinstance(val, dict): + _recursive_update(config[key], val, list_extend) + else: + config[key] = val + elif isinstance(config.get(key), list): + if isinstance(val, list) and list_extend: + config[key].extend(val) + else: + config[key] = val + else: + config[key] = val diff --git a/jupyter_book/config_schema.json b/jupyter_book/config_schema.json new file mode 100644 index 0000000000000000000000000000000000000000..cf6890d3b339589b6a4e68c0a81c1b508c251a81 --- /dev/null +++ b/jupyter_book/config_schema.json @@ -0,0 +1,212 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "author": { + "type": "string" + }, + "copyright": { + "type": "string" + }, + "logo": { + "type": "string" + }, + "exclude_patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "parse": { + "type": "object", + "properties": { + "myst_extended_syntax": { + "type": "boolean" + }, + "myst_url_schemes": { + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + } + } + }, + "execute": { + "type": "object", + "properties": { + "execute_notebooks": { + "type": [ + "string", + "boolean" + ], + "enum": [ + "auto", + "cache", + "force", + "off" + ], + "default": "auto" + }, + "cache": { + "type": "string" + }, + "timeout": { + "type": "number", + "minimum": 0, + "default": 30 + }, + "allow_errors": { + "type": "boolean", + "default": false + }, + "stderr_output": { + "type": "string", + "enum": [ + "show", + "remove", + "remove-warn", + "warn", + "error", + "severe" + ], + "default": "show" + }, + "run_in_temp": { + "type": "boolean", + "default": false + }, + "exclude_patterns": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [] + }, + "html": { + "type": "object", + "properties": { + "favicon": { + "type": "string" + }, + "use_edit_page_button": { + "type": "boolean" + }, + "use_repository_button": { + "type": "boolean" + }, + "use_issues_button": { + "type": "boolean" + }, + "extra_navbar": { + "type": "string" + }, + "extra_footer": { + "type": "string" + }, + "google_analytics_id": { + "type": "string" + }, + "home_page_in_navbar": { + "type": "boolean" + }, + "baseurl": { + "type": "string" + }, + "comments": { + "type": "object", + "properties": { + "hypothesis": { + "type": [ + "boolean", + "object" + ] + }, + "utterances": { + "type": [ + "boolean", + "object" + ] + } + }, + "required": [] + } + }, + "required": [] + }, + "latex": { + "type": "object", + "properties": { + "latex_engine": { + "type": "string", + "default": "pdflatex" + } + } + }, + "launch_buttons": { + "type": "object", + "properties": { + "notebook_interface": { + "type": "string" + }, + "binderhub_url": { + "type": "string" + }, + "jupyterhub_url": { + "type": "string" + }, + "thebe": { + "type": "boolean" + }, + "colab_url": { + "type": "string" + } + }, + "required": [] + }, + "repository": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "path_to_book": { + "type": "string" + }, + "branch": { + "type": "string" + } + }, + "required": [] + }, + "sphinx": { + "type": "object", + "properties": { + "extra_extensions": { + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + }, + "config": { + "type": [ + "null", + "object" + ] + } + }, + "required": [] + } + }, + "required": [] +} diff --git a/jupyter_book/default_config.yml b/jupyter_book/default_config.yml index 08ef8e4f72a04991287db6ae81f011ac25c4e1d7..09a4bfd92a0b0a4e8a75b1343c1b29cbe42eb0a3 100644 --- a/jupyter_book/default_config.yml +++ b/jupyter_book/default_config.yml @@ -9,14 +9,26 @@ title : My Jupyter Book # The title of the book. Will be author : The Jupyter Book community # The author of the book copyright : "2020" # Copyright year to be placed in the footer logo : "" # A path to the book logo -exclude_patterns : [] # Patterns to skip when building the book. Can be glob-style (e.g. "*skip.ipynb") +# Patterns to skip when building the book. Can be glob-style (e.g. "*skip.ipynb") +exclude_patterns : [_build, Thumbs.db, .DS_Store, "**.ipynb_checkpoints"] ####################################################################################### # Execution settings execute: execute_notebooks : auto # Whether to execute notebooks at build time. Must be one of ("auto", "force", "cache", "off") - cache : "" # A path to the jupyter cache that will be used to store execution artifacs. Defaults to `_build/.jupyter_cache/` - exclude_patterns : [] # A list of patterns to *skip* in execution (e.g. a notebook that takes a really long time) + cache : "" # A path to the jupyter cache that will be used to store execution artifacs. Defaults to `_build/.jupyter_cache/` + exclude_patterns : [] # A list of patterns to *skip* in execution (e.g. a notebook that takes a really long time) + timeout : 30 # The maximum time (in seconds) each notebook cell is allowed to run. + run_in_temp : false # If `True`, then a temporary directory will be created and used as the command working directory (cwd), + # otherwise the notebook's parent directory will be the cwd. + allow_errors : false # If `False`, when a code cell raises an error the execution is stopped, otherwise all cells are always run. + stderr_output : show # One of 'show', 'remove', 'remove-warn', 'warn', 'error', 'severe' + +####################################################################################### +# Parse and render settings +parse: + myst_extended_syntax : false # enable MyST extended syntax support (see documents for details) + myst_url_schemes : [mailto, http, https] # URI schemes that will be recognised as external URLs in Markdown links ####################################################################################### # HTML-specific settings @@ -34,6 +46,11 @@ html: hypothesis : false utterances : false +####################################################################################### +# LaTeX-specific settings +latex: + latex_engine : pdflatex # one of 'pdflatex', 'xelatex' (recommended for unicode), 'luatex', 'platex', 'uplatex' + ####################################################################################### # Launch button settings launch_buttons: @@ -51,5 +68,5 @@ repository: ####################################################################################### # Advanced and power-user settings sphinx: - extra_extensions : # A list of extra extensions to load by Sphinx. + extra_extensions : # A list of extra extensions to load by Sphinx (added to those already used by JB). config : # key-value pairs to directly over-ride the Sphinx configuration diff --git a/jupyter_book/pdf.py b/jupyter_book/pdf.py index 696dec12238a9927711b54460cc2d04d5a362835..8f0c1687c41ec7320c17d9d13eb85eee2f5fa62f 100644 --- a/jupyter_book/pdf.py +++ b/jupyter_book/pdf.py @@ -1,4 +1,5 @@ """Commands to facilitate conversion to PDF.""" +from copy import copy from pathlib import Path import asyncio @@ -43,11 +44,9 @@ async def _html_to_pdf(html_file, pdf_file): await browser.close() -def update_latex_documents(latex_documents, latexoverrides): - """ - Apply latexoverrides from _config.yml to latex_documents tuple - """ - latexdocs_tuple = ( +def update_latex_document(latex_document: tuple, updates: dict): + """Apply updates from _config.yml to a latex_document tuple""" + names = ( "startdocname", "targetname", "title", @@ -55,13 +54,8 @@ def update_latex_documents(latex_documents, latexoverrides): "theme", "toctree_only", ) - updated_latexdocs = [] - for loc, item in enumerate(latexdocs_tuple): - # the last element toctree_only seems optionally included - if loc >= len(latex_documents): - break - if item in latexoverrides["latex_documents"].keys(): - updated_latexdocs.append(latexoverrides["latex_documents"][item]) - else: - updated_latexdocs.append(latex_documents[loc]) - return tuple(updated_latexdocs) + updated = list(copy(latex_document)) + for i, (_, name) in enumerate(zip(latex_document, names)): + if name in updates: + updated[i] = updates[name] + return tuple(updated) diff --git a/jupyter_book/sphinx.py b/jupyter_book/sphinx.py index be6e0758ababe33a7f416e4310cc981fb35e843b..7d69162d36e687120e859eda9ed387525363c1eb 100644 --- a/jupyter_book/sphinx.py +++ b/jupyter_book/sphinx.py @@ -1,40 +1,22 @@ """Tools for interacting with Sphinx.""" -import sys import os.path as op -import yaml from pathlib import Path +import sys +from typing import Union + from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.application import Sphinx from sphinx.cmd.build import handle_exception +import yaml -from .yaml import PATH_YAML_DEFAULT, yaml_to_sphinx, _recursive_update - +from .config import get_final_config +from .pdf import update_latex_document REDIRECT_TEXT = """ <meta http-equiv="Refresh" content="0; url={first_page}" /> """ ROOT = Path(__file__) -# Some configuration values that are really sphinx-specific -DEFAULT_CONFIG = dict( - extensions=[ - "sphinx_togglebutton", - "sphinx_copybutton", - "myst_nb", - "jupyter_book", - "sphinxcontrib.bibtex", - "sphinx_thebe", - "sphinx_comments", - "sphinx.ext.intersphinx", - ], - language=None, - pygments_style="sphinx", - html_theme="sphinx_book_theme", - html_theme_options={"search_bar_text": "Search this book..."}, - html_add_permalinks="¶", - html_sourcelink_suffix="", - numfig=True, -) def build_sphinx( @@ -44,15 +26,11 @@ def build_sphinx( path_config=None, noconfig=False, confoverrides=None, - extra_extensions=None, - htmloverrides=None, - latexoverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, - nitpicky=False, builder="html", freshenv=False, warningiserror=False, @@ -60,88 +38,20 @@ def build_sphinx( verbosity=0, jobs=None, keep_going=False, -): +) -> Union[int, Exception]: """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. - Extra parameters - ---------------- - - extra_extensions : list | None - A list of extra extensions to load into Sphinx. This must be done - before Sphinx is initialized otherwise the extensions aren't properly - initialized. """ - - if confoverrides is None: - confoverrides = {} - if latexoverrides is None: - latexoverrides = {} - ####################### - # Configuration updates - - # Start with the default Sphinx config - sphinx_config = DEFAULT_CONFIG.copy() - - # Update with the *default* config.yml - default_yaml_config = yaml.safe_load(PATH_YAML_DEFAULT.read_text(encoding="utf8")) - new_config = yaml_to_sphinx(default_yaml_config) - _recursive_update(sphinx_config, new_config) - - # Update with the given config file, if it exists - if path_config: - path_config = Path(path_config) - yaml_config = yaml.safe_load(path_config.read_text(encoding="utf8")) - - # Check for manual Sphinx over-rides which we'll apply later to take precedence - sphinx_overrides = yaml_config.get("sphinx", {}).get("config") - if sphinx_overrides: - confoverrides.update(sphinx_overrides) - - # Some latex-specific changes we need to make if we're building latex - if builder == "latex": - # First update the overrides with the latex config - latexoverrides.update(yaml_config.get("latex", {})) - - # If we have a document title and no explicit latex title, use the doc title - if "title" in yaml_config.keys(): - latex_documents = latexoverrides.get("latex_documents", {}) - if "title" not in latex_documents: - latex_documents["title"] = yaml_config["title"] - latexoverrides["latex_documents"] = latex_documents - - new_config = yaml_to_sphinx(yaml_config) - _recursive_update(sphinx_config, new_config) - - # Manual configuration overrides from the CLI - _recursive_update(sphinx_config, confoverrides) - - # HTML-specific configuration from the CLI - if htmloverrides is None: - htmloverrides = {} - for key, val in htmloverrides.items(): - sphinx_config["html_context.%s" % key] = val - - # #LaTeX-specific configuration - # TODO: if this is included we should ignore latex_documents - # if latexoverrides is None: - # latexoverrides = {} - # for key, val in latexoverrides.items(): - # config[key] = val - - # Add the folder `_static` if it exists - if Path(sourcedir).joinpath("_static").is_dir(): - paths_static = sphinx_config.get("html_static_path", []) - paths_static.append("_static") - sphinx_config["html_static_path"] = paths_static - - # Flags from the CLI - # Raise more warnings - if nitpicky: - sphinx_config["nitpicky"] = True + # Configuration creation + sphinx_config, config_meta = get_final_config( + user_yaml=Path(path_config) if path_config else None, + cli_config=confoverrides or {}, + sourcedir=Path(sourcedir), + ) ################################## # Preparing Sphinx build arguments @@ -167,7 +77,7 @@ def build_sphinx( if not op.isfile(filename): missing_files.append(filename) if missing_files: - raise ValueError("cannot find files %r" % missing_files) + raise IOError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") @@ -193,7 +103,9 @@ def build_sphinx( # Build with Sphinx app = None # In case we fail, this allows us to handle the exception try: - # This patch is what Sphinx does, so we copy it blindly... + # These patches temporarily override docutils global variables, + # such as the dictionaries of directives, roles and nodes + # NOTE: this action is not thread-safe and not suitable for asynchronous use! with patch_docutils(confdir), docutils_namespace(): app = Sphinx( srcdir=sourcedir, @@ -211,17 +123,21 @@ def build_sphinx( parallel=jobs, keep_going=keep_going, ) - # Apply Latex Overrides for latex_documents - if ( - latexoverrides is not None - and "latex_documents" in latexoverrides.keys() - ): - from .pdf import update_latex_documents + app.srcdir = Path(app.srcdir).as_posix() + app.outdir = Path(app.outdir).as_posix() + app.confdir = Path(app.confdir).as_posix() + app.doctreedir = Path(app.doctreedir).as_posix() + + # We have to apply this update after the sphinx initialisation, + # since default_latex_documents is dynamically generated + # see sphinx/builders/latex/__init__.py:default_latex_documents + # TODO what if the user has specifically set latex_documents? + default_latex_document = app.config.latex_documents[0] + new_latex_document = update_latex_document( + default_latex_document, config_meta["latex_doc_overrides"] + ) + app.config.latex_documents = [new_latex_document] - latex_documents = update_latex_documents( - app.config.latex_documents[0], latexoverrides - ) - app.config.latex_documents = [latex_documents] app.build(force_all, filenames) # Write an index.html file in the root to redirect to the first page @@ -229,14 +145,14 @@ def build_sphinx( if sphinx_config["globaltoc_path"]: path_toc = Path(sphinx_config["globaltoc_path"]) if not path_toc.exists(): - raise ValueError( + raise IOError( ( "You gave a Configuration file path" f"that doesn't exist: {path_toc}" ) ) if path_toc.suffix not in [".yml", ".yaml"]: - raise ValueError( + raise IOError( "You gave a Configuration file path" f"that is not a YAML file: {path_toc}" ) @@ -250,7 +166,7 @@ def build_sphinx( else: first_page = toc[0]["file"] first_page = first_page.split(".")[0] + ".html" - with open(path_index, "w") as ff: + with open(path_index, "w", encoding="utf8") as ff: ff.write(REDIRECT_TEXT.format(first_page=first_page)) return app.statuscode except (Exception, KeyboardInterrupt) as exc: diff --git a/jupyter_book/toc.py b/jupyter_book/toc.py index c8b20bcc873836b2d00e7de361e151d5849c15fd..77ceeb7b41a7ece23dc276ee3aa4ab81675ab395 100644 --- a/jupyter_book/toc.py +++ b/jupyter_book/toc.py @@ -10,10 +10,12 @@ from .utils import _filename_to_title, SUPPORTED_FILE_SUFFIXES, _error logger = logging.getLogger(__name__) -def _no_suffix(path): - if isinstance(path, str): - path = str(Path(path).with_suffix("")) - return path +def _posix_no_suffix(path): + posix_path = None + if path: + the_path = Path(path) + posix_path = (the_path.with_suffix("")).as_posix() + return posix_path def find_name(pages, name): @@ -27,7 +29,7 @@ def find_name(pages, name): pages = [pages] for page in pages: - if _no_suffix(page.get("file")) == name: + if _posix_no_suffix(page.get("file")) == name: return page else: sections = page.get("sections", []) @@ -43,10 +45,10 @@ def add_toctree(app, docname, source): # First check whether this page has any descendants # If so, then we'll manually add them as a toctree object - path_parent = app.env.doc2path(docname, base=None) + path_parent = Path(app.env.doc2path(docname, base=None)).as_posix() toc = app.config["globaltoc"] - parent_page = find_name(toc, _no_suffix(path_parent)) + parent_page = find_name(toc, _posix_no_suffix(path_parent)) # If we didn't find this page in the TOC, raise a warning if parent_page is None: logger.warning(f"Found a content page that is not in _toc.yml: {path_parent}.") @@ -116,7 +118,9 @@ def add_toctree(app, docname, source): for ipage in isection.get("sections"): if ipage.get("file"): # Update path so it is relative to the root of the parent - path_sec = os.path.relpath(ipage.get("file"), path_parent_folder) + path_sec = Path( + os.path.relpath(ipage.get("file"), path_parent_folder) + ).as_posix() elif ipage.get("url"): path_sec = ipage.get("url") else: @@ -185,7 +189,7 @@ def add_toc_to_sphinx(app, config): app.config["globaltoc"] = toc # Update the main toctree file for whatever the first file here is - app.config["master_doc"] = _no_suffix(toc["file"]) + app.config["master_doc"] = _posix_no_suffix(toc["file"]) def _gen_toctree(options, subsections, parent_suff): @@ -231,8 +235,8 @@ def _content_path_to_yaml(path, root_path, split_char="_", add_titles=True): else: title = _filename_to_title(path.name, split_char=split_char) - path_rel_root = path.relative_to(root_path) - out = {"file": str(path_rel_root.with_suffix(""))} + path_rel_root = Path(os.path.relpath(path, root_path)) + out = {"file": path_rel_root.with_suffix("").as_posix()} if add_titles: out["title"] = title return out @@ -281,7 +285,7 @@ def _find_content_structure( # Now recursively run this on folders, and add as another sub-page folders = [ii for ii in path.iterdir() if ii.is_dir()] for folder in folders: - if any(iskip in str(folder) for iskip in skip_text): + if any(iskip in folder.as_posix() for iskip in skip_text): continue folder_out = _find_content_structure( folder, diff --git a/jupyter_book/utils.py b/jupyter_book/utils.py index 923a6012efd0d7a219eb0616501d2a8f7fad7074..420c0cfa5c5d60e9c6316fdb4a1cb143036084c1 100644 --- a/jupyter_book/utils.py +++ b/jupyter_book/utils.py @@ -39,7 +39,7 @@ def _color_message(msg, style): return bcolors[style] + msg + endc -def _message_box(msg, color="green", doprint=True): +def _message_box(msg, color="green", doprint=True, print_func=print): # Prepare the message so the indentation is the same as the box msg = dedent(msg) @@ -54,13 +54,13 @@ def _message_box(msg, color="green", doprint=True): """ box = dedent(box).format(msg=msg, border_colored=border_colored) if doprint is True: - print(box) + print_func(box) return box def _error(msg, kind=None): if kind is None: - kind = ValueError + kind = RuntimeError box = _message_box(msg, color="red", doprint=False) raise kind(box) diff --git a/jupyter_book/yaml.py b/jupyter_book/yaml.py deleted file mode 100644 index 9efa004ed72e268641173fcd54de72edaac3595f..0000000000000000000000000000000000000000 --- a/jupyter_book/yaml.py +++ /dev/null @@ -1,130 +0,0 @@ -"""A small sphinx extension to let you configure a site with YAML metadata.""" -from pathlib import Path - - -# Transform a "Jupyter Book" YAML configuration file into a Sphinx configuration file. -# This is so that we can choose more user-friendly words for things than Sphinx uses. -# e.g., 'logo' instead of 'html_logo'. -# Note that this should only be used for **top level** keys. -PATH_YAML_DEFAULT = Path(__file__).parent.joinpath("default_config.yml") - - -def yaml_to_sphinx(yaml): - """Convert a Jupyter Book style config structure into a Sphinx config dict.""" - sphinx_config = { - "exclude_patterns": [ - "_build", - "Thumbs.db", - ".DS_Store", - "**.ipynb_checkpoints", - ], - } - - # Start with an empty options block - theme_options = {} - - # Launch button configuration - launch_buttons_config = yaml.get("launch_buttons", {}) - repository_config = yaml.get("repository", {}) - - theme_options["launch_buttons"] = launch_buttons_config - - theme_options["path_to_docs"] = repository_config.get("path_to_book", "") - theme_options["repository_url"] = repository_config.get("url", "") - theme_options["repository_branch"] = repository_config.get("branch", "") - - # HTML - html = yaml.get("html") - if html: - sphinx_config["html_favicon"] = html.get("favicon", "") - sphinx_config["html_baseurl"] = html.get("baseurl", "") - - theme_options["google_analytics_id"] = html.get("google_analytics_id", "") - # Deprecate navbar_footer_text after a release cycle - theme_options["navbar_footer_text"] = html.get("navbar_footer_text", "") - theme_options["extra_navbar"] = html.get("extra_navbar", "") - theme_options["extra_footer"] = html.get("extra_footer", "") - theme_options["home_page_in_toc"] = html.get("home_page_in_navbar") - - # Comments config - sphinx_config["comments_config"] = html.get("comments", {}) - - # Pass through the buttons - btns = ["use_repository_button", "use_edit_page_button", "use_issues_button"] - use_buttons = {btn: html.get(btn) for btn in btns if html.get(btn) is not None} - if any(use_buttons.values()): - if not repository_config.get("url"): - raise ValueError( - "To use 'repository' buttons, you must specify the repository URL" - ) - # Update our config - theme_options.update(use_buttons) - - # Update the theme options in the main config - sphinx_config["html_theme_options"] = theme_options - - execute = yaml.get("execute") - if execute: - if execute.get("execute_notebooks") is False: - # Special case because YAML treats `off` as "False". - execute["execute_notebooks"] = "off" - sphinx_config["jupyter_execute_notebooks"] = execute.get( - "execute_notebooks", "auto" - ) - sphinx_config["execution_timeout"] = execute.get("timeout", 30) - sphinx_config["jupyter_cache"] = execute.get("cache", "") - _recursive_update( - sphinx_config, - {"execution_excludepatterns": execute.get("exclude_patterns", [])}, - ) - - # LaTeX - latex = yaml.get("latex") - if latex: - sphinx_config["latex_engine"] = latex.get("latex_engine", "pdflatex") - - # Extra extensions - extra_extensions = yaml.get("sphinx", {}).get("extra_extensions") - if extra_extensions: - if not isinstance(extra_extensions, list): - extra_extensions = [extra_extensions] - extensions = sphinx_config.get("extensions", []) - for extra in extra_extensions: - extensions.append(extra) - sphinx_config["extensions"] = extensions - - # Files that we wish to skip - sphinx_config["exclude_patterns"].extend(yaml.get("exclude_patterns", [])) - - # Now do simple top-level translations - YAML_TRANSLATIONS = { - "logo": "html_logo", - "title": "html_title", - "execute_notebooks": "jupyter_execute_notebooks", - "project": "project", - "author": "author", - "copyright": "copyright", - } - for key, newkey in YAML_TRANSLATIONS.items(): - if key in yaml: - val = yaml.get(key) - if val is None: - val = "" - sphinx_config[newkey] = val - return sphinx_config - - -def _recursive_update(config, update): - """Update the dict `config` with `update` recursively. - This *updates* nested dicts / lists instead of replacing them. - """ - for key, val in update.items(): - if isinstance(config.get(key), dict): - config[key].update(val) - elif isinstance(config.get(key), list): - if isinstance(val, list): - config[key].extend(val) - else: - config[key] = val - else: - config[key] = val diff --git a/setup.py b/setup.py index 99176cbcf972999c5eaa392f5caf10b2db9a5b9d..f449c2485a02f94e4cc06a58fa60e40805c9b32b 100644 --- a/setup.py +++ b/setup.py @@ -19,10 +19,11 @@ test_reqs = [ "pytest>=3.6,<4", "pytest-cov", "pytest-xdist", + "pytest-timeout", "beautifulsoup4", "matplotlib", "pytest-regressions", - "jupytext", + "jupytext==1.6.0rc0", "altair", "sphinx_click", "sphinx_tabs", @@ -52,21 +53,22 @@ setup( install_requires=[ "pyyaml", "docutils>=0.15", - "sphinx<3", - "myst-nb~=0.8.0", - # this is required by the current markdow-it-py - # but can be remove when moving to myst-nb 0.9 - "attrs~=19.3", + "sphinx>=2,<4", + "myst-nb~=0.10.0", + # required for Markdown figures, but can be removed when myst-nb updates + "myst-parser~=0.12.6", "click", "setuptools", "nbformat", "nbconvert", + "jsonschema", "sphinx_togglebutton", "sphinx-copybutton", "sphinx-comments", "sphinxcontrib-bibtex", - "sphinx_book_theme>=0.0.34", + "sphinx_book_theme>=0.0.36", "sphinx-thebe>=0.0.6", + "sphinx-panels~=0.4.1", ], extras_require={ "code_style": ["flake8<3.8.0,>=3.7.0", "black", "pre-commit==1.17.0"], diff --git a/tests/books/toc/_toc_urllink.yml b/tests/books/toc/_toc_urllink.yml index 1c561d534467a2d547c7a64668ad3324fa3752b7..2631f6dee727b1cce296361723335aaa0c3bc716 100644 --- a/tests/books/toc/_toc_urllink.yml +++ b/tests/books/toc/_toc_urllink.yml @@ -14,5 +14,5 @@ sections: sections: - file: subfolder/asubpage title: Asubpage - - url: https://jupyterbook.org/content-types/markdown.html + - url: https://jupyterbook.org/file-types/markdown.html title: Markdown pages diff --git a/tests/conftest.py b/tests/conftest.py index 0ed7f5fcd35823903d4e1a318377891e6c5cb338..3c9a586309f381559132edb2074719487e895b79 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,42 +7,38 @@ from click.testing import CliRunner @pytest.fixture() -def build_resources(tmpdir): +def build_resources(temp_with_override): """Copys ./books and ./books/tocs to a temporary directory and yields the paths as `pathlib.Path` objects. """ src = Path(__file__).parent.resolve().joinpath("books").absolute() - dst = tmpdir.join("books") + dst = temp_with_override / "books" shutil.copytree(src, dst) - books = Path(dst) - tocs = books / "toc" - yield books, tocs + yield Path(dst), Path(dst) / "toc" shutil.rmtree(dst) @pytest.fixture() -def pages(tmpdir): +def pages(temp_with_override): """Copys ./pages to a temporary directory and yields the path as a `pathlib.Path` object. """ src = Path(__file__).parent.joinpath("pages").absolute() - dst = tmpdir.join("pages") + dst = temp_with_override / "pages" shutil.copytree(src, dst) - pages = Path(dst) - yield pages + yield Path(dst) shutil.rmtree(dst) @pytest.fixture() -def docs(tmpdir): +def docs(temp_with_override): """Copys ../docs to a temporary directory and yields the path as a `pathlib.Path` object. """ src = Path(__file__).parent.parent.joinpath("docs").absolute() - dst = tmpdir.join("docs") + dst = temp_with_override / "docs" shutil.copytree(src, dst) - docs = Path(dst) - yield docs + yield Path(dst) shutil.rmtree(dst) diff --git a/tests/test_build.py b/tests/test_build.py index 88b9ad077e9cb2e30703916a9c97638a591e0c30..455a9a5ff9aef92cb755bd589b8e6d25dae39d8e 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -2,25 +2,34 @@ from pathlib import Path import pytest from bs4 import BeautifulSoup +from click.testing import CliRunner + +import sphinx from jupyter_book import commands -def test_create(tmpdir, cli): - book = Path(tmpdir) / "new_book" - result = cli.invoke(commands.create, str(book)) +def test_version(cli: CliRunner): + result = cli.invoke(commands.main, "--version") + assert result.exit_code == 0, result.output + assert "Jupyter Book" in result.output, result.output + + +def test_create(temp_with_override: Path, cli): + book = temp_with_override / "new_book" + result = cli.invoke(commands.create, book.as_posix()) assert result.exit_code == 0 assert book.joinpath("_config.yml").exists() assert len(list(book.iterdir())) == 9 -def test_build_from_template(tmpdir, cli): +def test_build_from_template(temp_with_override, cli): """Test building the book template and a few test configs.""" # Create the book from the template - book = Path(tmpdir) / "new_book" - _ = cli.invoke(commands.create, str(book)) - build_result = cli.invoke(commands.build, str(book)) - assert build_result.exit_code == 0 + book = temp_with_override / "new_book" + _ = cli.invoke(commands.create, book.as_posix()) + build_result = cli.invoke(commands.build, book.as_posix()) + assert build_result.exit_code == 0, build_result.output html = book.joinpath("_build", "html") assert html.joinpath("index.html").exists() assert html.joinpath("intro.html").exists() @@ -30,9 +39,9 @@ def test_custom_config(cli, build_resources): """Test a variety of custom configuration values.""" books, _ = build_resources config = books.joinpath("config") - result = cli.invoke(commands.build, str(config)) + result = cli.invoke(commands.build, config.as_posix()) assert result.exit_code == 0 - html = config.joinpath("_build", "html", "index.html").read_text() + html = config.joinpath("_build", "html", "index.html").read_text(encoding="utf8") soup = BeautifulSoup(html, "html.parser") assert '<h1 class="site-logo" id="site-title">TEST PROJECT NAME</h1>' in html assert '<div class="sphinx-tabs docutils container">' in html @@ -45,12 +54,13 @@ def test_custom_config(cli, build_resources): @pytest.mark.parametrize("toc", ["_toc.yml", "_toc_startwithlist.yml"]) -def test_toc_builds(cli, build_resources, toc, tmpdir): +def test_toc_builds(cli, build_resources, toc): """Test building the book template with several different TOC files.""" books, tocs = build_resources - toc = str(tocs / toc) - result = cli.invoke(commands.build, [str(tocs), "--toc", toc, "-W"]) - assert result.exit_code == 0 + result = cli.invoke( + commands.build, [tocs.as_posix(), "--toc", (tocs / toc).as_posix(), "-W"] + ) + assert result.exit_code == 0, result.output def test_toc_rebuild(cli, build_resources): @@ -62,17 +72,17 @@ def test_toc_rebuild(cli, build_resources): index_html = tocs.joinpath("_build", "html", "index.html") # Not using -W because we expect warnings for pages not listed in TOC - result = cli.invoke(commands.build, [str(tocs), "--toc", str(toc)]) - html = BeautifulSoup(index_html.read_text(), "html.parser") + result = cli.invoke(commands.build, [tocs.as_posix(), "--toc", toc.as_posix()]) + html = BeautifulSoup(index_html.read_text(encoding="utf8"), "html.parser") tags = html.find_all("a", "reference internal") assert result.exit_code == 0 assert tags[1].attrs["href"] == "content1.html" assert tags[2].attrs["href"] == "content2.html" toc.write_text("- file: index\n- file: content2\n- file: content1\n") - result = cli.invoke(commands.build, [str(tocs), "--toc", str(toc)]) + result = cli.invoke(commands.build, [tocs.as_posix(), "--toc", toc.as_posix()]) assert result.exit_code == 0 - html = BeautifulSoup(index_html.read_text(), "html.parser") + html = BeautifulSoup(index_html.read_text(encoding="utf8"), "html.parser") tags = html.find_all("a", "reference internal") # The rendered TOC should reflect the order in the modified _toc.yml assert tags[1].attrs["href"] == "content2.html" @@ -90,9 +100,10 @@ def test_toc_rebuild(cli, build_resources): ) def test_corrupt_toc(build_resources, cli, toc, msg): books, tocs = build_resources - toc = str(tocs / toc) - with pytest.raises(ValueError): - result = cli.invoke(commands.build, [str(tocs), "--toc", toc, "-W"]) + with pytest.raises(RuntimeError): + result = cli.invoke( + commands.build, [tocs.as_posix(), "--toc", (tocs / toc).as_posix(), "-W"] + ) assert result.exit_code == 1 assert msg in result.output raise result.exception @@ -107,7 +118,7 @@ def test_build_errors(build_resources, cli): # No table of contents message p_notoc = books.joinpath("notoc") - with pytest.raises(ValueError): + with pytest.raises(RuntimeError): result = cli.invoke(commands.build, [p_notoc.as_posix()]) assert result.exit_code == 1 assert "Couldn't find a Table of Contents file" in str(result.exception) @@ -115,14 +126,14 @@ def test_build_errors(build_resources, cli): # Test error on warnings and book error message p_syntax = books.joinpath("sphinx_syntaxerr") - with pytest.raises(ValueError): + with pytest.raises(RuntimeError): result = cli.invoke(commands.build, [p_syntax.as_posix(), "-W"]) assert result.exit_code == 1 assert "There was an error in building your book" in str(result.exception) raise result.exception # Config file path does not exist - with pytest.raises(ValueError): + with pytest.raises(IOError): result = cli.invoke( commands.build, [p_syntax.as_posix(), "--config", "non_existent_path"] ) @@ -150,7 +161,7 @@ def test_build_page(pages, cli): assert result.exit_code == 0 assert html.joinpath("single_page.html").exists() assert not html.joinpath("extra_page.html").exists() - assert 'url=single_page.html" />' in index.read_text() + assert 'url=single_page.html" />' in index.read_text(encoding="utf8") def test_build_page_nested(build_resources, cli): @@ -164,9 +175,10 @@ def test_build_page_nested(build_resources, cli): assert result.exit_code == 0 assert html.joinpath("markdown.html").exists() assert not html.joinpath("extra_page.html").exists() - assert 'url=markdown.html" />' in index.read_text() + assert 'url=markdown.html" />' in index.read_text(encoding="utf8") +@pytest.mark.skipif(sphinx.version_info[0] == 2, reason="randomly fails on CI") def test_execution_timeout(pages, build_resources, cli): """Testing timeout execution for a page.""" books, _ = build_resources diff --git a/tests/test_clean.py b/tests/test_clean.py index ed246312e76b7e6473deaa822fb12900503f86dd..a66cae035d98ea3c938594897d269baf19fac9f8 100644 --- a/tests/test_clean.py +++ b/tests/test_clean.py @@ -1,20 +1,17 @@ """Testing clean functionality of the CLI.""" - -from pathlib import Path -from subprocess import run, PIPE -import pytest import os +from click.testing import CliRunner -path_tests = Path(__file__).parent.resolve() -path_books = path_tests.joinpath("books") -path_root = path_tests.parent +from jupyter_book.commands import build, clean -def test_clean_book(tmpdir): - path = path_books.joinpath("clean_cache") +def test_clean_book(cli: CliRunner, build_resources): + books, tocs = build_resources + path = books.joinpath("clean_cache") build_path = path.joinpath("_build") - run(f"jb build {path}".split()) + result = cli.invoke(build, path.as_posix()) + assert result.exit_code == 0 # Ensure _build exists assert build_path.exists() @@ -23,30 +20,32 @@ def test_clean_book(tmpdir): assert build_path.joinpath(".jupyter_cache").exists() # Empty _build except .jupyter_cache - run(f"jb clean {path}".split()) + result = cli.invoke(clean, path.as_posix()) + assert result.exit_code == 0 # Ensure _build and .jupyter_cache exist assert build_path.exists() assert build_path.joinpath(".jupyter_cache").exists() - run(f"jb clean --all {path}".split()) + result = cli.invoke(clean, ("--all", path.as_posix())) + assert result.exit_code == 0 # Ensure _build is removed assert not path.joinpath("_build").exists() # === Excepted errors === # Non-existent folder - with pytest.raises(ValueError): - out = run("jb clean doesnt/exist".split(), stderr=PIPE) - err = out.stderr.decode() - if "ValueError" in err: - raise ValueError(err) - assert "Path to book isn't a directory" in err + result = cli.invoke(clean, "doesnt/exist") + assert result.exit_code != 0 + assert isinstance(result.exception, RuntimeError) + assert "Path to book isn't a directory" in str(result.exception) -def test_clean_html(tmpdir): - path = path_books.joinpath("clean_cache") +def test_clean_html(cli, build_resources): + books, tocs = build_resources + path = books.joinpath("clean_cache") build_path = path.joinpath("_build") - run(f"jb build {path}".split()) + result = cli.invoke(build, path.as_posix()) + assert result.exit_code == 0 # Ensure _build exists assert build_path.exists() @@ -54,7 +53,8 @@ def test_clean_html(tmpdir): assert build_path.joinpath("html").exists() # Remove html - run(f"jb clean --html {path}".split()) + result = cli.invoke(clean, ("--html", path.as_posix())) + assert result.exit_code == 0 # Ensure _build exists assert build_path.exists() @@ -63,9 +63,11 @@ def test_clean_html(tmpdir): assert not build_path.joinpath("html").exists() -def test_clean_latex(tmpdir): - path = path_books.joinpath("clean_cache") - run(f"jb build {path}".split()) +def test_clean_latex(cli, build_resources): + books, tocs = build_resources + path = books.joinpath("clean_cache") + result = cli.invoke(build, path.as_posix()) + assert result.exit_code == 0 build_path = path.joinpath("_build") # Ensure _build exists @@ -77,7 +79,8 @@ def test_clean_latex(tmpdir): assert build_path.joinpath("latex").exists() # Remove html - run(f"jb clean --latex {path}".split()) + result = cli.invoke(clean, ("--latex", path.as_posix())) + assert result.exit_code == 0 # Ensure _build exists assert build_path.exists() @@ -86,9 +89,11 @@ def test_clean_latex(tmpdir): assert not build_path.joinpath("latex").exists() -def test_clean_html_latex(tmpdir): - path = path_books.joinpath("clean_cache") - run(f"jb build {path}".split()) +def test_clean_html_latex(cli, build_resources): + books, tocs = build_resources + path = books.joinpath("clean_cache") + result = cli.invoke(build, path.as_posix()) + assert result.exit_code == 0 build_path = path.joinpath("_build") @@ -103,7 +108,8 @@ def test_clean_html_latex(tmpdir): assert build_path.joinpath("html").exists() # Remove html - run(f"jb clean --html --latex {path}".split()) + result = cli.invoke(clean, ("--html", "--latex", path.as_posix())) + assert result.exit_code == 0 # Ensure _build exists assert build_path.exists() diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000000000000000000000000000000000000..188b044609564706f263bf44bcfbe0b92a14f40c --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,75 @@ +import pytest + +import jsonschema +from jupyter_book.config import get_final_config, validate_yaml +from jupyter_book.commands import sphinx + + +@pytest.mark.parametrize( + "user_config", + [ + {}, + {"title": "hallo"}, + {"html": {"extra_footer": ""}}, + {"execute": {"execute_notebooks": "cache"}}, + {"parse": {"myst_extended_syntax": True}}, + {"latex": {"latex_documents": {"targetname": "book.tex", "title": "other"}}}, + {"launch_buttons": {"binderhub_url": "other"}}, + {"repository": {"url": "other"}}, + {"exclude_patterns": ["new"]}, + { + "sphinx": { + "extra_extensions": ["other"], + "config": { + "html_theme_options": { + "launch_buttons": {"binderhub_url": "other"} + }, + "html_theme": "other", + "new": "value", + }, + } + }, + ], + ids=[ + "empty", + "title", + "html.extra_footer", + "execute.method", + "extended_syntax", + "latex_doc", + "launch_buttons", + "repository", + "exclude_patterns", + "sphinx", + ], +) +def test_get_final_config(user_config, data_regression): + final_config, metadata = get_final_config( + user_config, validate=True, raise_on_invalid=True + ) + data_regression.check( + {"_user_config": user_config, "final": final_config, "metadata": metadata} + ) + + +def test_validate_yaml(): + with pytest.raises(jsonschema.ValidationError): + validate_yaml({"title": 1}, raise_on_errors=True) + assert "Warning" in validate_yaml({"title": 1}, raise_on_errors=False) + assert validate_yaml({"title": ""}, raise_on_errors=False) is None + + +def test_config_sphinx_command(cli, temp_with_override, file_regression): + temp_with_override.joinpath("_config.yml").write_text( + "title: test\n", encoding="utf8" + ) + temp_with_override.joinpath("_toc.yml").write_text("\n", encoding="utf8") + result = cli.invoke(sphinx, temp_with_override.as_posix()) + assert result.exit_code == 0, result.exception + # remove global_toc which is path dependent + output = "\n".join( + line + for line in result.output.splitlines() + if not line.startswith("globaltoc_path") + ) + file_regression.check(output, encoding="utf8") diff --git a/tests/test_config/test_config_sphinx_command.txt b/tests/test_config/test_config_sphinx_command.txt new file mode 100644 index 0000000000000000000000000000000000000000..b02d29a6226ede7f23f5d150072cde25f7195d8f --- /dev/null +++ b/tests/test_config/test_config_sphinx_command.txt @@ -0,0 +1,26 @@ +author = 'The Jupyter Book community' +comments_config = {'hypothesis': False, 'utterances': False} +copyright = '2020' +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build'] +execution_allow_errors = False +execution_excludepatterns = [] +execution_in_temp = False +execution_timeout = 30 +extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinxcontrib.bibtex', 'sphinx_thebe', 'sphinx_comments', 'sphinx.ext.intersphinx', 'sphinx_panels'] +html_add_permalinks = '¶' +html_baseurl = '' +html_favicon = '' +html_logo = '' +html_sourcelink_suffix = '' +html_theme = 'sphinx_book_theme' +html_theme_options = {'search_bar_text': 'Search this book...', 'launch_buttons': {'notebook_interface': 'classic', 'binderhub_url': 'https://mybinder.org', 'jupyterhub_url': '', 'thebe': False, 'colab_url': 'https://colab.research.google.com'}, 'path_to_docs': '', 'repository_url': 'https://github.com/executablebooks/jupyter-book', 'repository_branch': 'master', 'google_analytics_id': '', 'extra_navbar': 'Powered by <a href="https://jupyterbook.org">Jupyter Book</a>', 'extra_footer': '', 'home_page_in_toc': True, 'use_repository_button': False, 'use_edit_page_button': False, 'use_issues_button': False} +html_title = 'test' +jupyter_cache = '' +jupyter_execute_notebooks = 'auto' +language = None +latex_engine = 'pdflatex' +myst_url_schemes = ['mailto', 'http', 'https'] +nb_output_stderr = 'show' +numfig = True +panels_add_boostrap_css = False +pygments_style = 'sphinx' diff --git a/tests/test_config/test_get_final_config_empty_.yml b/tests/test_config/test_get_final_config_empty_.yml new file mode 100644 index 0000000000000000000000000000000000000000..bcefda3fbf60a090533d3421380adf0eb44ea630 --- /dev/null +++ b/tests/test_config/test_get_final_config_empty_.yml @@ -0,0 +1,66 @@ +_user_config: {} +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_exclude_patterns_.yml b/tests/test_config/test_get_final_config_exclude_patterns_.yml new file mode 100644 index 0000000000000000000000000000000000000000..7fc9d4a74a28d19ee9f1f1a2e1a25bba00d8c7fd --- /dev/null +++ b/tests/test_config/test_get_final_config_exclude_patterns_.yml @@ -0,0 +1,69 @@ +_user_config: + exclude_patterns: + - new +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + - new + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_execute_method_.yml b/tests/test_config/test_get_final_config_execute_method_.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb49e5250909a0426bfb46842d351aa9cb31dbc1 --- /dev/null +++ b/tests/test_config/test_get_final_config_execute_method_.yml @@ -0,0 +1,68 @@ +_user_config: + execute: + execute_notebooks: cache +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: cache + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_extended_syntax_.yml b/tests/test_config/test_get_final_config_extended_syntax_.yml new file mode 100644 index 0000000000000000000000000000000000000000..dbeb9f7695ce0ba370d28055b02973ba4ca45a63 --- /dev/null +++ b/tests/test_config/test_get_final_config_extended_syntax_.yml @@ -0,0 +1,74 @@ +_user_config: + parse: + myst_extended_syntax: true +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_admonition_enable: true + myst_amsmath_enable: true + myst_deflist_enable: true + myst_dmath_enable: true + myst_figure_enable: true + myst_html_img_enable: true + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_html_extra_footer_.yml b/tests/test_config/test_get_final_config_html_extra_footer_.yml new file mode 100644 index 0000000000000000000000000000000000000000..f503af17e799624ea6c51e8014288b8fb2fee754 --- /dev/null +++ b/tests/test_config/test_get_final_config_html_extra_footer_.yml @@ -0,0 +1,68 @@ +_user_config: + html: + extra_footer: '' +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_latex_doc_.yml b/tests/test_config/test_get_final_config_latex_doc_.yml new file mode 100644 index 0000000000000000000000000000000000000000..64eebfdf825ed526424f9989c36f751864778da5 --- /dev/null +++ b/tests/test_config/test_get_final_config_latex_doc_.yml @@ -0,0 +1,71 @@ +_user_config: + latex: + latex_documents: + targetname: book.tex + title: other +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + targetname: book.tex + title: other diff --git a/tests/test_config/test_get_final_config_launch_buttons_.yml b/tests/test_config/test_get_final_config_launch_buttons_.yml new file mode 100644 index 0000000000000000000000000000000000000000..a693edcdfdca28b10954dc5490631fe4ea9127e1 --- /dev/null +++ b/tests/test_config/test_get_final_config_launch_buttons_.yml @@ -0,0 +1,68 @@ +_user_config: + launch_buttons: + binderhub_url: other +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: other + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_repository_.yml b/tests/test_config/test_get_final_config_repository_.yml new file mode 100644 index 0000000000000000000000000000000000000000..dc2a2c12e7487621d7c5c7a8341d0c5f6077b6aa --- /dev/null +++ b/tests/test_config/test_get_final_config_repository_.yml @@ -0,0 +1,68 @@ +_user_config: + repository: + url: other +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: other + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_sphinx_.yml b/tests/test_config/test_get_final_config_sphinx_.yml new file mode 100644 index 0000000000000000000000000000000000000000..85b86937f4863ddc93650574fe433b54bf301d18 --- /dev/null +++ b/tests/test_config/test_get_final_config_sphinx_.yml @@ -0,0 +1,62 @@ +_user_config: + sphinx: + config: + html_theme: other + html_theme_options: + launch_buttons: + binderhub_url: other + new: value + extra_extensions: + - other +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + - other + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: other + html_theme_options: + launch_buttons: + binderhub_url: other + html_title: My Jupyter Book + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + new: value + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: My Jupyter Book diff --git a/tests/test_config/test_get_final_config_title_.yml b/tests/test_config/test_get_final_config_title_.yml new file mode 100644 index 0000000000000000000000000000000000000000..334db1140dc202d9b56514d143c4be2198e96f92 --- /dev/null +++ b/tests/test_config/test_get_final_config_title_.yml @@ -0,0 +1,67 @@ +_user_config: + title: hallo +final: + author: The Jupyter Book community + comments_config: + hypothesis: false + utterances: false + copyright: '2020' + exclude_patterns: + - '**.ipynb_checkpoints' + - .DS_Store + - Thumbs.db + - _build + execution_allow_errors: false + execution_excludepatterns: [] + execution_in_temp: false + execution_timeout: 30 + extensions: + - sphinx_togglebutton + - sphinx_copybutton + - myst_nb + - jupyter_book + - sphinxcontrib.bibtex + - sphinx_thebe + - sphinx_comments + - sphinx.ext.intersphinx + - sphinx_panels + html_add_permalinks: ¶ + html_baseurl: '' + html_favicon: '' + html_logo: '' + html_sourcelink_suffix: '' + html_theme: sphinx_book_theme + html_theme_options: + extra_footer: '' + extra_navbar: Powered by <a href="https://jupyterbook.org">Jupyter Book</a> + google_analytics_id: '' + home_page_in_toc: true + launch_buttons: + binderhub_url: https://mybinder.org + colab_url: https://colab.research.google.com + jupyterhub_url: '' + notebook_interface: classic + thebe: false + path_to_docs: '' + repository_branch: master + repository_url: https://github.com/executablebooks/jupyter-book + search_bar_text: Search this book... + use_edit_page_button: false + use_issues_button: false + use_repository_button: false + html_title: hallo + jupyter_cache: '' + jupyter_execute_notebooks: auto + language: null + latex_engine: pdflatex + myst_url_schemes: + - mailto + - http + - https + nb_output_stderr: show + numfig: true + panels_add_boostrap_css: false + pygments_style: sphinx +metadata: + latex_doc_overrides: + title: hallo diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 95530a4f189597119300d664418b8a50fec46560..7ee01932b4550bd434dd3ad2b9553f4f330d114d 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -1,18 +1,24 @@ """Testing PDF functionality of the CLI.""" from pathlib import Path -from subprocess import run + +from click.testing import CliRunner +import pytest + +from jupyter_book.commands import build path_tests = Path(__file__).parent -def test_pdfhtml(tmpdir): - path_output = Path(tmpdir).absolute() +@pytest.mark.requires_chrome +def test_pdfhtml(cli: CliRunner, temp_with_override: Path): + path_output = temp_with_override.absolute() # test for build path_template = path_tests.parent.joinpath("jupyter_book", "book_template") - cmd = f"jb build {path_template} --path-output {path_output} --builder pdfhtml" - run(cmd.split(), check=True) + cmd = f"{path_template} --path-output {path_output} --builder pdfhtml" + result = cli.invoke(build, cmd.split()) + assert result.exit_code == 0 path_html = path_output.joinpath("_build", "html") path_pdf = path_output.joinpath("_build", "pdf") assert path_html.joinpath("index.html").exists() @@ -22,8 +28,9 @@ def test_pdfhtml(tmpdir): path_page = path_tests.parent.joinpath("jupyter_book", "book_template").joinpath( "markdown.md" ) - cmd = f"jb build {path_page} --path-output {path_output} --builder pdfhtml" - run(cmd.split(), check=True) + cmd = f"{path_page} --path-output {path_output} --builder pdfhtml" + result = cli.invoke(build, cmd.split()) + assert result.exit_code == 0 path_html = path_output.joinpath("_build", "_page", "markdown", "html") path_pdf = path_output.joinpath("_build", "_page", "markdown", "pdf") assert path_html.joinpath("markdown.html").exists() @@ -31,13 +38,15 @@ def test_pdfhtml(tmpdir): # TODO: Update to include more detailed tests for pdflatex build chain -def test_pdflatex(tmpdir): - path_output = Path(tmpdir).absolute() +@pytest.mark.requires_tex +def test_pdflatex(cli: CliRunner, temp_with_override: Path): + path_output = temp_with_override.absolute() # test for build path_template = path_tests.parent.joinpath("jupyter_book", "book_template") - cmd = f"jb build {path_template} --path-output {path_output} --builder pdflatex" - run(cmd.split(), check=True) + cmd = f"{path_template} --path-output {path_output} --builder pdflatex" + result = cli.invoke(build, cmd.split()) + assert result.exit_code == 0 path_pdf = path_output.joinpath("_build", "latex") assert path_pdf.joinpath("book.pdf").exists() @@ -45,7 +54,8 @@ def test_pdflatex(tmpdir): path_page = path_tests.parent.joinpath("jupyter_book", "book_template").joinpath( "markdown.md" ) - cmd = f"jb build {path_page} --path-output {path_output} --builder pdflatex" - run(cmd.split(), check=True) + cmd = f"{path_page} --path-output {path_output} --builder pdflatex" + result = cli.invoke(build, cmd.split()) + assert result.exit_code == 0 path_pdf = path_output.joinpath("_build", "_page", "markdown", "latex") assert path_pdf.joinpath("book.pdf").exists() diff --git a/tests/test_toc.py b/tests/test_toc.py index fded80831be2e371938076dd02b06b3e6d94b8bf..b6037bf572dbd5991a07a6e5a9850df3ef891f45 100644 --- a/tests/test_toc.py +++ b/tests/test_toc.py @@ -1,39 +1,46 @@ from pathlib import Path -from subprocess import run, PIPE -import pytest + +from click.testing import CliRunner import yaml from bs4 import BeautifulSoup +from jupyter_book.commands import build, toc path_books = Path(__file__).parent.joinpath("books") -def test_toc(build_resources): +def test_toc_basic(cli: CliRunner, build_resources): books, tocs = build_resources - run(f"jb toc {tocs}".split(), check=True) + # run(f"jb toc {tocs}".split(), check=True) + result = cli.invoke(toc, tocs.as_posix()) + assert result.exit_code == 0 + toc_yaml = tocs.joinpath("_toc.yml") _ = yaml.safe_load(toc_yaml.read_text(encoding="utf8")) - # Folder with no content should return none + +def test_toc_fail(cli: CliRunner, build_resources): + """Folder with no content should return none""" + books, tocs = build_resources p_empty = tocs.parent - with pytest.raises(ValueError): - out = run(f"jb toc {p_empty}".split(), stderr=PIPE, stdout=PIPE) - err = out.stderr.decode() - if "ValueError" in err: - raise ValueError(err) - assert "No content files were found in" in err + result = cli.invoke(toc, p_empty.as_posix()) + assert result.exit_code != 0 + assert isinstance(result.exception, RuntimeError) + assert "No content files were found in" in str(result.exception) -def test_toc_add_titles(build_resources): +def test_toc_add_titles(cli: CliRunner, build_resources): books, tocs = build_resources - run(f"jb toc {tocs}".split(), check=True) + result = cli.invoke(toc, tocs.as_posix()) + assert result.exit_code == 0 toc_yaml = tocs.joinpath("_toc.yml") res = yaml.safe_load(toc_yaml.read_text(encoding="utf8")) assert "title" not in res for section in res["sections"]: assert "title" not in section - run(f"jb toc {tocs} --add-titles".split(), check=True) + result = cli.invoke(toc, (tocs.as_posix(), "--add-titles")) + assert result.exit_code == 0 toc_yaml = tocs.joinpath("_toc.yml") res = yaml.safe_load(toc_yaml.read_text(encoding="utf8")) assert "title" in res @@ -41,9 +48,9 @@ def test_toc_add_titles(build_resources): assert "title" in section -def test_toc_numbered(tmpdir, file_regression): +def test_toc_numbered(cli: CliRunner, temp_with_override, file_regression): """Testing that numbers make it into the sidebar""" - path_output = Path(tmpdir).joinpath("mybook").absolute() + path_output = temp_with_override.joinpath("mybook").absolute() toc_list = [ "_toc_numbered.yml", # Numbered in top-level title "_toc_numbered_parts.yml", # Numbered in top-level title w/ parts @@ -53,10 +60,18 @@ def test_toc_numbered(tmpdir, file_regression): # Numbering with files p_toc = path_books.joinpath("toc") path_toc = p_toc.joinpath(itoc) - run( - f"jb build {p_toc} --path-output {path_output} --toc {path_toc} -W".split(), - check=True, + result = cli.invoke( + build, + [ + p_toc.as_posix(), + "--path-output", + path_output.as_posix(), + "--toc", + path_toc.as_posix(), + "-W", + ], ) + assert result.exit_code == 0 path_toc_directive = path_output.joinpath("_build", "html", "index.html") diff --git a/tests/test_tocdirective.py b/tests/test_tocdirective.py index 87c3e8bf3c33435e104e7c03864d953ff03ffd73..a27ed1efaefa542916b8ba531a9dc8589f878864 100644 --- a/tests/test_tocdirective.py +++ b/tests/test_tocdirective.py @@ -1,22 +1,35 @@ +import os from pathlib import Path -from subprocess import run, PIPE + +from click.testing import CliRunner from bs4 import BeautifulSoup +import pytest + +from jupyter_book.commands import build path_tests = Path(__file__).parent.resolve() path_books = path_tests.joinpath("books") path_root = path_tests.parent -def test_toc_startwithlist(tmpdir, file_regression): +def test_toc_startwithlist(cli: CliRunner, temp_with_override, file_regression): """Testing a basic _toc.yml for tableofcontents directive""" - path_output = Path(tmpdir).joinpath("mybook").absolute() + path_output = temp_with_override.joinpath("mybook").absolute() # Regular TOC should work p_toc = path_books.joinpath("toc") path_toc = p_toc.joinpath("_toc_startwithlist.yml") - run( - f"jb build {p_toc} --path-output {path_output} --toc {path_toc} -W".split(), - check=True, + result = cli.invoke( + build, + [ + p_toc.as_posix(), + "--path-output", + path_output.as_posix(), + "--toc", + path_toc.as_posix(), + "-W", + ], ) + assert result.exit_code == 0 path_toc_directive = path_output.joinpath("_build", "html", "index.html") @@ -24,19 +37,27 @@ def test_toc_startwithlist(tmpdir, file_regression): soup = BeautifulSoup(path_toc_directive.read_text(encoding="utf8"), "html.parser") toc = soup.find_all("div", class_="tableofcontents-wrapper")[0] - file_regression.check(str(toc), extension=".html") + file_regression.check(str(toc), extension=".html", encoding="utf8") -def test_toc_parts(tmpdir, file_regression): +def test_toc_parts(cli: CliRunner, temp_with_override, file_regression): """Testing `header` in _toc.yml""" - path_output = Path(tmpdir).joinpath("mybook").absolute() + path_output = temp_with_override.joinpath("mybook").absolute() # Regular TOC should work p_toc = path_books.joinpath("toc") path_toc = p_toc.joinpath("_toc_parts.yml") - run( - f"jb build {p_toc} --path-output {path_output} --toc {path_toc} -W".split(), - check=True, + result = cli.invoke( + build, + [ + p_toc.as_posix(), + "--path-output", + path_output.as_posix(), + "--toc", + path_toc.as_posix(), + "-W", + ], ) + assert result.exit_code == 0 path_index = path_output.joinpath("_build", "html", "index.html") @@ -45,7 +66,10 @@ def test_toc_parts(tmpdir, file_regression): toc = soup.find_all("div", class_="tableofcontents-wrapper")[0] file_regression.check( - str(toc), basename="test_toc_parts_directive", extension=".html" + str(toc), + basename="test_toc_parts_directive", + extension=".html", + encoding="utf8", ) # check the sidebar structure is correct @@ -53,40 +77,61 @@ def test_toc_parts(tmpdir, file_regression): soup.select(".bd-links")[0].prettify(), basename="test_toc_parts_sidebar", extension=".html", + encoding="utf8", ) # TODO: remove these tests in 0.7.5 when chapters: is deprecated # check that using `chapter:` raises a warning but outputs the same thing path_toc = p_toc.joinpath("_toc_chapters.yml") - out = run( - f"jb build {p_toc} --path-output {path_output} --toc {path_toc}".split(), - check=True, - stderr=PIPE, - stdout=PIPE, + result = cli.invoke( + build, + [ + p_toc.as_posix(), + "--path-output", + path_output.as_posix(), + "--toc", + path_toc.as_posix(), + ], ) - assert "Found `- chapter:` in `_toc.yml`." in out.stderr.decode() + assert result.exit_code == 0 + + assert "Found `- chapter:` in `_toc.yml`." in result.output soup = BeautifulSoup(path_index.read_text(encoding="utf8"), "html.parser") file_regression.check( soup.select(".bd-links")[0].prettify(), basename="test_toc_parts_sidebar", extension=".html", + encoding="utf8", ) -def test_toc_urllink(tmpdir, file_regression): +@pytest.mark.skipif( + os.name == "nt", + reason="Theme error writing content1: " + "filename, directory name, or volume label syntax is incorrect", +) +def test_toc_urllink(cli: CliRunner, temp_with_override, file_regression): """Testing with additional `url` link key in _toc.yml""" - path_output = Path(tmpdir).joinpath("mybook").absolute() + path_output = temp_with_override.joinpath("mybook").absolute() # Regular TOC should work p_toc = path_books.joinpath("toc") path_toc = p_toc.joinpath("_toc_urllink.yml") - run( - f"jb build {p_toc} --path-output {path_output} --toc {path_toc} -W".split(), - check=True, + result = cli.invoke( + build, + [ + p_toc.as_posix(), + "--path-output", + path_output.as_posix(), + "--toc", + path_toc.as_posix(), + "-W", + ], ) + assert result.exit_code == 0, result.output path_toc_directive = path_output.joinpath("_build", "html", "index.html") # get the tableofcontents markup soup = BeautifulSoup(path_toc_directive.read_text(encoding="utf8"), "html.parser") toc = soup.find_all("div", class_="tableofcontents-wrapper")[0] - file_regression.check(str(toc), extension=".html") + file_regression.check(str(toc), extension=".html", encoding="utf8") diff --git a/tests/test_utils.py b/tests/test_utils.py index 9fba24809114c4fc1fe145600de38abc2ac54fda..9794b0979aaea9b8c441a31c65d64749c8b9b38b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,15 +1,17 @@ -from pathlib import Path -from subprocess import run, PIPE +from click.testing import CliRunner import pytest + +from jupyter_book.commands import init as myst_init from jupyter_book.utils import init_myst_file -def test_myst_init(tmpdir): +def test_myst_init(cli: CliRunner, temp_with_override): """Test adding myst metadata to text files.""" - path = Path(tmpdir).joinpath("tmp.md").absolute() + path = temp_with_override.joinpath("tmp.md").absolute() text = "TEST" with open(path, "w") as ff: ff.write(text) + init_myst_file(path, kernel="python3") # Make sure it runs properly. Default kernel should be python3 @@ -19,7 +21,11 @@ def test_myst_init(tmpdir): assert "name: python3" in new_text # Make sure the CLI works too - run(f"jb myst init {path} --kernel python3".split(), check=True, stdout=PIPE) + with pytest.warns(None) as records: + result = cli.invoke(myst_init, f"{path} --kernel python3".split()) + # old versions of jupytext give: UserWarning: myst-parse failed unexpectedly + assert len(records) == 0, records + assert result.exit_code == 0 # Non-existent kernel with pytest.raises(Exception) as err: diff --git a/tox.ini b/tox.ini index ece7e08f2659d1a94923311aa006fb97b4ffd67f..75e25ac85ee84b1350163b8f6685604a3c2ccd04 100644 --- a/tox.ini +++ b/tox.ini @@ -11,9 +11,30 @@ # then then deleting compiled files has been found to fix it: `find . -name \*.pyc -delete` [tox] -envlist = py{36,37,38} +envlist = py{36,37,38}-sphinx{2,3} -[testenv:py{36,37,38}] +[testenv] +# only recreate the environment when we use `tox -r` recreate = false + +[testenv:py{36,37,38}-sphinx{2,3}] extras = testing +deps = + sphinx2: sphinx>=2,<3 + sphinx3: sphinx>=3,<4 commands = pytest {posargs} + +[testenv:docs-{update,clean}] +extras = sphinx +commands = + clean: jupyter-book clean --all docs/ + jupyter-book build -W -n --keep-going --builder html {posargs:docs/} + +[pytest] +timeout = 100 +markers = + requires_tex: mark a test which requires a TeX installation. + requires_chrome: mark a test which requires a chrome/chromium browser +filterwarnings = + ignore::DeprecationWarning:pyee.* + ignore::DeprecationWarning:pybtex.*