├── nbconvert ├── py.typed ├── utils │ ├── __init__.py │ ├── lexers.py │ ├── _contextlib_chdir.py │ ├── exceptions.py │ ├── version.py │ ├── base.py │ ├── text.py │ └── iso639_1.py ├── resources │ └── __init__.py ├── __main__.py ├── writers │ ├── __init__.py │ ├── stdout.py │ ├── base.py │ └── debug.py ├── conftest.py ├── postprocessors │ ├── __init__.py │ └── base.py ├── templates │ ├── README.md │ └── skeleton │ │ ├── README.md │ │ └── Makefile ├── _version.py ├── exporters │ ├── qtpng.py │ ├── python.py │ ├── qtpdf.py │ ├── __init__.py │ ├── notebook.py │ ├── asciidoc.py │ ├── markdown.py │ ├── rst.py │ └── qt_exporter.py ├── filters │ ├── metadata.py │ ├── filter_links.py │ ├── datatypefilter.py │ ├── __init__.py │ ├── latex.py │ └── widgetsdatatypefilter.py ├── preprocessors │ ├── clearoutput.py │ ├── __init__.py │ ├── coalescestreams.py │ ├── convertfigures.py │ ├── regexremove.py │ ├── latex.py │ └── base.py └── __init__.py ├── tests ├── __init__.py ├── utils │ ├── __init__.py │ ├── test_version.py │ └── test_io.py ├── exporters │ ├── __init__.py │ ├── files │ │ ├── esmodule.js │ │ ├── lablike.html.j2 │ │ ├── esmodule.html.j2 │ │ ├── notebook3.ipynb │ │ ├── rawtest.ipynb │ │ ├── prompt_numbers.ipynb │ │ └── rst_output.ipynb │ ├── test_qtpdf.py │ ├── test_python.py │ ├── test_qtpng.py │ ├── base.py │ ├── test_markdown.py │ ├── test_notebook.py │ ├── cheese.py │ ├── test_webpdf.py │ ├── test_pdf.py │ ├── test_script.py │ ├── test_asciidoc.py │ └── test_rst.py ├── filters │ ├── __init__.py │ ├── test_metadata.py │ ├── test_datatypefilter.py │ ├── test_latex.py │ ├── test_pandoc.py │ └── test_highlight.py ├── writers │ ├── __init__.py │ ├── test_stdout.py │ └── test_debug.py ├── postprocessors │ ├── __init__.py │ └── test_serve.py ├── preprocessors │ ├── __init__.py │ ├── files │ │ ├── HelloWorld.ipynb │ │ └── MixedMarkdown.ipynb │ ├── fake_kernelmanager.py │ ├── test_csshtmlheader.py │ ├── test_highlightmagics.py │ ├── test_latex.py │ ├── test_clearoutput.py │ ├── test_regexremove.py │ └── base.py ├── files │ ├── testimage.png │ ├── containerized_deployments.jpeg │ ├── override.py │ ├── jupyter_nbconvert_config.py │ ├── hello.py │ ├── notebook_jl.ipynb │ ├── latex-linked-image.ipynb │ ├── notebook5_embed_images.ipynb │ ├── Unexecuted_widget.ipynb │ ├── Unexecuted_widget_2.ipynb │ ├── notebook3_with_errors.ipynb │ └── notebook_tags.ipynb ├── README.md ├── exporter_entrypoint │ ├── eptest-0.1.dist-info │ │ └── entry_points.txt │ └── eptest.py ├── testutils.py └── fake_exporters.py ├── docs ├── source │ ├── _static │ │ ├── empty.txt │ │ ├── writer_inheritance.png │ │ ├── exporter_inheritance.png │ │ └── preprocessor_inheritance.png │ ├── api │ │ ├── index.rst │ │ ├── postprocessors.rst │ │ ├── writers.rst │ │ ├── nbconvertapp.rst │ │ ├── preprocessors.rst │ │ ├── exporters.rst │ │ └── filters.rst │ ├── latex_citations.rst │ ├── need_help.rst │ ├── dejavu.rst │ ├── highlighting.rst │ └── index.rst ├── api_examples │ └── template_path │ │ ├── media │ │ ├── image1.png │ │ └── image2.png │ │ ├── project_templates │ │ └── nbconvert │ │ │ └── templates │ │ │ └── classic_clone │ │ │ └── conf.json │ │ ├── make_html.py │ │ └── quiz_notebook.py ├── README.md └── autogen_config.py ├── .prettierignore ├── share └── templates │ ├── basic │ ├── index.html.j2 │ └── conf.json │ ├── webpdf │ ├── index.pdf.j2 │ └── conf.json │ ├── rst │ └── conf.json │ ├── script │ ├── conf.json │ └── script.j2 │ ├── asciidoc │ ├── conf.json │ └── index.asciidoc.j2 │ ├── markdown │ ├── conf.json │ └── index.md.j2 │ ├── python │ ├── conf.json │ └── index.py.j2 │ ├── compatibility │ ├── full.tpl │ └── display_priority.tpl │ ├── latex │ ├── conf.json │ ├── style_bw_python.tex.j2 │ ├── index.tex.j2 │ ├── style_python.tex.j2 │ ├── report.tex.j2 │ ├── display_priority.j2 │ ├── style_bw_ipython.tex.j2 │ ├── style_ipython.tex.j2 │ └── document_contents.tex.j2 │ ├── base │ ├── cell_id_anchor.j2 │ ├── celltags.j2 │ ├── mathjax.html.j2 │ ├── jupyter_widgets.html.j2 │ └── display_priority.j2 │ ├── lab │ └── conf.json │ ├── classic │ └── conf.json │ └── reveal │ ├── conf.json │ ├── cellslidedata.j2 │ ├── base.html.j2 │ └── static │ └── custom_reveal.css ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── enforce-label.yml │ ├── docs.yml │ ├── prep-release.yml │ └── publish-release.yml ├── SECURITY.md ├── .readthedocs.yaml ├── check_requirements.py ├── .gitignore ├── LICENSE ├── RELEASE.md ├── CONTRIBUTING.md ├── .pre-commit-config.yaml └── hatch_build.py /nbconvert/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nbconvert/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/exporters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/filters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/writers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/_static/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nbconvert/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/postprocessors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/preprocessors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | tests/files/*.html 2 | -------------------------------------------------------------------------------- /share/templates/basic/index.html.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'classic/base.html.j2' -%} 2 | -------------------------------------------------------------------------------- /share/templates/webpdf/index.pdf.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'lab/index.html.j2' -%} 2 | -------------------------------------------------------------------------------- /tests/exporters/files/esmodule.js: -------------------------------------------------------------------------------- 1 | const blerg = true 2 | export { blerg }; 3 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Initial pre-commit reformat 2 | 68b496b7fcf4cfbffe9e1656ac52400a24cacc45 3 | -------------------------------------------------------------------------------- /tests/files/testimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/tests/files/testimage.png -------------------------------------------------------------------------------- /nbconvert/__main__.py: -------------------------------------------------------------------------------- 1 | """nbconvert cli entry point.""" 2 | 3 | from .nbconvertapp import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | To compare nbconvert html output to the notebook's native html see https://gist.github.com/9241376.git. 2 | -------------------------------------------------------------------------------- /share/templates/rst/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/x-rst": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/basic/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "classic", 3 | "mimetypes": { 4 | "text/html": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/script/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/plain": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/source/_static/writer_inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/docs/source/_static/writer_inheritance.png -------------------------------------------------------------------------------- /share/templates/asciidoc/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/asciidoc": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/markdown/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/markdown": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/python/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/x-python": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/script/script.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'null.j2' -%} 2 | 3 | {% block input %} 4 | {{ cell.source }} 5 | {% endblock input %} 6 | -------------------------------------------------------------------------------- /share/templates/webpdf/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "lab", 3 | "mimetypes": { 4 | "application/pdf": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/files/containerized_deployments.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/tests/files/containerized_deployments.jpeg -------------------------------------------------------------------------------- /docs/source/_static/exporter_inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/docs/source/_static/exporter_inheritance.png -------------------------------------------------------------------------------- /docs/api_examples/template_path/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/docs/api_examples/template_path/media/image1.png -------------------------------------------------------------------------------- /docs/api_examples/template_path/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/docs/api_examples/template_path/media/image2.png -------------------------------------------------------------------------------- /docs/source/_static/preprocessor_inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbconvert/HEAD/docs/source/_static/preprocessor_inheritance.png -------------------------------------------------------------------------------- /tests/exporters/files/lablike.html.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'lab/index.html.j2' -%} 2 | {%- block body_footer -%} 3 | UNIQUE 4 | {%- endblock body_footer -%} 5 | -------------------------------------------------------------------------------- /nbconvert/writers/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import WriterBase 2 | from .debug import DebugWriter 3 | from .files import FilesWriter 4 | from .stdout import StdoutWriter 5 | -------------------------------------------------------------------------------- /share/templates/compatibility/full.tpl: -------------------------------------------------------------------------------- 1 | {{ resources.deprecated("This template is deprecated, please use classic/index.html.j2") }} 2 | {%- extends 'index.html.j2' -%} 3 | -------------------------------------------------------------------------------- /docs/api_examples/template_path/project_templates/nbconvert/templates/classic_clone/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/html": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /share/templates/compatibility/display_priority.tpl: -------------------------------------------------------------------------------- 1 | {{ resources.deprecated("This template is deprecated, please use base/display_priority.j2") }} 2 | {%- extends 'display_priority.j2' -%} 3 | -------------------------------------------------------------------------------- /share/templates/latex/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/latex": true, 5 | "text/tex": true, 6 | "application/pdf": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/exporters/files/esmodule.html.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'lab/index.html.j2' -%} 2 | {%- block html_head_js -%} 3 | {{ resources.include_js("esmodule.js", True) }} 4 | {%- endblock html_head_js -%} 5 | -------------------------------------------------------------------------------- /tests/exporter_entrypoint/eptest-0.1.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [nbconvert.exporters] 2 | entrypoint_test = eptest:DummyExporter 3 | 4 | [nbconvert.exporters.script] 5 | dummy = eptest:DummyScriptExporter 6 | -------------------------------------------------------------------------------- /share/templates/base/cell_id_anchor.j2: -------------------------------------------------------------------------------- 1 | {%- macro cell_id_anchor(cell) -%} 2 | {% if cell.id | length > 0 -%} 3 | id="{{ ('cell-id=' ~ cell.id) | escape_html -}}" 4 | {%- endif %} 5 | {%- endmacro %} 6 | -------------------------------------------------------------------------------- /tests/files/override.py: -------------------------------------------------------------------------------- 1 | c = get_config() # type:ignore # noqa 2 | 3 | # Export all the notebooks in the current directory to the sphinx_howto format. 4 | c.NbConvertApp.notebooks = ["notebook2.ipynb"] 5 | c.NbConvertApp.export_format = "python" 6 | -------------------------------------------------------------------------------- /nbconvert/conftest.py: -------------------------------------------------------------------------------- 1 | """pytest configuration.""" 2 | 3 | import asyncio 4 | import os 5 | 6 | if os.name == "nt": 7 | asyncio.set_event_loop_policy( 8 | asyncio.WindowsSelectorEventLoopPolicy() # type:ignore[attr-defined] 9 | ) 10 | -------------------------------------------------------------------------------- /tests/files/jupyter_nbconvert_config.py: -------------------------------------------------------------------------------- 1 | c = get_config() # type:ignore # noqa 2 | 3 | # Export all the notebooks in the current directory to the sphinx_howto format. 4 | c.NbConvertApp.notebooks = ["notebook1.ipynb"] 5 | c.NbConvertApp.export_format = "python" 6 | -------------------------------------------------------------------------------- /tests/files/hello.py: -------------------------------------------------------------------------------- 1 | from nbconvert.writers.base import WriterBase 2 | 3 | 4 | class HelloWriter(WriterBase): 5 | def write(self, output, resources, notebook_name=None, **kw): 6 | with open("hello.txt", "w") as outfile: 7 | outfile.write("hello world") 8 | -------------------------------------------------------------------------------- /share/templates/base/celltags.j2: -------------------------------------------------------------------------------- 1 | {%- macro celltags(cell) -%} 2 | {% if cell.metadata.tags | length > 0 -%} 3 | {% for tag in (cell.metadata.tags) -%} 4 | {{ (' celltag_' ~ tag) | escape_html -}} 5 | {%- endfor -%} 6 | {%- endif %} 7 | {%- endmacro %} 8 | -------------------------------------------------------------------------------- /nbconvert/utils/lexers.py: -------------------------------------------------------------------------------- 1 | """Deprecated as of 5.0; import from IPython.lib.lexers instead.""" 2 | 3 | from warnings import warn 4 | 5 | warn("nbconvert.utils.lexers is deprecated as of 5.0. Use IPython.lib.lexers", stacklevel=2) 6 | 7 | from IPython.lib.lexers import * # noqa: F403, E402 8 | -------------------------------------------------------------------------------- /share/templates/lab/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/html": true 5 | }, 6 | "preprocessors": { 7 | "100-pygments": { 8 | "type": "nbconvert.preprocessors.CSSHTMLHeaderPreprocessor", 9 | "enabled": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/exporter_entrypoint/eptest.py: -------------------------------------------------------------------------------- 1 | from nbconvert.exporters import Exporter 2 | 3 | 4 | class DummyExporter(Exporter): 5 | pass 6 | 7 | 8 | class DummyScriptExporter(Exporter): 9 | def from_notebook_node(self, nb, resources=None, **kw): 10 | return "dummy-script-exported", resources 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | ## name: Question about: If you need some help labels: question 4 | 5 | If you have a question, you can file an issue here or 6 | preferably ask on [Jupyter Discourse](https://discourse.jupyter.org/). 7 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | Python API for working with nbconvert 2 | ===================================== 3 | 4 | .. module:: nbconvert 5 | 6 | Contents: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | nbconvertapp 12 | exporters 13 | preprocessors 14 | filters 15 | writers 16 | postprocessors 17 | -------------------------------------------------------------------------------- /nbconvert/postprocessors/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import PostProcessorBase 2 | 3 | # protect against unavailable tornado 4 | try: 5 | from .serve import ServePostProcessor 6 | except ImportError: 7 | ServePostProcessor = None # type:ignore[misc,assignment] 8 | 9 | __all__ = ["PostProcessorBase", "ServePostProcessor"] 10 | -------------------------------------------------------------------------------- /share/templates/classic/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "base", 3 | "mimetypes": { 4 | "text/html": true 5 | }, 6 | "preprocessors": { 7 | "100-pygments": { 8 | "type": "nbconvert.preprocessors.CSSHTMLHeaderPreprocessor", 9 | "enabled": true, 10 | "style": "default" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Set update schedule for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates to GitHub Actions every weekday 8 | interval: "weekly" 9 | groups: 10 | actions: 11 | patterns: 12 | - "*" 13 | -------------------------------------------------------------------------------- /nbconvert/templates/README.md: -------------------------------------------------------------------------------- 1 | # README FIRST 2 | 3 | Please do not add new templates for nbconvert here. 4 | 5 | In order to speed up the distribution of nbconvert templates and make it 6 | simpler to share such contributions, we encourage [sharing those links on our 7 | wiki 8 | page](https://github.com/ipython/ipython/wiki/Cookbook:%20nbconvert%20templates). 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | All IPython and Jupyter security are handled via security@jupyter.org. 6 | You can find more information on the Jupyter website. https://jupyter.org/security 7 | 8 | ## Tidelift 9 | 10 | You can report security concerns for IPython via the [Tidelift platform](https://tidelift.com). 11 | -------------------------------------------------------------------------------- /tests/testutils.py: -------------------------------------------------------------------------------- 1 | from shutil import which 2 | 3 | import pytest 4 | 5 | 6 | def onlyif_cmds_exist(*commands): 7 | """ 8 | Decorator to skip test when at least one of `commands` is not found. 9 | """ 10 | for cmd in commands: 11 | if not which(cmd): 12 | return pytest.mark.skip(f"This test runs only if command '{cmd}' is installed") 13 | return lambda f: f 14 | -------------------------------------------------------------------------------- /.github/workflows/enforce-label.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - name: enforce-triage-label 13 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | ## name: Feature Request about: Suggest an idea for this project 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /share/templates/reveal/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "lab", 3 | "mimetypes": { 4 | "text/html": true 5 | }, 6 | "preprocessors": { 7 | "100-pygments": { 8 | "type": "nbconvert.preprocessors.CSSHTMLHeaderPreprocessor", 9 | "enabled": true 10 | }, 11 | "500-reveal": { 12 | "type": "nbconvert.exporters.slides._RevealMetadataPreprocessor", 13 | "enabled": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/source/api/postprocessors.rst: -------------------------------------------------------------------------------- 1 | Postprocessors 2 | ============== 3 | 4 | .. module:: nbconvert.postprocessors 5 | 6 | .. seealso:: 7 | 8 | :doc:`/config_options` 9 | Configurable options for the nbconvert application 10 | 11 | .. autoclass:: PostProcessorBase 12 | 13 | .. automethod:: postprocess 14 | 15 | Specialized postprocessors 16 | -------------------------- 17 | 18 | .. autoclass:: ServePostProcessor 19 | 20 | .. automethod:: postprocess 21 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | sphinx: 9 | configuration: docs/source/conf.py 10 | 11 | formats: all 12 | 13 | python: 14 | install: 15 | - method: pip 16 | path: . 17 | extra_requirements: 18 | - docs 19 | 20 | build: 21 | os: ubuntu-22.04 22 | tools: 23 | python: "3.11" 24 | -------------------------------------------------------------------------------- /share/templates/reveal/cellslidedata.j2: -------------------------------------------------------------------------------- 1 | {%- macro cellslidedata(cell) -%} 2 | {% if cell.metadata.slideshow | length > 0 -%} 3 | {% if cell.metadata.slideshow.data | length > 0 -%} 4 | {% for key in (cell.metadata.slideshow.data) -%} 5 | {{- (' data_' ~ key)|replace("_", "-") -}}="{{- cell.metadata.slideshow.data[key]|escape_html -}}" 6 | {%- endfor -%} 7 | {%- endif %} 8 | {%- endif %} 9 | {%- endmacro %} 10 | -------------------------------------------------------------------------------- /docs/source/api/writers.rst: -------------------------------------------------------------------------------- 1 | Writers 2 | ======= 3 | 4 | .. module:: nbconvert.writers 5 | 6 | .. seealso:: 7 | 8 | :doc:`/config_options` 9 | Configurable options for the nbconvert application 10 | 11 | .. autoclass:: WriterBase 12 | 13 | .. automethod:: __init__ 14 | 15 | .. automethod:: write 16 | 17 | Specialized writers 18 | ------------------- 19 | 20 | .. autoclass:: DebugWriter 21 | 22 | .. autoclass:: FilesWriter 23 | 24 | .. autoclass:: StdoutWriter 25 | -------------------------------------------------------------------------------- /tests/preprocessors/files/HelloWorld.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "Hello World\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "print(\"Hello World\")" 20 | ] 21 | } 22 | ], 23 | "metadata": {}, 24 | "nbformat": 4, 25 | "nbformat_minor": 0 26 | } 27 | -------------------------------------------------------------------------------- /tests/utils/test_version.py: -------------------------------------------------------------------------------- 1 | from nbconvert.utils.version import check_version 2 | 3 | 4 | def test_check_version(): 5 | """Test the behaviour of check_version. 6 | 7 | This is mostly used to make sure the pandoc version is appropriate for the library. 8 | """ 9 | 10 | assert check_version("1.19.2.4", "1.12.1") 11 | assert check_version("2.2.3.2", "1.12.1") 12 | assert check_version("1.19.2.4", "1.12.1", max_v="2.0") 13 | assert not check_version("2.2.3.2", "1.12.1", max_v="2.0") 14 | -------------------------------------------------------------------------------- /nbconvert/_version.py: -------------------------------------------------------------------------------- 1 | """nbconvert version info.""" 2 | 3 | import re 4 | 5 | # Version string must appear intact for versioning 6 | __version__ = "7.16.6" 7 | 8 | # Build up version_info tuple for backwards compatibility 9 | pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" 10 | match = re.match(pattern, __version__) 11 | assert match is not None 12 | parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]] 13 | if match["rest"]: 14 | parts.append(match["rest"]) 15 | version_info = tuple(parts) 16 | -------------------------------------------------------------------------------- /nbconvert/exporters/qtpng.py: -------------------------------------------------------------------------------- 1 | """Export to PNG via a headless browser""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from .qt_exporter import QtExporter 7 | 8 | 9 | class QtPNGExporter(QtExporter): 10 | """Writer designed to write to PNG files. 11 | 12 | This inherits from :class:`HTMLExporter`. It creates the HTML using the 13 | template machinery, and then uses pyqtwebengine to create a png. 14 | """ 15 | 16 | export_from_notebook = "PNG via HTML" 17 | format = "png" 18 | -------------------------------------------------------------------------------- /nbconvert/filters/metadata.py: -------------------------------------------------------------------------------- 1 | """filters for metadata""" 2 | 3 | 4 | def get_metadata(output, key, mimetype=None): 5 | """Resolve an output metadata key 6 | 7 | If mimetype given, resolve at mimetype level first, 8 | then fallback to top-level. 9 | Otherwise, just resolve at top-level. 10 | Returns None if no data found. 11 | """ 12 | md = output.get("metadata") or {} 13 | if mimetype and mimetype in md: 14 | value = md[mimetype].get(key) 15 | if value is not None: 16 | return value 17 | return md.get(key) 18 | -------------------------------------------------------------------------------- /share/templates/python/index.py.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'null.j2' -%} 2 | 3 | {%- block header -%} 4 | #!/usr/bin/env python 5 | # coding: utf-8 6 | {% endblock header %} 7 | 8 | {% block in_prompt %} 9 | {% if resources.global_content_filter.include_input_prompt -%} 10 | # In[{{ cell.execution_count if cell.execution_count else ' ' }}]: 11 | {% endif %} 12 | {% endblock in_prompt %} 13 | 14 | {% block input %} 15 | {{ cell.source | ipython2python }} 16 | {% endblock input %} 17 | 18 | {% block markdowncell scoped %} 19 | {{ cell.source | comment_lines }} 20 | {% endblock markdowncell %} 21 | -------------------------------------------------------------------------------- /nbconvert/templates/skeleton/README.md: -------------------------------------------------------------------------------- 1 | ## Template skeleton 2 | 3 | This directory contains the template skeleton files. 4 | 5 | Do not modify the contents of the `../latex/skeleton` folder. Instead, 6 | if you need to, make modifications to the files in this folder and then run 7 | `make` to generate the corresponding latex skeleton files in the 8 | `../latex/skeleton` folder. 9 | 10 | If you would like to share your resulting templates with others, we encourage 11 | [sharing those links on our wiki 12 | page](https://github.com/ipython/ipython/wiki/Cookbook:%20nbconvert%20templates). 13 | -------------------------------------------------------------------------------- /share/templates/latex/style_bw_python.tex.j2: -------------------------------------------------------------------------------- 1 | ((= Black&white Python input/output style =)) 2 | 3 | ((*- extends 'base.tex.j2' -*)) 4 | 5 | %=============================================================================== 6 | % Input 7 | %=============================================================================== 8 | 9 | ((* block input scoped *)) 10 | \begin{verbatim} 11 | ((*- if resources.global_content_filter.include_input_prompt *)) 12 | ((( cell.source | add_prompts ))) 13 | ((* else *)) 14 | ((( cell.source ))) 15 | ((* endif *)) 16 | \end{verbatim} 17 | ((* endblock input *)) 18 | -------------------------------------------------------------------------------- /share/templates/latex/index.tex.j2: -------------------------------------------------------------------------------- 1 | 2 | ((=- Default to the notebook output style -=)) 3 | ((*- if not cell_style is defined -*)) 4 | ((* set cell_style = 'style_jupyter.tex.j2' *)) 5 | ((*- endif -*)) 6 | 7 | ((=- Inherit from the specified cell style. -=)) 8 | ((* extends cell_style *)) 9 | 10 | 11 | %=============================================================================== 12 | % Latex Article 13 | %=============================================================================== 14 | 15 | ((*- block docclass -*)) 16 | \documentclass[11pt]{article} 17 | ((*- endblock docclass -*)) 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | ## name: Bug report about: Create a report to help us improve 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | **Nbconvert version:** 17 | -------------------------------------------------------------------------------- /tests/files/notebook_jl.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "function foobar(x)\n", 12 | " 100x\n", 13 | "end" 14 | ] 15 | } 16 | ], 17 | "metadata": { 18 | "kernelspec": { 19 | "display_name": "Julia 0.4.0-dev", 20 | "language": "julia", 21 | "name": "julia-0.4" 22 | }, 23 | "language_info": { 24 | "name": "julia", 25 | "version": "0.4.0" 26 | } 27 | }, 28 | "nbformat": 4, 29 | "nbformat_minor": 0 30 | } 31 | -------------------------------------------------------------------------------- /docs/source/api/nbconvertapp.rst: -------------------------------------------------------------------------------- 1 | NbConvertApp 2 | ============ 3 | 4 | .. module:: nbconvert.nbconvertapp 5 | 6 | .. seealso:: 7 | 8 | :doc:`/config_options` 9 | Configurable options for the nbconvert application 10 | 11 | .. autoclass:: NbConvertApp 12 | 13 | .. automethod:: init_notebooks 14 | 15 | .. automethod:: convert_notebooks 16 | 17 | .. automethod:: convert_single_notebook 18 | 19 | .. automethod:: init_single_notebook_resources 20 | 21 | .. automethod:: export_single_notebook 22 | 23 | .. automethod:: write_single_notebook 24 | 25 | .. automethod:: postprocess_single_notebook 26 | -------------------------------------------------------------------------------- /nbconvert/writers/stdout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains Stdout writer 3 | """ 4 | 5 | # Copyright (c) Jupyter Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | 8 | from nbconvert.utils import io 9 | 10 | from .base import WriterBase 11 | 12 | 13 | class StdoutWriter(WriterBase): 14 | """Consumes output from nbconvert export...() methods and writes to the 15 | stdout stream.""" 16 | 17 | def write(self, output, resources, **kw): 18 | """ 19 | Consume and write Jinja output. 20 | 21 | See base for more... 22 | """ 23 | stream = io.unicode_std_stream() 24 | stream.write(output) 25 | -------------------------------------------------------------------------------- /tests/fake_exporters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module that define a custom exporter just to test the ability to invoke 3 | nbconvert with full qualified name 4 | """ 5 | 6 | from traitlets import default 7 | 8 | from nbconvert.exporters.html import HTMLExporter 9 | 10 | 11 | class MyExporter(HTMLExporter): 12 | """ 13 | My custom exporter 14 | """ 15 | 16 | @default("file_extension") 17 | def _file_extension_default(self): 18 | """ 19 | The new file extension is `.test_ext` 20 | """ 21 | return ".test_ext" 22 | 23 | @default("template_extension") 24 | def _template_extension_default(self): 25 | return ".html.j2" 26 | -------------------------------------------------------------------------------- /docs/source/latex_citations.rst: -------------------------------------------------------------------------------- 1 | LaTeX citations 2 | =============== 3 | 4 | ``nbconvert`` now has support for LaTeX citations. With this capability you 5 | can: 6 | 7 | * Manage citations using BibTeX. 8 | * Cite those citations in Markdown cells using HTML data attributes. 9 | * Have ``nbconvert`` generate proper LaTeX citations and run BibTeX. 10 | 11 | For an example of how this works, please see the `citations example`_ in 12 | the nbconvert-examples_ repository. 13 | 14 | .. _nbconvert-examples: https://github.com/jupyter/nbconvert-examples 15 | .. _citations example: https://nbviewer.jupyter.org/github/jupyter/nbconvert-examples/blob/master/citations/Tutorial.ipynb 16 | -------------------------------------------------------------------------------- /tests/files/latex-linked-image.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "![image](testimage.png)" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "Python 3", 14 | "language": "", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.4.3" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/filters/test_metadata.py: -------------------------------------------------------------------------------- 1 | from nbconvert.filters import get_metadata 2 | 3 | 4 | def test_get_metadata(): 5 | output = { 6 | "metadata": { 7 | "width": 1, 8 | "height": 2, 9 | "image/png": { 10 | "unconfined": True, 11 | "height": 3, 12 | }, 13 | } 14 | } 15 | assert get_metadata(output, "nowhere") is None 16 | assert get_metadata(output, "height") == 2 17 | assert get_metadata(output, "unconfined") is None 18 | assert get_metadata(output, "unconfined", "image/png") is True 19 | assert get_metadata(output, "width", "image/png") == 1 20 | assert get_metadata(output, "height", "image/png") == 3 21 | -------------------------------------------------------------------------------- /nbconvert/utils/_contextlib_chdir.py: -------------------------------------------------------------------------------- 1 | """Backport of Python 3.11's contextlib.chdir.""" 2 | 3 | import os 4 | from contextlib import AbstractContextManager 5 | 6 | 7 | class chdir(AbstractContextManager): # type:ignore[type-arg] 8 | """Non thread-safe context manager to change the current working directory.""" 9 | 10 | def __init__(self, path): 11 | """Initialize the manager.""" 12 | self.path = path 13 | self._old_cwd = [] 14 | 15 | def __enter__(self): 16 | """Enter the context.""" 17 | self._old_cwd.append(os.getcwd()) 18 | os.chdir(self.path) 19 | 20 | def __exit__(self, *excinfo): 21 | """Exit the context.""" 22 | os.chdir(self._old_cwd.pop()) 23 | -------------------------------------------------------------------------------- /nbconvert/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | """NbConvert specific exceptions""" 2 | # ----------------------------------------------------------------------------- 3 | # Copyright (c) 2013, the IPython Development Team. 4 | # 5 | # Distributed under the terms of the Modified BSD License. 6 | # 7 | # The full license is in the file COPYING.txt, distributed with this software. 8 | # ----------------------------------------------------------------------------- 9 | 10 | # ----------------------------------------------------------------------------- 11 | # Classes and functions 12 | # ----------------------------------------------------------------------------- 13 | 14 | 15 | class ConversionException(Exception): 16 | """An exception raised by the conversion process.""" 17 | -------------------------------------------------------------------------------- /nbconvert/exporters/python.py: -------------------------------------------------------------------------------- 1 | """Python script Exporter class""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import default 7 | 8 | from .templateexporter import TemplateExporter 9 | 10 | 11 | class PythonExporter(TemplateExporter): 12 | """ 13 | Exports a Python code file. 14 | Note that the file produced will have a shebang of '#!/usr/bin/env python' 15 | regardless of the actual python version used in the notebook. 16 | """ 17 | 18 | @default("file_extension") 19 | def _file_extension_default(self): 20 | return ".py" 21 | 22 | @default("template_name") 23 | def _template_name_default(self): 24 | return "python" 25 | 26 | output_mimetype = "text/x-python" 27 | -------------------------------------------------------------------------------- /nbconvert/templates/skeleton/Makefile: -------------------------------------------------------------------------------- 1 | TPLS := $(patsubst %.tpl,../latex/skeleton/%.tplx,$(wildcard *.tpl)) 2 | 3 | all: clean $(TPLS) 4 | 5 | # Convert standard Jinja2 syntax to LaTeX safe Jinja2 6 | # see http://flask.pocoo.org/snippets/55/ for more info 7 | ../latex/skeleton/%.tplx: %.tpl 8 | @echo 'generating tex equivalent of $^: $@' 9 | @echo '((=- Auto-generated template file, DO NOT edit directly!\n' \ 10 | ' To edit this file, please refer to ../../skeleton/README.md' \ 11 | '-=))\n\n' > $@ 12 | @sed \ 13 | -e 's/{%/((*/g' \ 14 | -e 's/%}/*))/g' \ 15 | -e 's/{{/(((/g' \ 16 | -e 's/}}/)))/g' \ 17 | -e 's/{#/((=/g' \ 18 | -e 's/#}/=))/g' \ 19 | -e "s/tpl'/tplx'/g" \ 20 | $^ >> $@ 21 | 22 | clean: 23 | @echo "cleaning generated tplx files..." 24 | @-rm ../latex/skeleton/*.tplx 25 | -------------------------------------------------------------------------------- /tests/postprocessors/test_serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for the serve post-processor 3 | """ 4 | 5 | # Copyright (c) Jupyter Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | 8 | import pytest 9 | 10 | from tests.base import TestsBase 11 | 12 | 13 | class TestServe(TestsBase): 14 | """Contains test functions for serve.py""" 15 | 16 | def test_constructor(self): 17 | """Can a ServePostProcessor be constructed?""" 18 | pytest.importorskip("tornado") 19 | try: 20 | from nbconvert.postprocessors.serve import ( # noqa: PLC0415 21 | ServePostProcessor, 22 | ) 23 | except ModuleNotFoundError: 24 | print("Something weird is happening.\nTornado is sometimes present, sometimes not.") 25 | raise 26 | ServePostProcessor() 27 | -------------------------------------------------------------------------------- /tests/exporters/test_qtpdf.py: -------------------------------------------------------------------------------- 1 | """Tests for the qtpdf preprocessor""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import pytest 7 | 8 | from nbconvert.exporters.qt_screenshot import QT_INSTALLED 9 | from nbconvert.exporters.qtpdf import QtPDFExporter 10 | 11 | from .base import ExportersTestsBase 12 | 13 | 14 | @pytest.mark.skipif(not QT_INSTALLED, reason="PyQtWebEngine not installed") 15 | class TestQtPDFExporter(ExportersTestsBase): 16 | """Contains test functions for qtpdf.py""" 17 | 18 | exporter_class = QtPDFExporter # type:ignore 19 | 20 | def test_export(self): 21 | """ 22 | Can a TemplateExporter export something? 23 | """ 24 | (output, _resources) = QtPDFExporter().from_filename(self._get_notebook()) 25 | assert len(output) > 0 26 | -------------------------------------------------------------------------------- /docs/api_examples/template_path/make_html.py: -------------------------------------------------------------------------------- 1 | """ 2 | this script builds html files with either classic or classic_clone templates 3 | 4 | Note: nbconvert 6.0 changed ``template_path`` to ``template_paths`` 5 | """ 6 | 7 | import nbformat 8 | from traitlets.config import Config 9 | 10 | from nbconvert import HTMLExporter 11 | 12 | nbfile = "quiz_notebook.ipynb" 13 | the_ipynb = nbformat.read(nbfile, as_version=4) 14 | 15 | c = Config() 16 | c.TemplateExporter.template_paths = [".", "./project_templates"] 17 | for template in ["classic", "classic_clone"]: 18 | c.HTMLExporter.template_name = template 19 | html_exporter = HTMLExporter(config=c) 20 | (body, resources) = html_exporter.from_notebook_node(the_ipynb) 21 | with open(f"{template}.html", "w") as outfile: 22 | outfile.write(body) 23 | print(f"\n{'*' * 20}\n{template} succeeds\n{'*' * 20}\n") 24 | -------------------------------------------------------------------------------- /check_requirements.py: -------------------------------------------------------------------------------- 1 | """Verify that the "all" reqs are in sync.""" 2 | 3 | import sys 4 | 5 | from tomli import load 6 | 7 | with open("pyproject.toml", "rb") as fid: 8 | data = load(fid) 9 | 10 | all_reqs = data["project"]["optional-dependencies"]["all"] 11 | remaining_all = all_reqs.copy() 12 | errors = [] 13 | 14 | for key, reqs in data["project"]["optional-dependencies"].items(): 15 | if key == "all": 16 | continue 17 | for req in reqs: 18 | if req not in all_reqs: 19 | errors.append(req) 20 | elif req in remaining_all: 21 | remaining_all.remove(req) 22 | 23 | if errors: 24 | print('Missing deps in "all" reqs:') 25 | print(list(errors)) 26 | 27 | if remaining_all: 28 | print('Reqs in "all" but nowhere else:') 29 | print(list(remaining_all)) 30 | 31 | if errors or remaining_all: 32 | sys.exit(1) 33 | -------------------------------------------------------------------------------- /share/templates/latex/style_python.tex.j2: -------------------------------------------------------------------------------- 1 | ((= Python input/output style =)) 2 | 3 | ((*- extends 'base.tex.j2' -*)) 4 | 5 | % Custom definitions 6 | ((* block definitions *)) 7 | ((( super() ))) 8 | 9 | % Pygments definitions 10 | ((( resources.latex.pygments_definitions ))) 11 | ((* endblock definitions *)) 12 | 13 | %=============================================================================== 14 | % Input 15 | %=============================================================================== 16 | 17 | ((* block input scoped *)) 18 | \begin{Verbatim}[commandchars=\\\{\}] 19 | ((*- if resources.global_content_filter.include_input_prompt *)) 20 | ((( cell.source | highlight_code(strip_verbatim=True, metadata=cell.metadata) | add_prompts ))) 21 | ((* else *)) 22 | ((( cell.source | highlight_code(strip_verbatim=True, metadata=cell.metadata) ))) 23 | ((* endif *)) 24 | \end{Verbatim} 25 | ((* endblock input *)) 26 | -------------------------------------------------------------------------------- /tests/preprocessors/fake_kernelmanager.py: -------------------------------------------------------------------------------- 1 | from jupyter_client.manager import KernelManager 2 | 3 | 4 | class FakeCustomKernelManager(KernelManager): 5 | expected_methods = { 6 | "__init__": 0, 7 | "client": 0, 8 | "start_kernel": 0, 9 | } 10 | 11 | def __init__(self, *args, **kwargs): 12 | self.log.info("FakeCustomKernelManager initialized") 13 | self.expected_methods["__init__"] += 1 14 | super().__init__(*args, **kwargs) 15 | 16 | def start_kernel(self, *args, **kwargs): 17 | self.log.info("FakeCustomKernelManager started a kernel") 18 | self.expected_methods["start_kernel"] += 1 19 | return super().start_kernel(*args, **kwargs) 20 | 21 | def client(self, *args, **kwargs): 22 | self.log.info("FakeCustomKernelManager created a client") 23 | self.expected_methods["client"] += 1 24 | return super().client(*args, **kwargs) 25 | -------------------------------------------------------------------------------- /nbconvert/exporters/qtpdf.py: -------------------------------------------------------------------------------- 1 | """Export to PDF via a headless browser""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import Bool 7 | 8 | from .qt_exporter import QtExporter 9 | 10 | 11 | class QtPDFExporter(QtExporter): 12 | """Writer designed to write to PDF files. 13 | 14 | This inherits from :class:`HTMLExporter`. It creates the HTML using the 15 | template machinery, and then uses pyqtwebengine to create a pdf. 16 | """ 17 | 18 | export_from_notebook = "PDF via HTML" 19 | format = "pdf" 20 | 21 | paginate = Bool( # type:ignore[assignment] 22 | True, 23 | help=""" 24 | Split generated notebook into multiple pages. 25 | 26 | If False, a PDF with one long page will be generated. 27 | 28 | Set to True to match behavior of LaTeX based PDF generator 29 | """, 30 | ).tag(config=True) 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | docs/man/*.gz 6 | docs/source/api/generated 7 | docs/source/config/options 8 | docs/source/interactive/magics-generated.txt 9 | docs/gh-pages 10 | docs/source/CHANGELOG.md 11 | nbconvert/resources/style.min.css 12 | tests/files/*.html 13 | *.py[co] 14 | __pycache__ 15 | *.egg-info 16 | *~ 17 | *.bak 18 | .ipynb_checkpoints 19 | .tox 20 | .DS_Store 21 | \#*# 22 | .#* 23 | .coverage* 24 | htmlcov 25 | .cache 26 | docs/source/*.tpl 27 | docs/source/*.j2 28 | docs/source/example.html 29 | docs/source/mytemplate/* 30 | docs/source/config_options.rst 31 | .venv 32 | 33 | # Eclipse pollutes the filesystem 34 | .project 35 | .pydevproject 36 | .settings 37 | 38 | # VSCode 39 | *.code-workspace 40 | .history 41 | .vscode 42 | 43 | # Downloaded theme files 44 | share/templates/lab/static/index.css 45 | share/templates/lab/static/theme-dark.css 46 | share/templates/lab/static/theme-light.css 47 | share/templates/classic/static/style.css 48 | -------------------------------------------------------------------------------- /tests/exporters/test_python.py: -------------------------------------------------------------------------------- 1 | """Tests for PythonExporter""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from nbconvert.exporters.python import PythonExporter 7 | 8 | from .base import ExportersTestsBase 9 | 10 | 11 | class TestPythonExporter(ExportersTestsBase): 12 | """Tests for PythonExporter""" 13 | 14 | exporter_class = PythonExporter # type:ignore 15 | should_include_raw = ["python"] # type:ignore 16 | 17 | def test_constructor(self): 18 | """Can a PythonExporter be constructed?""" 19 | self.exporter_class() # type:ignore 20 | 21 | def test_export(self): 22 | """Can a PythonExporter export something?""" 23 | (output, _resources) = self.exporter_class().from_filename( # type:ignore 24 | self._get_notebook() 25 | ) 26 | self.assertIn("coding: utf-8", output) 27 | self.assertIn("#!/usr/bin/env python", output) 28 | -------------------------------------------------------------------------------- /share/templates/reveal/base.html.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'lab/base.html.j2' -%} 2 | {% from 'cellslidedata.j2' import cellslidedata %} 3 | 4 | {%- block any_cell scoped -%} 5 | {%- if cell.metadata.get('slide_start', False) -%} 6 |
7 | {%- endif -%} 8 | {%- if cell.metadata.get('subslide_start', False) -%} 9 |
10 | {%- endif -%} 11 | {%- if cell.metadata.get('fragment_start', False) -%} 12 |
13 | {%- endif -%} 14 | 15 | {%- if cell.metadata.slide_type == 'notes' -%} 16 | 19 | {%- elif cell.metadata.slide_type == 'skip' -%} 20 | {%- else -%} 21 | {{ super() }} 22 | {%- endif -%} 23 | 24 | {%- if cell.metadata.get('fragment_end', False) -%} 25 |
26 | {%- endif -%} 27 | {%- if cell.metadata.get('subslide_end', False) -%} 28 |
29 | {%- endif -%} 30 | {%- if cell.metadata.get('slide_end', False) -%} 31 |
32 | {%- endif -%} 33 | {%- endblock any_cell -%} 34 | -------------------------------------------------------------------------------- /share/templates/latex/report.tex.j2: -------------------------------------------------------------------------------- 1 | 2 | % Default to the notebook output style 3 | ((* if not cell_style is defined *)) 4 | ((* set cell_style = 'style_ipython.tex.j2' *)) 5 | ((* endif *)) 6 | 7 | % Inherit from the specified cell style. 8 | ((* extends cell_style *)) 9 | 10 | 11 | %=============================================================================== 12 | % Latex Book 13 | %=============================================================================== 14 | 15 | ((* block predoc *)) 16 | ((( super() ))) 17 | ((* block tableofcontents *))\tableofcontents((* endblock tableofcontents *)) 18 | ((* endblock predoc *)) 19 | 20 | ((* block docclass *)) 21 | \documentclass{report} 22 | ((* endblock docclass *)) 23 | 24 | ((* block markdowncell scoped *)) 25 | ((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'json',extra_args=[]) | resolve_references | convert_pandoc('json','latex', extra_args=["--top-level-division=chapter"]) ))) 26 | ((* endblock markdowncell *)) 27 | -------------------------------------------------------------------------------- /tests/preprocessors/files/MixedMarkdown.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "this is a code cell\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "print(\"this is a code cell\")" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "# This is a markdown cell" 25 | ] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 3", 31 | "language": "python", 32 | "name": "python3" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 3 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython3", 44 | "version": "3.8.2" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 4 49 | } 50 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/clearoutput.py: -------------------------------------------------------------------------------- 1 | """Module containing a preprocessor that removes the outputs from code cells""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import Set 7 | 8 | from .base import Preprocessor 9 | 10 | 11 | class ClearOutputPreprocessor(Preprocessor): 12 | """ 13 | Removes the output from all code cells in a notebook. 14 | """ 15 | 16 | remove_metadata_fields = Set({"collapsed", "scrolled"}).tag(config=True) 17 | 18 | def preprocess_cell(self, cell, resources, cell_index): 19 | """ 20 | Apply a transformation on each cell. See base.py for details. 21 | """ 22 | if cell.cell_type == "code": 23 | cell.outputs = [] 24 | cell.execution_count = None 25 | # Remove metadata associated with output 26 | if "metadata" in cell: 27 | for field in self.remove_metadata_fields: 28 | cell.metadata.pop(field, None) 29 | return cell, resources 30 | -------------------------------------------------------------------------------- /nbconvert/utils/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for version comparison 3 | 4 | It is a bit ridiculous that we need these. 5 | """ 6 | 7 | # Copyright (c) Jupyter Development Team. 8 | # Distributed under the terms of the Modified BSD License. 9 | 10 | from packaging.version import Version 11 | 12 | 13 | def check_version(v, min_v, max_v=None): 14 | """check version string v >= min_v and v < max_v 15 | 16 | Parameters 17 | ---------- 18 | v : str 19 | version of the package 20 | min_v : str 21 | minimal version supported 22 | max_v : str 23 | earliest version not supported 24 | Note: If dev/prerelease tags result in TypeError for string-number 25 | comparison, it is assumed that the check passes and the version dependency 26 | is satisfied. Users on dev branches are responsible for keeping their own 27 | packages up to date. 28 | """ 29 | 30 | try: 31 | below_max = Version(v) < Version(max_v) if max_v is not None else True 32 | return Version(v) >= Version(min_v) and below_max 33 | except TypeError: 34 | return True 35 | -------------------------------------------------------------------------------- /tests/exporters/test_qtpng.py: -------------------------------------------------------------------------------- 1 | """Tests for the qtpng preprocessor""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from nbconvert.exporters.qt_screenshot import QT_INSTALLED 11 | from nbconvert.exporters.qtpng import QtPNGExporter 12 | 13 | from .base import ExportersTestsBase 14 | 15 | 16 | @pytest.mark.skipif(not QT_INSTALLED, reason="PyQtWebEngine not installed") 17 | class TestQtPNGExporter(ExportersTestsBase): 18 | """Contains test functions for qtpng.py""" 19 | 20 | exporter_class = QtPNGExporter # type:ignore 21 | 22 | @pytest.mark.flaky 23 | def test_export(self): 24 | """ 25 | Can a TemplateExporter export something? 26 | """ 27 | if os.name == "nt": 28 | # currently not supported 29 | with pytest.raises(RuntimeError): 30 | (output, _resources) = QtPNGExporter().from_filename(self._get_notebook()) 31 | else: 32 | (output, _resources) = QtPNGExporter().from_filename(self._get_notebook()) 33 | assert len(output) > 0 34 | -------------------------------------------------------------------------------- /docs/source/need_help.rst: -------------------------------------------------------------------------------- 1 | Need help? 2 | ========== 3 | 4 | Technical Support 5 | ----------------- 6 | 7 | - `GitHub Issues and Bug Reports `_. A place to report bugs or regressions found for nbconvert 8 | 9 | - `Community Technical Support and Discussion - Jupyter Discourse `_ : 10 | A place for installation, configuration, and troubleshooting assistance by the Jupyter community. 11 | As a non-profit project with maintainers who are primarily volunteers, we rely on the community 12 | for technical support. Please use Discourse to ask questions and share your knowledge. 13 | 14 | Documentation 15 | ------------- 16 | 17 | - `Documentation for Jupyter nbconvert `_ 18 | - `nbconvert examples repo on GitHub `_ 19 | - `Documentation for Project Jupyter `_ 20 | 21 | Jupyter Resources 22 | ----------------- 23 | 24 | - `Jupyter mailing list `_ 25 | - `Project Jupyter website `_ 26 | -------------------------------------------------------------------------------- /tests/files/notebook5_embed_images.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "e684608d", 6 | "metadata": {}, 7 | "source": [ 8 | "![](./containerized_deployments.jpeg)" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "e609fcaf", 14 | "metadata": {}, 15 | "source": [ 16 | "" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "a0043f9e", 22 | "metadata": {}, 23 | "source": [ 24 | "
\n", 25 | " \n", 26 | "
" 27 | ] 28 | } 29 | ], 30 | "metadata": { 31 | "kernelspec": { 32 | "display_name": "Python 3 (ipykernel)", 33 | "language": "python", 34 | "name": "python3" 35 | }, 36 | "language_info": { 37 | "codemirror_mode": { 38 | "name": "ipython", 39 | "version": 3 40 | }, 41 | "file_extension": ".py", 42 | "mimetype": "text/x-python", 43 | "name": "python", 44 | "nbconvert_exporter": "python", 45 | "pygments_lexer": "ipython3", 46 | "version": "3.9.10" 47 | } 48 | }, 49 | "nbformat": 4, 50 | "nbformat_minor": 5 51 | } 52 | -------------------------------------------------------------------------------- /tests/utils/test_io.py: -------------------------------------------------------------------------------- 1 | """Tests for utils.io""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import io as stdlib_io 7 | import sys 8 | from io import StringIO 9 | 10 | from nbconvert.utils.io import unicode_std_stream 11 | 12 | 13 | def test_UnicodeStdStream(): 14 | # Test wrapping a bytes-level stdout 15 | stdoutb = stdlib_io.BytesIO() 16 | stdout = stdlib_io.TextIOWrapper(stdoutb, encoding="ascii") 17 | 18 | orig_stdout = sys.stdout 19 | sys.stdout = stdout 20 | try: 21 | sample = "@łe¶ŧ←" 22 | stream = unicode_std_stream() 23 | stream.write(sample) 24 | 25 | output = stdoutb.getvalue().decode("utf-8") 26 | assert output == sample 27 | assert not stdout.closed 28 | finally: 29 | sys.stdout = orig_stdout 30 | 31 | 32 | def test_UnicodeStdStream_nowrap(): 33 | # If we replace stdout with a StringIO, it shouldn't get wrapped. 34 | orig_stdout = sys.stdout 35 | sys.stdout = StringIO() 36 | try: 37 | assert unicode_std_stream() is sys.stdout 38 | assert not sys.stdout.closed 39 | finally: 40 | sys.stdout = orig_stdout 41 | -------------------------------------------------------------------------------- /tests/files/Unexecuted_widget.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "036cb3ce-96fe-4159-8145-40bb74b263f9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import ipywidgets as widgets\n", 11 | "\n", 12 | "widgets.IntSlider(\n", 13 | " value=7,\n", 14 | " min=0,\n", 15 | " max=10,\n", 16 | " step=1,\n", 17 | " description=\"Test:\",\n", 18 | " disabled=False,\n", 19 | " continuous_update=False,\n", 20 | " orientation=\"horizontal\",\n", 21 | " readout=True,\n", 22 | " readout_format=\"d\",\n", 23 | ")" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3 (ipykernel)", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.9.7" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 5 48 | } 49 | -------------------------------------------------------------------------------- /tests/files/Unexecuted_widget_2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "036cb3ce-96fe-4159-8145-40bb74b263f9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import ipywidgets as widgets\n", 11 | "\n", 12 | "widgets.IntSlider(\n", 13 | " value=7,\n", 14 | " min=0,\n", 15 | " max=10,\n", 16 | " step=1,\n", 17 | " description=\"Test:\",\n", 18 | " disabled=False,\n", 19 | " continuous_update=False,\n", 20 | " orientation=\"horizontal\",\n", 21 | " readout=True,\n", 22 | " readout_format=\"d\",\n", 23 | ")" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3 (ipykernel)", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.9.7" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 5 48 | } 49 | -------------------------------------------------------------------------------- /nbconvert/utils/base.py: -------------------------------------------------------------------------------- 1 | """Global configuration class.""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import List, Unicode 7 | from traitlets.config.configurable import LoggingConfigurable 8 | 9 | 10 | class NbConvertBase(LoggingConfigurable): 11 | """Global configurable class for shared config 12 | 13 | Useful for display data priority that might be used by many transformers 14 | """ 15 | 16 | display_data_priority = List( 17 | [ 18 | "text/html", 19 | "application/pdf", 20 | "text/latex", 21 | "image/svg+xml", 22 | "image/png", 23 | "image/jpeg", 24 | "text/markdown", 25 | "text/plain", 26 | ], 27 | help=""" 28 | An ordered list of preferred output type, the first 29 | encountered will usually be used when converting discarding 30 | the others. 31 | """, 32 | ).tag(config=True) 33 | 34 | default_language = Unicode( 35 | "ipython", 36 | help="Deprecated default highlight language as of 5.0, please use language_info metadata instead", 37 | ).tag(config=True) 38 | -------------------------------------------------------------------------------- /nbconvert/utils/text.py: -------------------------------------------------------------------------------- 1 | """Text related utils.""" 2 | 3 | import os 4 | import re 5 | 6 | 7 | def indent(instr, nspaces=4, ntabs=0, flatten=False): 8 | """Indent a string a given number of spaces or tabstops. 9 | 10 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. 11 | 12 | Parameters 13 | ---------- 14 | 15 | instr : basestring 16 | The string to be indented. 17 | nspaces : int (default: 4) 18 | The number of spaces to be indented. 19 | ntabs : int (default: 0) 20 | The number of tabs to be indented. 21 | flatten : bool (default: False) 22 | Whether to scrub existing indentation. If True, all lines will be 23 | aligned to the same indentation. If False, existing indentation will 24 | be strictly increased. 25 | 26 | Returns 27 | ------- 28 | 29 | str|unicode : string indented by ntabs and nspaces. 30 | 31 | """ 32 | if instr is None: 33 | return None 34 | ind = "\t" * ntabs + " " * nspaces 35 | pat = re.compile("^\\s*", re.MULTILINE) if flatten else re.compile("^", re.MULTILINE) 36 | outstr = re.sub(pat, ind, instr) 37 | if outstr.endswith(os.linesep + ind): 38 | return outstr[: -len(ind)] 39 | return outstr 40 | -------------------------------------------------------------------------------- /tests/exporters/files/notebook3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdin", 10 | "output_type": "stream", 11 | "text": [ 12 | "test input: input value\n" 13 | ] 14 | }, 15 | { 16 | "data": { 17 | "text/plain": [ 18 | "'input value'" 19 | ] 20 | }, 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "output_type": "execute_result" 24 | } 25 | ], 26 | "source": [ 27 | "input(\"test input:\")" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [] 36 | } 37 | ], 38 | "metadata": { 39 | "kernelspec": { 40 | "display_name": "Python 3", 41 | "language": "python", 42 | "name": "python3" 43 | }, 44 | "language_info": { 45 | "codemirror_mode": { 46 | "name": "ipython", 47 | "version": 3 48 | }, 49 | "file_extension": ".py", 50 | "mimetype": "text/x-python", 51 | "name": "python", 52 | "nbconvert_exporter": "python", 53 | "pygments_lexer": "ipython3", 54 | "version": "3.8.5" 55 | } 56 | }, 57 | "nbformat": 4, 58 | "nbformat_minor": 4 59 | } 60 | -------------------------------------------------------------------------------- /nbconvert/postprocessors/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic post processor 3 | """ 4 | # ----------------------------------------------------------------------------- 5 | # Copyright (c) 2013, the IPython Development Team. 6 | # 7 | # Distributed under the terms of the Modified BSD License. 8 | # 9 | # The full license is in the file COPYING.txt, distributed with this software. 10 | # ----------------------------------------------------------------------------- 11 | 12 | # ----------------------------------------------------------------------------- 13 | # Imports 14 | # ----------------------------------------------------------------------------- 15 | 16 | from nbconvert.utils.base import NbConvertBase 17 | 18 | 19 | # ----------------------------------------------------------------------------- 20 | # Classes 21 | # ----------------------------------------------------------------------------- 22 | class PostProcessorBase(NbConvertBase): 23 | """The base class for post processors.""" 24 | 25 | def __call__(self, input_): 26 | """ 27 | See def postprocess() ... 28 | """ 29 | self.postprocess(input_) 30 | 31 | def postprocess(self, input_): 32 | """ 33 | Post-process output from a writer. 34 | """ 35 | msg = "postprocess" 36 | raise NotImplementedError(msg) 37 | -------------------------------------------------------------------------------- /nbconvert/exporters/__init__.py: -------------------------------------------------------------------------------- 1 | from .asciidoc import ASCIIDocExporter 2 | from .base import ExporterDisabledError, ExporterNameError, export, get_export_names, get_exporter 3 | from .exporter import Exporter, FilenameExtension, ResourcesDict 4 | from .html import HTMLExporter 5 | from .latex import LatexExporter 6 | from .markdown import MarkdownExporter 7 | from .notebook import NotebookExporter 8 | from .pdf import PDFExporter 9 | from .python import PythonExporter 10 | from .qtpdf import QtPDFExporter 11 | from .qtpng import QtPNGExporter 12 | from .rst import RSTExporter 13 | from .script import ScriptExporter 14 | from .slides import SlidesExporter 15 | from .templateexporter import TemplateExporter 16 | from .webpdf import WebPDFExporter 17 | 18 | __all__ = [ 19 | "ASCIIDocExporter", 20 | "Exporter", 21 | "ExporterDisabledError", 22 | "ExporterNameError", 23 | "FilenameExtension", 24 | "HTMLExporter", 25 | "LatexExporter", 26 | "MarkdownExporter", 27 | "NotebookExporter", 28 | "PDFExporter", 29 | "PythonExporter", 30 | "QtPDFExporter", 31 | "QtPNGExporter", 32 | "RSTExporter", 33 | "ResourcesDict", 34 | "ScriptExporter", 35 | "SlidesExporter", 36 | "TemplateExporter", 37 | "WebPDFExporter", 38 | "export", 39 | "get_export_names", 40 | "get_exporter", 41 | ] 42 | -------------------------------------------------------------------------------- /share/templates/base/mathjax.html.j2: -------------------------------------------------------------------------------- 1 | 2 | {%- macro mathjax(url="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS_CHTML-full,Safe") -%} 3 | 4 | 5 | 6 | 37 | 38 | {%- endmacro %} 39 | -------------------------------------------------------------------------------- /share/templates/base/jupyter_widgets.html.j2: -------------------------------------------------------------------------------- 1 | {%- macro jupyter_widgets(widgets_cdn_url, html_manager_semver_range, widget_renderer_url='') -%} 2 | 3 | 35 | 36 | {%- endmacro %} 37 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | concurrency: 9 | group: docs-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | generate-docs: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: ["3.10", "3.14"] 18 | steps: 19 | - name: Check out repository code 20 | uses: actions/checkout@v5 21 | - name: Install dependencies 22 | run: | 23 | sudo apt-get update 24 | sudo apt-get install texlive-plain-generic inkscape texlive-xetex latexmk enchant-2 25 | 26 | # pandoc is not up to date in the ubuntu repos, so we install directly 27 | wget https://github.com/jgm/pandoc/releases/download/3.1.2/pandoc-3.1.2-1-amd64.deb && sudo dpkg -i pandoc-3.1.2-1-amd64.deb 28 | 29 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 30 | - name: Install 31 | run: pip install -v ".[all]" 32 | - name: List installed packages 33 | run: | 34 | pip freeze 35 | pip check 36 | - name: Build HTML docs 37 | run: | 38 | cd docs 39 | make html SPHINXOPTS="-W" 40 | - name: Upload HTML 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: html-${{ github.job }}-${{ strategy.job-index }} 44 | path: build/sphinx/html 45 | -------------------------------------------------------------------------------- /docs/source/dejavu.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: none 2 | 3 | Dejavu 4 | ====== 5 | 6 | Dejavu intends to be a tool to facilitate for Jupyter users to generate static outputs from their notebooks, mimicking the behavior of `voilà `_. 7 | 8 | Running dejavu 9 | -------------- 10 | 11 | Dejavu works exactly the same as nbconvert and you can use all command line options that you would with nbconvert. To run a default instance:: 12 | 13 | jupyter dejavu notebook.ipynb 14 | 15 | In case you want to show code in addition to its output use the flag ``--show-input``. 16 | 17 | 18 | Configuring the Notebook for slides presentations 19 | ------------------------------------------------- 20 | 21 | In case the user intends to do a slide presentation out of their Jupyter 22 | notebook it's recommended to use the ``reveal`` template. In orders to obtain a 23 | better result from it's advised to use the slides metadatas available in the 24 | cells: 25 | 26 | 27 | * In the notebook, select a cell and click on the "Property Inspector menu" 28 | 29 | .. tip:: 30 | 31 | The "Property Inspector menu" can be located in the right side bar, its symbol contains two gears. 32 | 33 | * Select a cell in the notebook 34 | 35 | * In the Property Inspector menu select the cell's slide type: 36 | 37 | * Slide 38 | * Sub-Slide 39 | * Fragment 40 | * Skip 41 | * Notes 42 | 43 | * Repeat the process for all cells 44 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/__init__.py: -------------------------------------------------------------------------------- 1 | # Class base Preprocessors 2 | # Backwards compatibility for imported name 3 | from nbclient.exceptions import CellExecutionError 4 | 5 | from .base import Preprocessor 6 | from .clearmetadata import ClearMetadataPreprocessor 7 | from .clearoutput import ClearOutputPreprocessor 8 | from .coalescestreams import CoalesceStreamsPreprocessor 9 | from .convertfigures import ConvertFiguresPreprocessor 10 | from .csshtmlheader import CSSHTMLHeaderPreprocessor 11 | from .execute import ExecutePreprocessor 12 | from .extractattachments import ExtractAttachmentsPreprocessor 13 | from .extractoutput import ExtractOutputPreprocessor 14 | from .highlightmagics import HighlightMagicsPreprocessor 15 | from .latex import LatexPreprocessor 16 | from .regexremove import RegexRemovePreprocessor 17 | from .svg2pdf import SVG2PDFPreprocessor 18 | from .tagremove import TagRemovePreprocessor 19 | 20 | __all__ = [ 21 | "CSSHTMLHeaderPreprocessor", 22 | "CellExecutionError", 23 | "ClearMetadataPreprocessor", 24 | "ClearOutputPreprocessor", 25 | "CoalesceStreamsPreprocessor", 26 | "ConvertFiguresPreprocessor", 27 | "ExecutePreprocessor", 28 | "ExtractAttachmentsPreprocessor", 29 | "ExtractOutputPreprocessor", 30 | "HighlightMagicsPreprocessor", 31 | "LatexPreprocessor", 32 | "Preprocessor", 33 | "RegexRemovePreprocessor", 34 | "SVG2PDFPreprocessor", 35 | "TagRemovePreprocessor", 36 | ] 37 | -------------------------------------------------------------------------------- /nbconvert/writers/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains writer base class. 3 | """ 4 | 5 | # Copyright (c) Jupyter Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | from __future__ import annotations 8 | 9 | from traitlets import List, Unicode 10 | 11 | from nbconvert.utils.base import NbConvertBase 12 | 13 | 14 | class WriterBase(NbConvertBase): 15 | """Consumes output from nbconvert export...() methods and writes to a 16 | useful location.""" 17 | 18 | files = List( 19 | Unicode(), 20 | help=""" 21 | List of the files that the notebook references. Files will be 22 | included with written output.""", 23 | ).tag(config=True) 24 | 25 | def __init__(self, config=None, **kw): 26 | """ 27 | Constructor 28 | """ 29 | super().__init__(config=config, **kw) 30 | 31 | def write(self, output, resources, **kw): 32 | """ 33 | Consume and write Jinja output. 34 | 35 | Parameters 36 | ---------- 37 | output : string 38 | Conversion results. This string contains the file contents of the 39 | converted file. 40 | resources : dict 41 | Resources created and filled by the nbconvert conversion process. 42 | Includes output from preprocessors, such as the extract figure 43 | preprocessor. 44 | """ 45 | 46 | raise NotImplementedError() 47 | -------------------------------------------------------------------------------- /tests/exporters/base.py: -------------------------------------------------------------------------------- 1 | """Base TestCase class for testing Exporters""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import os 7 | 8 | from tests.base import TestsBase 9 | 10 | all_raw_mimetypes = { 11 | "text/x-python", 12 | "text/markdown", 13 | "text/html", 14 | "text/restructuredtext", 15 | "text/latex", 16 | } 17 | 18 | 19 | class ExportersTestsBase(TestsBase): 20 | """Contains base test functions for exporters""" 21 | 22 | exporter_class = None 23 | should_include_raw = None 24 | 25 | def _get_notebook(self, nb_name="notebook2.ipynb"): 26 | return os.path.join(self._get_files_path(), nb_name) 27 | 28 | def test_raw_cell_inclusion(self): 29 | """test raw cell inclusion based on raw_mimetype metadata""" 30 | if self.should_include_raw is None: 31 | return 32 | exporter = self.exporter_class() 33 | (output, _resources) = exporter.from_filename(self._get_notebook("rawtest.ipynb")) 34 | for inc in self.should_include_raw: 35 | self.assertIn("raw %s" % inc, output, "should include %s" % inc) 36 | self.assertIn("no raw_mimetype metadata", output) 37 | for exc in all_raw_mimetypes.difference(self.should_include_raw): 38 | self.assertNotIn("raw %s" % exc, output, "should exclude %s" % exc) 39 | self.assertNotIn("never be included", output) 40 | -------------------------------------------------------------------------------- /docs/source/api/preprocessors.rst: -------------------------------------------------------------------------------- 1 | Preprocessors 2 | ============= 3 | 4 | .. module:: nbconvert.preprocessors 5 | :noindex: 6 | 7 | .. seealso:: 8 | 9 | :doc:`/config_options` 10 | Configurable options for the nbconvert application 11 | 12 | .. autoclass:: Preprocessor 13 | 14 | .. automethod:: __init__ 15 | 16 | .. automethod:: preprocess 17 | 18 | .. automethod:: preprocess_cell 19 | 20 | Specialized preprocessors 21 | ------------------------- 22 | 23 | Converting and extracting figures 24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | .. autoclass:: ConvertFiguresPreprocessor 27 | 28 | .. autoclass:: SVG2PDFPreprocessor 29 | 30 | .. autoclass:: ExtractOutputPreprocessor 31 | 32 | Converting text 33 | ~~~~~~~~~~~~~~~ 34 | 35 | .. autoclass:: LatexPreprocessor 36 | 37 | .. autoclass:: HighlightMagicsPreprocessor 38 | 39 | Metadata and header control 40 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 41 | 42 | .. autoclass:: ClearMetadataPreprocessor 43 | 44 | .. autoclass:: CSSHTMLHeaderPreprocessor 45 | 46 | Removing/Manipulating cells, inputs, and outputs 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | .. autoclass:: ClearOutputPreprocessor 50 | 51 | .. autoclass:: CoalesceStreamsPreprocessor 52 | 53 | .. autoclass:: RegexRemovePreprocessor 54 | 55 | .. autoclass:: TagRemovePreprocessor 56 | 57 | Executing Notebooks 58 | ~~~~~~~~~~~~~~~~~~~ 59 | 60 | .. autoclass:: ExecutePreprocessor 61 | :members: 62 | 63 | .. autoclass:: CellExecutionError 64 | -------------------------------------------------------------------------------- /tests/exporters/test_markdown.py: -------------------------------------------------------------------------------- 1 | """Tests for MarkdownExporter""" 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (c) 2013, the IPython Development Team. 5 | # 6 | # Distributed under the terms of the Modified BSD License. 7 | # 8 | # The full license is in the file COPYING.txt, distributed with this software. 9 | # ----------------------------------------------------------------------------- 10 | 11 | # ----------------------------------------------------------------------------- 12 | # Imports 13 | # ----------------------------------------------------------------------------- 14 | 15 | from nbconvert.exporters.markdown import MarkdownExporter 16 | 17 | from .base import ExportersTestsBase 18 | 19 | # ----------------------------------------------------------------------------- 20 | # Class 21 | # ----------------------------------------------------------------------------- 22 | 23 | 24 | class TestMarkdownExporter(ExportersTestsBase): 25 | """Tests for MarkdownExporter""" 26 | 27 | exporter_class = MarkdownExporter # type:ignore 28 | should_include_raw = ["markdown", "html"] # type:ignore 29 | 30 | def test_constructor(self): 31 | """ 32 | Can a MarkdownExporter be constructed? 33 | """ 34 | MarkdownExporter() 35 | 36 | def test_export(self): 37 | """ 38 | Can a MarkdownExporter export something? 39 | """ 40 | (output, _resources) = MarkdownExporter().from_filename(self._get_notebook()) 41 | assert len(output) > 0 42 | -------------------------------------------------------------------------------- /tests/files/notebook3_with_errors.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Notebook with errors" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "This notebook contains a cell which deliberately throws an exception. This is to test if `nbconvert` stops conversion if the flag `--execute` is given without `--allow-errors`. In the cells before and after the one which raises the exception we compute a couple of numbers. If they exist in the output we know that the respective cells were executed." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "print(f\"Hello world, my number is {24 - 1}\")" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "collapsed": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "print(\"Some text before the error\")\n", 37 | "raise RuntimeError(\"This is a deliberate exception\")" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": { 44 | "collapsed": true 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "print(f\"The answer to the question about life, the universe and everything is: {43 - 1}\")" 49 | ] 50 | } 51 | ], 52 | "metadata": {}, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /docs/source/api/exporters.rst: -------------------------------------------------------------------------------- 1 | Exporters 2 | ========= 3 | 4 | .. module:: nbconvert.exporters 5 | 6 | .. seealso:: 7 | 8 | :doc:`/config_options` 9 | Configurable options for the nbconvert application 10 | 11 | .. autofunction:: export 12 | 13 | .. autofunction:: get_exporter 14 | 15 | .. autofunction:: get_export_names 16 | 17 | Exporter base classes 18 | --------------------- 19 | 20 | .. autoclass:: Exporter 21 | 22 | .. automethod:: __init__ 23 | 24 | .. automethod:: from_notebook_node 25 | 26 | .. automethod:: from_filename 27 | 28 | .. automethod:: from_file 29 | 30 | .. automethod:: register_preprocessor 31 | 32 | .. autoclass:: TemplateExporter 33 | 34 | .. automethod:: __init__ 35 | 36 | .. automethod:: from_notebook_node 37 | 38 | .. automethod:: from_filename 39 | 40 | .. automethod:: from_file 41 | 42 | .. automethod:: register_preprocessor 43 | 44 | .. automethod:: register_filter 45 | 46 | Specialized exporter classes 47 | ---------------------------- 48 | 49 | The ``NotebookExporter`` inherits directly from 50 | ``Exporter``, while the other exporters listed here 51 | inherit either directly or indirectly from 52 | ``TemplateExporter``. 53 | 54 | .. autoclass:: NotebookExporter 55 | 56 | .. autoclass:: HTMLExporter 57 | 58 | .. autoclass:: SlidesExporter 59 | 60 | .. autoclass:: LatexExporter 61 | 62 | .. autoclass:: MarkdownExporter 63 | 64 | .. autoclass:: PDFExporter 65 | 66 | .. autoclass:: WebPDFExporter 67 | 68 | .. autoclass:: PythonExporter 69 | 70 | .. autoclass:: RSTExporter 71 | -------------------------------------------------------------------------------- /tests/exporters/test_notebook.py: -------------------------------------------------------------------------------- 1 | """Tests for notebook.py""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import json 7 | 8 | from nbformat import validate 9 | 10 | from nbconvert.exporters.notebook import NotebookExporter 11 | from tests.base import assert_big_text_equal 12 | 13 | from .base import ExportersTestsBase 14 | 15 | 16 | class TestNotebookExporter(ExportersTestsBase): 17 | """Contains test functions for notebook.py""" 18 | 19 | exporter_class = NotebookExporter # type:ignore 20 | 21 | def test_export(self): 22 | """ 23 | Does the NotebookExporter return the file unchanged? 24 | """ 25 | with open(self._get_notebook()) as f: 26 | file_contents = f.read() 27 | (output, _resources) = self.exporter_class().from_filename( # type:ignore 28 | self._get_notebook() 29 | ) 30 | assert len(output) > 0 31 | assert_big_text_equal(output, file_contents) 32 | 33 | def test_downgrade_3(self): 34 | exporter = self.exporter_class(nbformat_version=3) # type:ignore 35 | (output, _resources) = exporter.from_filename(self._get_notebook()) 36 | nb = json.loads(output) 37 | validate(nb) 38 | 39 | def test_downgrade_2(self): 40 | exporter = self.exporter_class(nbformat_version=2) # type:ignore 41 | (output, _resources) = exporter.from_filename(self._get_notebook()) 42 | nb = json.loads(output) 43 | self.assertEqual(nb["nbformat"], 2) 44 | -------------------------------------------------------------------------------- /tests/exporters/files/rawtest.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "raw", 5 | "metadata": { 6 | "raw_mimetype": "text/html" 7 | }, 8 | "source": [ 9 | "raw html" 10 | ] 11 | }, 12 | { 13 | "cell_type": "raw", 14 | "metadata": { 15 | "raw_mimetype": "text/markdown" 16 | }, 17 | "source": [ 18 | "* raw markdown\n", 19 | "* bullet\n", 20 | "* list" 21 | ] 22 | }, 23 | { 24 | "cell_type": "raw", 25 | "metadata": { 26 | "raw_mimetype": "text/restructuredtext" 27 | }, 28 | "source": [ 29 | "``raw rst``\n", 30 | "\n", 31 | ".. sourcecode:: python\n", 32 | "\n", 33 | " def foo(): pass\n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "raw", 38 | "metadata": { 39 | "raw_mimetype": "text/x-python" 40 | }, 41 | "source": [ 42 | "def bar():\n", 43 | " \"\"\"raw python\"\"\"\n", 44 | " pass" 45 | ] 46 | }, 47 | { 48 | "cell_type": "raw", 49 | "metadata": { 50 | "raw_mimetype": "text/latex" 51 | }, 52 | "source": [ 53 | "\\LaTeX\n", 54 | "% raw latex" 55 | ] 56 | }, 57 | { 58 | "cell_type": "raw", 59 | "metadata": {}, 60 | "source": [ 61 | "# no raw_mimetype metadata, should be included by default" 62 | ] 63 | }, 64 | { 65 | "cell_type": "raw", 66 | "metadata": { 67 | "raw_mimetype": "doesnotexist" 68 | }, 69 | "source": [ 70 | "garbage format defined, should never be included" 71 | ] 72 | } 73 | ], 74 | "metadata": {}, 75 | "nbformat": 4, 76 | "nbformat_minor": 0 77 | } 78 | -------------------------------------------------------------------------------- /tests/writers/test_stdout.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for stdout 3 | """ 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | 17 | import sys 18 | from io import StringIO 19 | 20 | from nbconvert.writers.stdout import StdoutWriter 21 | from tests.base import TestsBase 22 | 23 | # ----------------------------------------------------------------------------- 24 | # Class 25 | # ----------------------------------------------------------------------------- 26 | 27 | 28 | class TestStdout(TestsBase): 29 | """Contains test functions for stdout.py""" 30 | 31 | def test_output(self): 32 | """Test stdout writer output.""" 33 | 34 | # Capture the stdout. Remember original. 35 | stdout = sys.stdout 36 | stream = StringIO() 37 | sys.stdout = stream 38 | 39 | # Create stdout writer, test output 40 | writer = StdoutWriter() 41 | writer.write("ax", {"b": "c"}) 42 | output = stream.getvalue() 43 | self.fuzzy_compare(output, "ax") 44 | 45 | # Revert stdout 46 | sys.stdout = stdout 47 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/coalescestreams.py: -------------------------------------------------------------------------------- 1 | """Preprocessor for merging consecutive stream outputs for easier handling.""" 2 | 3 | import re 4 | 5 | # Copyright (c) IPython Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | from nbconvert.preprocessors import Preprocessor 8 | 9 | CR_PAT = re.compile(r".*\r(?=[^\n])") 10 | 11 | 12 | class CoalesceStreamsPreprocessor(Preprocessor): 13 | """ 14 | Merge consecutive sequences of stream output into single stream 15 | to prevent extra newlines inserted at flush calls 16 | """ 17 | 18 | def preprocess_cell(self, cell, resources, cell_index): 19 | """ 20 | Apply a transformation on each cell. See base.py for details. 21 | """ 22 | outputs = cell.get("outputs", []) 23 | if not outputs: 24 | return cell, resources 25 | 26 | last = outputs[0] 27 | new_outputs = [last] 28 | for output in outputs[1:]: 29 | if ( 30 | output.output_type == "stream" 31 | and last.output_type == "stream" 32 | and last.name == output.name 33 | ): 34 | last.text += output.text 35 | else: 36 | new_outputs.append(output) 37 | last = output 38 | 39 | # process \r characters 40 | for output in new_outputs: 41 | if output.output_type == "stream" and "\r" in output.text: 42 | output.text = CR_PAT.sub("", output.text) 43 | 44 | cell.outputs = new_outputs 45 | return cell, resources 46 | -------------------------------------------------------------------------------- /nbconvert/exporters/notebook.py: -------------------------------------------------------------------------------- 1 | """NotebookExporter class""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import nbformat 7 | from traitlets import Enum, default 8 | 9 | from .exporter import Exporter 10 | 11 | 12 | class NotebookExporter(Exporter): 13 | """Exports to an IPython notebook. 14 | 15 | This is useful when you want to use nbconvert's preprocessors to operate on 16 | a notebook (e.g. to execute it) and then write it back to a notebook file. 17 | """ 18 | 19 | nbformat_version = Enum( 20 | list(nbformat.versions), 21 | default_value=nbformat.current_nbformat, 22 | help="""The nbformat version to write. 23 | Use this to downgrade notebooks. 24 | """, 25 | ).tag(config=True) 26 | 27 | @default("file_extension") 28 | def _file_extension_default(self): 29 | return ".ipynb" 30 | 31 | output_mimetype = "application/json" 32 | export_from_notebook = "Notebook" 33 | 34 | def from_notebook_node(self, nb, resources=None, **kw): 35 | """Convert from notebook node.""" 36 | nb_copy, resources = super().from_notebook_node(nb, resources, **kw) 37 | if self.nbformat_version != nb_copy.nbformat: 38 | resources["output_suffix"] = ".v%i" % self.nbformat_version 39 | else: 40 | resources["output_suffix"] = ".nbconvert" 41 | output = nbformat.writes(nb_copy, version=self.nbformat_version) 42 | if not output.endswith("\n"): 43 | output = output + "\n" 44 | return output, resources 45 | -------------------------------------------------------------------------------- /nbconvert/__init__.py: -------------------------------------------------------------------------------- 1 | """Utilities for converting notebooks to and from different formats.""" 2 | 3 | from ._version import __version__, version_info 4 | 5 | try: 6 | from . import filters, postprocessors, preprocessors, writers 7 | from .exporters import ( 8 | ASCIIDocExporter, 9 | Exporter, 10 | ExporterNameError, 11 | FilenameExtension, 12 | HTMLExporter, 13 | LatexExporter, 14 | MarkdownExporter, 15 | NotebookExporter, 16 | PDFExporter, 17 | PythonExporter, 18 | QtPDFExporter, 19 | QtPNGExporter, 20 | RSTExporter, 21 | ScriptExporter, 22 | SlidesExporter, 23 | TemplateExporter, 24 | WebPDFExporter, 25 | export, 26 | get_export_names, 27 | get_exporter, 28 | ) 29 | except ModuleNotFoundError: 30 | # We hit this condition when the package is not yet fully installed. 31 | pass 32 | 33 | 34 | __all__ = [ 35 | "ASCIIDocExporter", 36 | "Exporter", 37 | "ExporterNameError", 38 | "FilenameExtension", 39 | "HTMLExporter", 40 | "LatexExporter", 41 | "MarkdownExporter", 42 | "NotebookExporter", 43 | "PDFExporter", 44 | "PythonExporter", 45 | "QtPDFExporter", 46 | "QtPNGExporter", 47 | "RSTExporter", 48 | "ScriptExporter", 49 | "SlidesExporter", 50 | "TemplateExporter", 51 | "WebPDFExporter", 52 | "__version__", 53 | "export", 54 | "filters", 55 | "get_export_names", 56 | "get_exporter", 57 | "postprocessors", 58 | "preprocessors", 59 | "version_info", 60 | "writers", 61 | ] 62 | -------------------------------------------------------------------------------- /tests/exporters/cheese.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains CheesePreprocessor 3 | """ 4 | # ----------------------------------------------------------------------------- 5 | # Copyright (c) 2013, the IPython Development Team. 6 | # 7 | # Distributed under the terms of the Modified BSD License. 8 | # 9 | # The full license is in the file COPYING.txt, distributed with this software. 10 | # ----------------------------------------------------------------------------- 11 | 12 | # ----------------------------------------------------------------------------- 13 | # Imports 14 | # ----------------------------------------------------------------------------- 15 | 16 | from nbconvert.preprocessors.base import Preprocessor 17 | 18 | # ----------------------------------------------------------------------------- 19 | # Classes 20 | # ----------------------------------------------------------------------------- 21 | 22 | 23 | class CheesePreprocessor(Preprocessor): 24 | """ 25 | Adds a cheese tag to the resources object 26 | """ 27 | 28 | def __init__(self, **kw): 29 | """ 30 | Public constructor 31 | """ 32 | super().__init__(**kw) 33 | 34 | def preprocess(self, nb, resources): 35 | """ 36 | Sphinx preprocessing to apply on each notebook. 37 | 38 | Parameters 39 | ---------- 40 | nb : NotebookNode 41 | Notebook being converted 42 | resources : dictionary 43 | Additional resources used in the conversion process. Allows 44 | preprocessors to pass variables into the Jinja engine. 45 | """ 46 | resources["cheese"] = "real" 47 | return nb, resources 48 | -------------------------------------------------------------------------------- /docs/source/api/filters.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: none 2 | 3 | Filters 4 | ======= 5 | 6 | Filters are for use with the ``nbconvert.exporters.templateexporter.TemplateExporter`` exporter. 7 | They provide a way for you transform notebook contents to a particular format depending 8 | on the template you are using. For example, when converting to HTML, you would want to 9 | use the ``ansi2html()`` function to convert ANSI colors (from 10 | e.g. a terminal traceback) to HTML colors. 11 | 12 | .. seealso:: 13 | 14 | :doc:`/api/exporters` 15 | API documentation for the various exporter classes 16 | 17 | .. module:: nbconvert.filters 18 | 19 | .. autofunction:: add_anchor 20 | 21 | .. autofunction:: add_prompts 22 | 23 | .. autofunction:: ansi2html 24 | 25 | .. autofunction:: ansi2latex 26 | 27 | .. autofunction:: ascii_only 28 | 29 | .. autofunction:: citation2latex 30 | 31 | .. autofunction:: comment_lines 32 | 33 | .. autofunction:: escape_latex 34 | 35 | .. autoclass:: DataTypeFilter 36 | 37 | .. autofunction:: get_lines 38 | 39 | .. autofunction:: convert_pandoc 40 | 41 | .. autoclass:: Highlight2HTML 42 | 43 | .. autoclass:: Highlight2Latex 44 | 45 | .. autofunction:: html2text 46 | 47 | .. autofunction:: indent 48 | 49 | .. autofunction:: ipython2python 50 | 51 | .. autofunction:: markdown2html 52 | 53 | .. autofunction:: markdown2latex 54 | 55 | .. autofunction:: markdown2rst 56 | 57 | .. autofunction:: path2url 58 | 59 | .. autofunction:: posix_path 60 | 61 | .. autofunction:: prevent_list_blocks 62 | 63 | .. autofunction:: strip_ansi 64 | 65 | .. autofunction:: strip_dollars 66 | 67 | .. autofunction:: strip_files_prefix 68 | 69 | .. autofunction:: wrap_text 70 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | since: 16 | description: "Use PRs with activity since this date or git reference" 17 | required: false 18 | since_last_stable: 19 | description: "Use PRs with activity since the last stable git tag" 20 | required: false 21 | type: boolean 22 | jobs: 23 | prep_release: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: write 27 | steps: 28 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 29 | 30 | - name: Prep Release 31 | id: prep-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | version_spec: ${{ github.event.inputs.version_spec }} 36 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 37 | target: ${{ github.event.inputs.target }} 38 | branch: ${{ github.event.inputs.branch }} 39 | since: ${{ github.event.inputs.since }} 40 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 41 | 42 | - name: "** Next Step **" 43 | run: | 44 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | - Copyright (c) 2001-2015, IPython Development Team 4 | - Copyright (c) 2015-, Jupyter Development Team 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | 3. Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /tests/filters/test_datatypefilter.py: -------------------------------------------------------------------------------- 1 | """Module with tests for DataTypeFilter""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | import pytest 6 | 7 | from nbconvert.filters.datatypefilter import DataTypeFilter 8 | from tests.base import TestsBase 9 | 10 | 11 | class TestDataTypeFilter(TestsBase): 12 | """Contains test functions for datatypefilter.py""" 13 | 14 | def test_constructor(self): 15 | """Can an instance of a DataTypeFilter be created?""" 16 | DataTypeFilter() 17 | 18 | def test_junk_types(self): 19 | """Can the DataTypeFilter pickout a useful type from a dict with junk types as keys?""" 20 | filter_ = DataTypeFilter() 21 | assert "image/png" in filter_({"hair": "1", "water": 2, "image/png": 3, "rock": 4.0}) 22 | assert "application/pdf" in filter_( 23 | { 24 | "application/pdf": "file_path", 25 | "hair": 2, 26 | "water": "yay", 27 | "png": "not a png", 28 | "rock": "is a rock", 29 | } 30 | ) 31 | 32 | with pytest.warns(UserWarning, match="Your element with.*"): 33 | self.assertEqual( 34 | filter_( 35 | {"hair": "this is not", "water": "going to return anything", "rock": "or is it"} 36 | ), 37 | [], 38 | ) 39 | 40 | def test_null(self): 41 | """Will the DataTypeFilter fail if no types are passed in?""" 42 | filter_ = DataTypeFilter() 43 | with pytest.warns(UserWarning, match="Your element with.*"): 44 | self.assertEqual(filter_({}), []) 45 | -------------------------------------------------------------------------------- /nbconvert/writers/debug.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains debug writer. 3 | """ 4 | 5 | from pprint import pprint 6 | 7 | from .base import WriterBase 8 | 9 | # ----------------------------------------------------------------------------- 10 | # Copyright (c) 2013, the IPython Development Team. 11 | # 12 | # Distributed under the terms of the Modified BSD License. 13 | # 14 | # The full license is in the file COPYING.txt, distributed with this software. 15 | # ----------------------------------------------------------------------------- 16 | 17 | # ----------------------------------------------------------------------------- 18 | # Imports 19 | # ----------------------------------------------------------------------------- 20 | 21 | 22 | # ----------------------------------------------------------------------------- 23 | # Classes 24 | # ----------------------------------------------------------------------------- 25 | 26 | 27 | class DebugWriter(WriterBase): 28 | """Consumes output from nbconvert export...() methods and writes useful 29 | debugging information to the stdout. The information includes a list of 30 | resources that were extracted from the notebook(s) during export.""" 31 | 32 | def write(self, output, resources, notebook_name="notebook", **kw): 33 | """ 34 | Consume and write Jinja output. 35 | 36 | See base for more... 37 | """ 38 | 39 | if isinstance(resources["outputs"], dict): 40 | print("outputs extracted from %s" % notebook_name) 41 | print("-" * 80) 42 | pprint(resources["outputs"], indent=2, width=70) # noqa: T203 43 | else: 44 | print("no outputs extracted from %s" % notebook_name) 45 | print("=" * 80) 46 | -------------------------------------------------------------------------------- /nbconvert/filters/filter_links.py: -------------------------------------------------------------------------------- 1 | """A pandoc filter used in converting notebooks to Latex. 2 | Converts links between notebooks to Latex cross-references. 3 | """ 4 | 5 | import re 6 | 7 | from pandocfilters import RawInline, applyJSONFilters, stringify # type:ignore[import-untyped] 8 | 9 | 10 | def resolve_references(source): 11 | """ 12 | This applies the resolve_one_reference to the text passed in via the source argument. 13 | 14 | This expects content in the form of a string encoded JSON object as represented 15 | internally in ``pandoc``. 16 | """ 17 | return applyJSONFilters([resolve_one_reference], source) 18 | 19 | 20 | def resolve_one_reference(key, val, fmt, meta): 21 | """ 22 | This takes a tuple of arguments that are compatible with ``pandocfilters.walk()`` that 23 | allows identifying hyperlinks in the document and transforms them into valid LaTeX 24 | \\hyperref{} calls so that linking to headers between cells is possible. 25 | 26 | See the documentation in ``pandocfilters.walk()`` for further information on the meaning 27 | and specification of ``key``, ``val``, ``fmt``, and ``meta``. 28 | """ 29 | 30 | if key == "Link": 31 | text = stringify(val[1]) 32 | target = val[2][0] 33 | m = re.match(r"#(.+)$", target) 34 | if m: 35 | # pandoc automatically makes labels for headings. 36 | label = m.group(1).lower() 37 | label = re.sub(r"[^\w-]+", "", label) # Strip HTML entities 38 | text = re.sub(r"_", r"\_", text) # Escape underscores in display text 39 | return RawInline("tex", rf"\hyperref[{label}]{{{text}}}") 40 | return None 41 | # Other elements will be returned unchanged. 42 | -------------------------------------------------------------------------------- /nbconvert/filters/datatypefilter.py: -------------------------------------------------------------------------------- 1 | """Filter used to select the first preferred output format available. 2 | 3 | The filter contained in the file allows the converter templates to select 4 | the output format that is most valuable to the active export format. The 5 | value of the different formats is set via 6 | NbConvertBase.display_data_priority 7 | """ 8 | # ----------------------------------------------------------------------------- 9 | # Copyright (c) 2013, the IPython Development Team. 10 | # 11 | # Distributed under the terms of the Modified BSD License. 12 | # 13 | # The full license is in the file COPYING.txt, distributed with this software. 14 | # ----------------------------------------------------------------------------- 15 | 16 | # ----------------------------------------------------------------------------- 17 | # Classes and functions 18 | # ----------------------------------------------------------------------------- 19 | 20 | from warnings import warn 21 | 22 | from nbconvert.utils.base import NbConvertBase 23 | 24 | __all__ = ["DataTypeFilter"] 25 | 26 | 27 | class DataTypeFilter(NbConvertBase): 28 | """Returns the preferred display format""" 29 | 30 | def __call__(self, output): 31 | """Return the first available format in the priority. 32 | 33 | Produces a UserWarning if no compatible mimetype is found. 34 | 35 | `output` is dict with structure {mimetype-of-element: value-of-element} 36 | 37 | """ 38 | for fmt in self.display_data_priority: 39 | if fmt in output: 40 | return [fmt] 41 | warn( 42 | f"Your element with mimetype(s) {output.keys()} is not able to be represented.", 43 | stacklevel=2, 44 | ) 45 | 46 | return [] 47 | -------------------------------------------------------------------------------- /tests/exporters/files/prompt_numbers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 10, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/plain": [ 22 | "(100,)" 23 | ] 24 | }, 25 | "execution_count": 10, 26 | "metadata": {}, 27 | "output_type": "execute_result" 28 | } 29 | ], 30 | "source": [ 31 | "evs = np.zeros(100)\n", 32 | "evs.shape" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "outputs": [], 42 | "source": [] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": { 48 | "collapsed": true 49 | }, 50 | "outputs": [], 51 | "source": [] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 0, 56 | "metadata": { 57 | "collapsed": true 58 | }, 59 | "outputs": [], 60 | "source": [] 61 | } 62 | ], 63 | "metadata": { 64 | "kernelspec": { 65 | "display_name": "Python 3", 66 | "language": "python", 67 | "name": "python3" 68 | }, 69 | "language_info": { 70 | "codemirror_mode": { 71 | "name": "ipython", 72 | "version": 3 73 | }, 74 | "file_extension": ".py", 75 | "mimetype": "text/x-python", 76 | "name": "python", 77 | "nbconvert_exporter": "python", 78 | "pygments_lexer": "ipython3", 79 | "version": "3.6.1" 80 | } 81 | }, 82 | "nbformat": 4, 83 | "nbformat_minor": 1 84 | } 85 | -------------------------------------------------------------------------------- /tests/writers/test_debug.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for debug 3 | """ 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | 17 | import sys 18 | from io import StringIO 19 | 20 | from nbconvert.writers.debug import DebugWriter 21 | from tests.base import TestsBase 22 | 23 | # ----------------------------------------------------------------------------- 24 | # Class 25 | # ----------------------------------------------------------------------------- 26 | 27 | 28 | class TestDebug(TestsBase): 29 | """Contains test functions for debug.py""" 30 | 31 | def test_output(self): 32 | """Test debug writer output.""" 33 | 34 | # Capture the stdout. Remember original. 35 | stdout = sys.stdout 36 | stream = StringIO() 37 | sys.stdout = stream 38 | 39 | # Create stdout writer, get output 40 | writer = DebugWriter() 41 | writer.write("aaa", {"outputs": {"bbb": "ccc"}}) 42 | output = stream.getvalue() 43 | 44 | # Check output. Make sure resources dictionary is dumped, but nothing 45 | # else. 46 | assert "aaa" not in output 47 | assert "bbb" in output 48 | assert "ccc" in output 49 | 50 | # Revert stdout 51 | sys.stdout = stdout 52 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/convertfigures.py: -------------------------------------------------------------------------------- 1 | """Module containing a preprocessor that converts outputs in the notebook from 2 | one format to another. 3 | 4 | Converts all of the outputs in a notebook from one format to another. 5 | """ 6 | # Copyright (c) Jupyter Development Team. 7 | # Distributed under the terms of the Modified BSD License. 8 | 9 | from traitlets import Unicode 10 | 11 | from .base import Preprocessor 12 | 13 | 14 | class ConvertFiguresPreprocessor(Preprocessor): 15 | """ 16 | Converts all of the outputs in a notebook from one format to another. 17 | """ 18 | 19 | from_format = Unicode(help="Format the converter accepts").tag(config=True) 20 | to_format = Unicode(help="Format the converter writes").tag(config=True) 21 | 22 | def __init__(self, **kw): 23 | """ 24 | Public constructor 25 | """ 26 | super().__init__(**kw) 27 | 28 | def convert_figure(self, data_format, data): 29 | """Convert the figure.""" 30 | raise NotImplementedError() 31 | 32 | def preprocess_cell(self, cell, resources, cell_index): 33 | """ 34 | Apply a transformation on each cell, 35 | 36 | See base.py 37 | """ 38 | 39 | # Loop through all of the datatypes of the outputs in the cell. 40 | for output in cell.get("outputs", []): 41 | if ( 42 | output.output_type in {"execute_result", "display_data"} 43 | and self.from_format in output.data 44 | and self.to_format not in output.data 45 | ): 46 | output.data[self.to_format] = self.convert_figure( 47 | self.from_format, output.data[self.from_format] 48 | ) 49 | 50 | return cell, resources 51 | -------------------------------------------------------------------------------- /nbconvert/exporters/asciidoc.py: -------------------------------------------------------------------------------- 1 | """ASCIIDoc Exporter class""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import default 7 | from traitlets.config import Config 8 | 9 | from .templateexporter import TemplateExporter 10 | 11 | 12 | class ASCIIDocExporter(TemplateExporter): 13 | """ 14 | Exports to an ASCIIDoc document (.asciidoc) 15 | """ 16 | 17 | @default("file_extension") 18 | def _file_extension_default(self): 19 | return ".asciidoc" 20 | 21 | @default("template_name") 22 | def _template_name_default(self): 23 | return "asciidoc" 24 | 25 | output_mimetype = "text/asciidoc" 26 | export_from_notebook = "AsciiDoc" 27 | 28 | @default("raw_mimetypes") 29 | def _raw_mimetypes_default(self): 30 | return ["text/asciidoc/", "text/markdown", "text/html", ""] 31 | 32 | @property 33 | def default_config(self): 34 | c = Config( 35 | { 36 | "NbConvertBase": { 37 | "display_data_priority": [ 38 | "text/html", 39 | "text/markdown", 40 | "image/svg+xml", 41 | "image/png", 42 | "image/jpeg", 43 | "text/plain", 44 | "text/latex", 45 | ] 46 | }, 47 | "ExtractOutputPreprocessor": {"enabled": True}, 48 | "HighlightMagicsPreprocessor": {"enabled": True}, 49 | } 50 | ) 51 | if super().default_config: 52 | c2 = super().default_config.copy() 53 | c2.merge(c) 54 | c = c2 55 | return c 56 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documenting nbconvert 2 | 3 | [Documentation for `nbconvert`](https://nbconvert.readthedocs.io/en/latest/) 4 | is hosted on ReadTheDocs. 5 | 6 | ## Build Documentation locally 7 | 8 | 1. Change directory to documentation root: 9 | 10 | ``` 11 | $ cd docs 12 | ``` 13 | 14 | 1. Create conda env (and install relevant dependencies): 15 | 16 | ``` 17 | $ conda env create -f environment.yml 18 | ``` 19 | 20 | 1. Activate the newly built conda environment `nbconvert_docs` 21 | 22 | ``` 23 | $ source activate nbconvert_docs 24 | ``` 25 | 26 | 1. Create an editable install for nbconvert with doc dependencies using 27 | 28 | ``` 29 | $ pip install -e '..[docs]' 30 | ``` 31 | 32 | or if you want, `cd ..` and `pip install . -e`. But then you will need to `cd docs` before 33 | continuing to the next step. 34 | 35 | 1. Build documentation using Makefile for Linux and OS X: 36 | 37 | ``` 38 | $ make html 39 | ``` 40 | 41 | or on Windows: 42 | 43 | ``` 44 | $ make.bat html 45 | ``` 46 | 47 | 1. Display the documentation locally by navigating to 48 | `build/html/index.html` in your browser: 49 | 50 | Or alternatively you may run a local server to display 51 | the docs. In Python 3: 52 | 53 | ``` 54 | $ python -m http.server 8000 55 | ``` 56 | 57 | In your browser, go to `http://localhost:8000`. 58 | 59 | ## Developing Documentation 60 | 61 | ### Helpful files and directories 62 | 63 | - `conf.py` - Sphinx build configuration file 64 | - `source` directory - source for documentation 65 | - `source/api` directory - source files for generated API documentation 66 | - `autogen_config.py` - Generates configuration of ipynb source files to rst 67 | - `index.rst` - Main landing page of the Sphinx documentation 68 | -------------------------------------------------------------------------------- /tests/preprocessors/test_csshtmlheader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for the csshtmlheader preprocessor 3 | """ 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | 17 | from nbconvert.preprocessors.csshtmlheader import CSSHTMLHeaderPreprocessor 18 | 19 | from .base import PreprocessorTestsBase 20 | 21 | # ----------------------------------------------------------------------------- 22 | # Class 23 | # ----------------------------------------------------------------------------- 24 | 25 | 26 | class TestCSSHTMLHeader(PreprocessorTestsBase): 27 | """Contains test functions for csshtmlheader.py""" 28 | 29 | def build_preprocessor(self): 30 | """Make an instance of a preprocessor""" 31 | preprocessor = CSSHTMLHeaderPreprocessor() 32 | preprocessor.enabled = True 33 | return preprocessor 34 | 35 | def test_constructor(self): 36 | """Can a CSSHTMLHeaderPreprocessor be constructed?""" 37 | self.build_preprocessor() 38 | 39 | def test_output(self): 40 | """Test the output of the CSSHTMLHeaderPreprocessor""" 41 | nb = self.build_notebook() 42 | res = self.build_resources() 43 | preprocessor = self.build_preprocessor() 44 | nb, res = preprocessor(nb, res) 45 | assert "css" in res["inlining"] 46 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making an NBConvert Release 2 | 3 | ## Using `jupyter_releaser` 4 | 5 | The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser) from this repository. 6 | 7 | - Run the ["Step 1: Prep Release"](https://github.com/jupyter/nbconvert/actions/workflows/prep-release.yml) workflow with the appropriate inputs. 8 | - You can usually use the following values for the workflow: 9 | - branch : 'main' when releasing from the main branch 10 | - "Post Version Specifier" empty unless you do a beta or rc release 11 | - keep `since_last_stable` unchecked 12 | - "Use PRs with activity since this date or git reference" 13 | - Review the changelog in the [draft GitHub release](https://github.com/jupyter/nbconvert/releases) created in Step 1. 14 | - You will need the URL to the created draft release in the form `https://github.com/jupyter/nbconvert/releases/tag/-` for next step. 15 | - Run the ["Step 2: Publish Release"](https://github.com/jupyter/nbconvert/actions/workflows/publish-release.yml) workflow to finalize the release. 16 | 17 | ## Manual Release 18 | 19 | To create a manual release, perform the following steps: 20 | 21 | ### Set up 22 | 23 | ```bash 24 | pip install pipx 25 | git pull origin $(git branch --show-current) 26 | git clean -dffx 27 | ``` 28 | 29 | ### Update the version and apply the tag 30 | 31 | ```bash 32 | echo "Enter new version" 33 | read new_version 34 | pipx run hatch version ${new_version} 35 | git tag -a ${new_version} -m "Release ${new_version}" 36 | ``` 37 | 38 | ### Build the artifacts 39 | 40 | ```bash 41 | rm -rf dist 42 | pipx run build . 43 | ``` 44 | 45 | ### Publish the artifacts to pypi 46 | 47 | ```bash 48 | pipx run twine check dist/* 49 | pipx run twine upload dist/* 50 | ``` 51 | -------------------------------------------------------------------------------- /nbconvert/exporters/markdown.py: -------------------------------------------------------------------------------- 1 | """Markdown Exporter class""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import default 7 | from traitlets.config import Config 8 | 9 | from .templateexporter import TemplateExporter 10 | 11 | 12 | class MarkdownExporter(TemplateExporter): 13 | """ 14 | Exports to a markdown document (.md) 15 | """ 16 | 17 | export_from_notebook = "Markdown" 18 | 19 | @default("file_extension") 20 | def _file_extension_default(self): 21 | return ".md" 22 | 23 | @default("template_name") 24 | def _template_name_default(self): 25 | return "markdown" 26 | 27 | output_mimetype = "text/markdown" 28 | 29 | @default("raw_mimetypes") 30 | def _raw_mimetypes_default(self): 31 | return ["text/markdown", "text/html", ""] 32 | 33 | @property 34 | def default_config(self): 35 | c = Config( 36 | { 37 | "ExtractAttachmentsPreprocessor": {"enabled": True}, 38 | "ExtractOutputPreprocessor": {"enabled": True}, 39 | "NbConvertBase": { 40 | "display_data_priority": [ 41 | "text/html", 42 | "text/markdown", 43 | "image/svg+xml", 44 | "text/latex", 45 | "image/png", 46 | "image/jpeg", 47 | "text/plain", 48 | ] 49 | }, 50 | "HighlightMagicsPreprocessor": {"enabled": True}, 51 | } 52 | ) 53 | if super().default_config: 54 | c2 = super().default_config.copy() 55 | c2.merge(c) 56 | c = c2 57 | return c 58 | -------------------------------------------------------------------------------- /share/templates/latex/display_priority.j2: -------------------------------------------------------------------------------- 1 | ((= Auto-generated template file, DO NOT edit directly! 2 | To edit this file, please refer to ../../skeleton/README.md =)) 3 | 4 | 5 | ((*- extends 'null.j2' -*)) 6 | 7 | ((=display data priority=)) 8 | 9 | 10 | ((*- block data_priority scoped -*)) 11 | ((*- for type in output.data | filter_data_type -*)) 12 | ((*- if type == 'application/pdf' -*)) 13 | ((*- block data_pdf -*)) 14 | ((*- endblock -*)) 15 | ((*- elif type == 'image/svg+xml' -*)) 16 | ((*- block data_svg -*)) 17 | ((*- endblock -*)) 18 | ((*- elif type == 'image/png' -*)) 19 | ((*- block data_png -*)) 20 | ((*- endblock -*)) 21 | ((*- elif type == 'text/html' -*)) 22 | ((*- block data_html -*)) 23 | ((*- endblock -*)) 24 | ((*- elif type == 'text/markdown' -*)) 25 | ((*- block data_markdown -*)) 26 | ((*- endblock -*)) 27 | ((*- elif type == 'image/jpeg' -*)) 28 | ((*- block data_jpg -*)) 29 | ((*- endblock -*)) 30 | ((*- elif type == 'text/plain' -*)) 31 | ((*- block data_text -*)) 32 | ((*- endblock -*)) 33 | ((*- elif type == 'text/latex' -*)) 34 | ((*- block data_latex -*)) 35 | ((*- endblock -*)) 36 | ((*- elif type == 'application/javascript' -*)) 37 | ((*- block data_javascript -*)) 38 | ((*- endblock -*)) 39 | ((*- elif type == 'application/vnd.jupyter.widget-view+json' -*)) 40 | ((*- block data_widget_view -*)) 41 | ((*- endblock -*)) 42 | ((*- else -*)) 43 | ((*- block data_other -*)) 44 | ((*- endblock -*)) 45 | ((*- endif -*)) 46 | ((*- endfor -*)) 47 | ((*- endblock data_priority -*)) 48 | -------------------------------------------------------------------------------- /tests/filters/test_latex.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for Latex 3 | """ 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | 17 | from nbconvert.filters.latex import escape_latex 18 | from tests.base import TestsBase 19 | 20 | # ----------------------------------------------------------------------------- 21 | # Class 22 | # ----------------------------------------------------------------------------- 23 | 24 | 25 | class TestLatex(TestsBase): 26 | def test_escape_latex(self): 27 | """escape_latex test""" 28 | tests = [ 29 | (r"How are \you doing today?", r"How are \textbackslash{}you doing today?"), 30 | ( 31 | r"\escapechar=`\A\catcode`\|=0 |string|foo", 32 | r"\textbackslash{}escapechar=`\textbackslash{}A\textbackslash{}catcode`\textbackslash{}|=0 |string|foo", 33 | ), 34 | ( 35 | r"# $ % & ~ _ ^ \ { }", 36 | r"\# \$ \% \& \textasciitilde{} \_ \^{} \textbackslash{} \{ \}", 37 | ), 38 | ("...", r"{\ldots}"), 39 | ("", ""), 40 | ] 41 | 42 | for test in tests: 43 | self._try_escape_latex(test[0], test[1]) 44 | 45 | def _try_escape_latex(self, test, result): 46 | """Try to remove latex from string""" 47 | self.assertEqual(escape_latex(test), result) 48 | -------------------------------------------------------------------------------- /nbconvert/filters/__init__.py: -------------------------------------------------------------------------------- 1 | from nbconvert.utils.text import indent 2 | 3 | from .ansi import ansi2html, ansi2latex, strip_ansi 4 | from .citation import citation2latex 5 | from .datatypefilter import DataTypeFilter 6 | from .highlight import Highlight2HTML, Highlight2Latex 7 | from .latex import escape_latex 8 | from .markdown import ( 9 | markdown2asciidoc, 10 | markdown2html, 11 | markdown2html_mistune, 12 | markdown2html_pandoc, 13 | markdown2latex, 14 | markdown2rst, 15 | ) 16 | from .metadata import get_metadata 17 | from .pandoc import ConvertExplicitlyRelativePaths, convert_pandoc 18 | from .strings import ( 19 | add_anchor, 20 | add_prompts, 21 | ascii_only, 22 | clean_html, 23 | comment_lines, 24 | get_lines, 25 | html2text, 26 | ipython2python, 27 | path2url, 28 | posix_path, 29 | prevent_list_blocks, 30 | strip_dollars, 31 | strip_files_prefix, 32 | strip_trailing_newline, 33 | text_base64, 34 | wrap_text, 35 | ) 36 | 37 | __all__ = [ 38 | "ConvertExplicitlyRelativePaths", 39 | "DataTypeFilter", 40 | "Highlight2HTML", 41 | "Highlight2Latex", 42 | "add_anchor", 43 | "add_prompts", 44 | "ansi2html", 45 | "ansi2latex", 46 | "ascii_only", 47 | "citation2latex", 48 | "clean_html", 49 | "comment_lines", 50 | "convert_pandoc", 51 | "escape_latex", 52 | "get_lines", 53 | "get_metadata", 54 | "html2text", 55 | "indent", 56 | "ipython2python", 57 | "markdown2asciidoc", 58 | "markdown2html", 59 | "markdown2html_mistune", 60 | "markdown2html_pandoc", 61 | "markdown2latex", 62 | "markdown2rst", 63 | "path2url", 64 | "posix_path", 65 | "prevent_list_blocks", 66 | "strip_ansi", 67 | "strip_dollars", 68 | "strip_files_prefix", 69 | "strip_trailing_newline", 70 | "text_base64", 71 | "wrap_text", 72 | ] 73 | -------------------------------------------------------------------------------- /share/templates/base/display_priority.j2: -------------------------------------------------------------------------------- 1 | {%- extends 'base/null.j2' -%} 2 | 3 | {#display data priority#} 4 | 5 | 6 | {%- block data_priority scoped -%} 7 | {%- for type in output.data | filter_data_type -%} 8 | {%- if type == 'application/pdf' -%} 9 | {%- block data_pdf -%} 10 | {%- endblock -%} 11 | {%- elif type == 'image/svg+xml' -%} 12 | {%- block data_svg -%} 13 | {%- endblock -%} 14 | {%- elif type == 'image/png' -%} 15 | {%- block data_png -%} 16 | {%- endblock -%} 17 | {%- elif type == 'text/html' -%} 18 | {%- block data_html -%} 19 | {%- endblock -%} 20 | {%- elif type == 'text/markdown' -%} 21 | {%- block data_markdown -%} 22 | {%- endblock -%} 23 | {%- elif type == 'image/jpeg' -%} 24 | {%- block data_jpg -%} 25 | {%- endblock -%} 26 | {%- elif type == 'text/plain' -%} 27 | {%- block data_text -%} 28 | {%- endblock -%} 29 | {%- elif type == 'text/latex' -%} 30 | {%- block data_latex -%} 31 | {%- endblock -%} 32 | {%- elif type == 'text/vnd.mermaid' -%} 33 | {%- block data_mermaid -%} 34 | {%- endblock -%} 35 | {%- elif type == 'application/javascript' -%} 36 | {%- block data_javascript -%} 37 | {%- endblock -%} 38 | {%- elif type == 'application/vnd.jupyter.widget-view+json' -%} 39 | {%- block data_widget_view -%} 40 | {%- endblock -%} 41 | {%- elif type == resources.output_mimetype -%} 42 | {%- block data_native -%} 43 | {%- endblock -%} 44 | {%- else -%} 45 | {%- block data_other -%} 46 | {%- endblock -%} 47 | {%- endif -%} 48 | {%- endfor -%} 49 | {%- endblock data_priority -%} 50 | -------------------------------------------------------------------------------- /tests/preprocessors/test_highlightmagics.py: -------------------------------------------------------------------------------- 1 | """Tests for the HighlightMagics preprocessor""" 2 | 3 | from nbconvert.preprocessors.highlightmagics import HighlightMagicsPreprocessor 4 | 5 | from .base import PreprocessorTestsBase 6 | 7 | 8 | class TestHighlightMagics(PreprocessorTestsBase): 9 | """Contains test functions for highlightmagics.py""" 10 | 11 | def build_preprocessor(self): 12 | """Make an instance of a preprocessor""" 13 | preprocessor = HighlightMagicsPreprocessor() 14 | preprocessor.enabled = True 15 | return preprocessor 16 | 17 | def test_constructor(self): 18 | """Can a HighlightMagicsPreprocessor be constructed?""" 19 | self.build_preprocessor() 20 | 21 | def test_tagging(self): 22 | """Test the HighlightMagicsPreprocessor tagging""" 23 | nb = self.build_notebook() 24 | res = self.build_resources() 25 | preprocessor = self.build_preprocessor() 26 | nb.cells[0].source = """%%R -i x,y -o XYcoef 27 | lm.fit <- lm(y~x) 28 | par(mfrow=c(2,2)) 29 | print(summary(lm.fit)) 30 | plot(lm.fit) 31 | XYcoef <- coef(lm.fit)""" 32 | 33 | nb, res = preprocessor(nb, res) 34 | 35 | assert "magics_language" in nb.cells[0]["metadata"] 36 | 37 | self.assertEqual(nb.cells[0]["metadata"]["magics_language"], "r") 38 | 39 | def test_no_false_positive(self): 40 | """Test that HighlightMagicsPreprocessor does not tag false positives""" 41 | nb = self.build_notebook() 42 | res = self.build_resources() 43 | preprocessor = self.build_preprocessor() 44 | nb.cells[0].source = """# this should not be detected 45 | print(\""" 46 | %%R -i x, y 47 | \""")""" 48 | nb, res = preprocessor(nb, res) 49 | 50 | assert "magics_language" not in nb.cells[0]["metadata"] 51 | -------------------------------------------------------------------------------- /nbconvert/exporters/rst.py: -------------------------------------------------------------------------------- 1 | """reStructuredText Exporter class""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import default 7 | from traitlets.config import Config 8 | 9 | from ..filters import DataTypeFilter 10 | from .templateexporter import TemplateExporter 11 | 12 | 13 | class RSTExporter(TemplateExporter): 14 | """ 15 | Exports reStructuredText documents. 16 | """ 17 | 18 | @default("file_extension") 19 | def _file_extension_default(self): 20 | return ".rst" 21 | 22 | @default("template_name") 23 | def _template_name_default(self): 24 | return "rst" 25 | 26 | @default("raw_mimetypes") 27 | def _raw_mimetypes_default(self): 28 | # Up to summer 2024, nbconvert had a mistaken output_mimetype. 29 | # Listing that as an extra option here maintains compatibility for 30 | # notebooks with raw cells marked as that mimetype. 31 | return [self.output_mimetype, "text/restructuredtext", ""] 32 | 33 | output_mimetype = "text/x-rst" 34 | export_from_notebook = "reST" 35 | 36 | def default_filters(self): 37 | """Override filter_data_type to use native rst outputs""" 38 | dtf = DataTypeFilter() 39 | dtf.display_data_priority = [self.output_mimetype, *dtf.display_data_priority] 40 | filters = dict(super().default_filters()) 41 | filters["filter_data_type"] = dtf 42 | return filters.items() 43 | 44 | @property 45 | def default_config(self): 46 | c = Config( 47 | { 48 | "CoalesceStreamsPreprocessor": {"enabled": True}, 49 | "ExtractOutputPreprocessor": {"enabled": True}, 50 | "HighlightMagicsPreprocessor": {"enabled": True}, 51 | } 52 | ) 53 | if super().default_config: 54 | c2 = super().default_config.copy() 55 | c2.merge(c) 56 | c = c2 57 | return c 58 | -------------------------------------------------------------------------------- /tests/exporters/files/rst_output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "d5202f0f-21b8-4509-a9e2-45caf1c7db7a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from textwrap import indent\n", 11 | "\n", 12 | "\n", 13 | "class Note:\n", 14 | " def __init__(self, text):\n", 15 | " self.text = text\n", 16 | "\n", 17 | " def _repr_html_(self):\n", 18 | " return f'
{self.text}
'\n", 19 | "\n", 20 | " def _repr_mimebundle_(self, include=None, exclude=None):\n", 21 | " return {\"text/x-rst\": \".. note::\\n\\n\" + indent(self.text, \" \")}" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "id": "5145b05f-3a07-4cff-8738-516a9c27cb58", 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/html": [ 33 | "
Testing testing
" 34 | ], 35 | "text/plain": [ 36 | "<__main__.Note at 0x7f457c0250d0>" 37 | ], 38 | "text/x-rst": [ 39 | ".. note::\n", 40 | "\n", 41 | " Testing testing" 42 | ] 43 | }, 44 | "execution_count": 2, 45 | "metadata": {}, 46 | "output_type": "execute_result" 47 | } 48 | ], 49 | "source": [ 50 | "Note(\"Testing testing\")" 51 | ] 52 | } 53 | ], 54 | "metadata": { 55 | "kernelspec": { 56 | "display_name": "Python 3 (ipykernel)", 57 | "language": "python", 58 | "name": "python3" 59 | }, 60 | "language_info": { 61 | "codemirror_mode": { 62 | "name": "ipython", 63 | "version": 3 64 | }, 65 | "file_extension": ".py", 66 | "mimetype": "text/x-python", 67 | "name": "python", 68 | "nbconvert_exporter": "python", 69 | "pygments_lexer": "ipython3", 70 | "version": "3.12.4" 71 | } 72 | }, 73 | "nbformat": 4, 74 | "nbformat_minor": 5 75 | } 76 | -------------------------------------------------------------------------------- /tests/files/notebook_tags.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "tags": [ 8 | "mycelltag", 9 | "mysecondcelltag" 10 | ] 11 | }, 12 | "outputs": [ 13 | { 14 | "name": "stdout", 15 | "output_type": "stream", 16 | "text": [ 17 | "this cell should have tags in html output\n" 18 | ] 19 | } 20 | ], 21 | "source": [ 22 | "print(\"this cell should have tags in html output\")" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "this cell should NOT have tags in html output\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "print(\"this cell should NOT have tags in html output\")" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": { 45 | "tags": [ 46 | "mymarkdowncelltag" 47 | ] 48 | }, 49 | "source": [ 50 | "This markdown cell should have tags in the html output" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "This markdown cell should **not** have tags in the html output" 58 | ] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Python 3", 64 | "language": "python", 65 | "name": "python3" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 3 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython3", 77 | "version": "3.7.3" 78 | }, 79 | "widgets": { 80 | "application/vnd.jupyter.widget-state+json": { 81 | "state": {}, 82 | "version_major": 2, 83 | "version_minor": 0 84 | } 85 | } 86 | }, 87 | "nbformat": 4, 88 | "nbformat_minor": 4 89 | } 90 | -------------------------------------------------------------------------------- /tests/preprocessors/test_latex.py: -------------------------------------------------------------------------------- 1 | """Tests for the latex preprocessor""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from nbconvert.preprocessors.latex import LatexPreprocessor 7 | 8 | from .base import PreprocessorTestsBase 9 | 10 | 11 | class TestLatex(PreprocessorTestsBase): 12 | """Contains test functions for latex.py""" 13 | 14 | def build_preprocessor(self): 15 | """Make an instance of a preprocessor""" 16 | preprocessor = LatexPreprocessor() 17 | preprocessor.enabled = True 18 | return preprocessor 19 | 20 | def test_constructor(self): 21 | """Can a LatexPreprocessor be constructed?""" 22 | self.build_preprocessor() 23 | 24 | def test_output(self): 25 | """Test the output of the LatexPreprocessor""" 26 | nb = self.build_notebook() 27 | res = self.build_resources() 28 | preprocessor = self.build_preprocessor() 29 | nb, res = preprocessor(nb, res) 30 | 31 | # Make sure the code cell wasn't modified. 32 | self.assertEqual(nb.cells[0].source, "$ e $") 33 | 34 | # Verify that the markdown cell wasn't processed. 35 | self.assertEqual(nb.cells[1].source, "$ e $") 36 | 37 | def test_highlight(self): 38 | """Check that highlighting style can be changed""" 39 | nb = self.build_notebook() 40 | res = self.build_resources() 41 | preprocessor = self.build_preprocessor() 42 | 43 | # Set the style to a known builtin that's not the default 44 | preprocessor.style = "colorful" 45 | nb, res = preprocessor(nb, res) 46 | style_defs = res["latex"]["pygments_definitions"] 47 | 48 | # Get the default 49 | from pygments.formatters import LatexFormatter # noqa: PLC0415 50 | 51 | default_defs = LatexFormatter(style="default").get_style_defs() 52 | 53 | # Verify that the style was in fact changed 54 | assert style_defs != default_defs 55 | -------------------------------------------------------------------------------- /nbconvert/filters/latex.py: -------------------------------------------------------------------------------- 1 | """Latex filters. 2 | 3 | Module of useful filters for processing Latex within Jinja latex templates. 4 | """ 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | import re 17 | 18 | # ----------------------------------------------------------------------------- 19 | # Globals and constants 20 | # ----------------------------------------------------------------------------- 21 | 22 | LATEX_RE_SUBS = ((re.compile(r"\.\.\.+"), r"{\\ldots}"),) 23 | 24 | # Latex substitutions for escaping latex. 25 | # see: http://stackoverflow.com/questions/16259923/how-can-i-escape-latex-special-characters-inside-django-templates 26 | 27 | LATEX_SUBS = { 28 | "&": r"\&", 29 | "%": r"\%", 30 | "$": r"\$", 31 | "#": r"\#", 32 | "_": r"\_", 33 | "{": r"\{", 34 | "}": r"\}", 35 | "~": r"\textasciitilde{}", 36 | "^": r"\^{}", 37 | "\\": r"\textbackslash{}", 38 | } 39 | 40 | 41 | # ----------------------------------------------------------------------------- 42 | # Functions 43 | # ----------------------------------------------------------------------------- 44 | 45 | __all__ = ["escape_latex"] 46 | 47 | 48 | def escape_latex(text): 49 | """ 50 | Escape characters that may conflict with latex. 51 | 52 | Parameters 53 | ---------- 54 | text : str 55 | Text containing characters that may conflict with Latex 56 | """ 57 | text = "".join(LATEX_SUBS.get(c, c) for c in text) 58 | for pattern, replacement in LATEX_RE_SUBS: 59 | text = pattern.sub(replacement, text) 60 | 61 | return text 62 | -------------------------------------------------------------------------------- /tests/preprocessors/test_clearoutput.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for the clearoutput preprocessor. 3 | """ 4 | 5 | # Copyright (c) IPython Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | 8 | from nbconvert.preprocessors.clearoutput import ClearOutputPreprocessor 9 | 10 | from .base import PreprocessorTestsBase 11 | 12 | 13 | class TestClearOutput(PreprocessorTestsBase): 14 | """Contains test functions for clearoutput.py""" 15 | 16 | def build_notebook(self): 17 | notebook = super().build_notebook() 18 | # Add a test field to the first cell 19 | if "metadata" not in notebook.cells[0]: 20 | notebook.cells[0].metadata = {} 21 | notebook.cells[0].metadata["test_field"] = "test_value" 22 | return notebook 23 | 24 | def build_preprocessor(self): 25 | """Make an instance of a preprocessor""" 26 | preprocessor = ClearOutputPreprocessor() 27 | preprocessor.enabled = True 28 | return preprocessor 29 | 30 | def test_constructor(self): 31 | """Can a ClearOutputPreprocessor be constructed?""" 32 | self.build_preprocessor() 33 | 34 | def test_output(self): 35 | """Test the output of the ClearOutputPreprocessor""" 36 | for remove_test_field in [False, True]: 37 | nb = self.build_notebook() 38 | res = self.build_resources() 39 | preprocessor = self.build_preprocessor() 40 | # Also remove the test field in addition to defaults 41 | if remove_test_field: 42 | preprocessor.remove_metadata_fields.add("test_field") 43 | nb, res = preprocessor(nb, res) 44 | assert nb.cells[0].outputs == [] 45 | assert nb.cells[0].execution_count is None 46 | if "metadata" in nb.cells[0]: 47 | for field in preprocessor.remove_metadata_fields: 48 | assert field not in nb.cells[0].metadata 49 | # Ensure the test field is only removed when added to the traitlet 50 | assert remove_test_field or "test_field" in nb.cells[0].metadata 51 | -------------------------------------------------------------------------------- /docs/source/highlighting.rst: -------------------------------------------------------------------------------- 1 | Customizing Syntax Highlighting 2 | =============================== 3 | 4 | Under the hood, nbconvert uses pygments to highlight code. pdf, webpdf and html exporting support 5 | changing the highlighting style. 6 | 7 | Using Builtin styles 8 | -------------------- 9 | Pygments has a number of builtin styles available. To use them, we just need to set the style setting 10 | in the relevant preprocessor. 11 | 12 | To change html and webpdf highlighting export with: 13 | 14 | .. code-block:: bash 15 | 16 | jupyter nbconvert --to html notebook.ipynb --CSSHTMLHeaderPreprocessor.style= 17 | 18 | To change pdf and latex highlighting export with: 19 | 20 | .. code-block:: bash 21 | 22 | jupyter nbconvert --to pdf notebook.ipynb --LatexPreprocessor.style= 23 | 24 | where ```` is the name of the pygments style. Available styles may vary from system to system. 25 | You can find all available styles with: 26 | 27 | .. code-block:: bash 28 | 29 | pygmentize -L styles 30 | 31 | from a terminal or 32 | 33 | .. code-block:: python 34 | 35 | from pygments.styles import get_all_styles 36 | 37 | print(list(get_all_styles())) 38 | 39 | from python. 40 | 41 | You can preview all the styles from an environment that can display html like jupyter notebook with: 42 | 43 | .. code-block:: python 44 | 45 | from pygments.styles import get_all_styles 46 | from pygments.formatters import Terminal256Formatter 47 | from pygments.lexers import PythonLexer 48 | from pygments import highlight 49 | 50 | code = """ 51 | import os 52 | def function(test=1): 53 | if test in [3,4]: 54 | print(test) 55 | """ 56 | for style in get_all_styles(): 57 | highlighted_code = highlight(code, PythonLexer(), Terminal256Formatter(style=style)) 58 | print(f"{style}:\n{highlighted_code}") 59 | 60 | Making your own styles 61 | ---------------------- 62 | To make your own style you must subclass ``pygments.styles.Style``, and then you must register your new style with Pygments using 63 | their plugin system. This is explained in detail in the `Pygments documentation `_. 64 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | environment: release 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - uses: actions/create-github-app-token@v2 25 | id: app-token 26 | with: 27 | app-id: ${{ vars.APP_ID }} 28 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 29 | 30 | - name: Populate Release 31 | id: populate-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 33 | with: 34 | token: ${{ steps.app-token.outputs.token }} 35 | target: ${{ github.event.inputs.target }} 36 | branch: ${{ github.event.inputs.branch }} 37 | release_url: ${{ github.event.inputs.release_url }} 38 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 39 | 40 | - name: Finalize Release 41 | id: finalize-release 42 | env: 43 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 45 | with: 46 | token: ${{ steps.app-token.outputs.token }} 47 | target: ${{ github.event.inputs.target }} 48 | release_url: ${{ steps.populate-release.outputs.release_url }} 49 | 50 | - name: "** Next Step **" 51 | if: ${{ success() }} 52 | run: | 53 | echo "Verify the final release" 54 | echo ${{ steps.finalize-release.outputs.release_url }} 55 | 56 | - name: "** Failure Message **" 57 | if: ${{ failure() }} 58 | run: | 59 | echo "Failed to Publish the Draft Release Url:" 60 | echo ${{ steps.populate-release.outputs.release_url }} 61 | -------------------------------------------------------------------------------- /share/templates/latex/style_bw_ipython.tex.j2: -------------------------------------------------------------------------------- 1 | ((= Black&white ipython input/output style =)) 2 | 3 | ((*- extends 'base.tex.j2' -*)) 4 | 5 | %=============================================================================== 6 | % Input 7 | %=============================================================================== 8 | 9 | ((* block input scoped *)) 10 | ((*- if resources.global_content_filter.include_input_prompt *)) 11 | ((( add_prompt(cell.source, cell, 'In ') ))) 12 | ((* else *)) 13 | (((cell.source))) 14 | ((* endif -*)) 15 | ((* endblock input *)) 16 | 17 | 18 | %=============================================================================== 19 | % Output 20 | %=============================================================================== 21 | 22 | ((* block execute_result scoped *)) 23 | ((*- for type in output.data | filter_data_type -*)) 24 | ((*- if resources.global_content_filter.include_output_prompt -*)) 25 | ((*- if type in ['text/plain'] *)) 26 | ((( add_prompt(output.data['text/plain'], cell, 'Out') ))) 27 | ((*- else -*)) 28 | \verb+Out[((( cell.execution_count )))]:+((( super() ))) 29 | ((*- endif -*)) 30 | ((*- else -*)) 31 | ((*- if type in ['text/plain'] *)) 32 | ((( output.data['text/plain'] ))) 33 | ((*- else -*)) 34 | \verb+((( super() ))) 35 | ((*- endif -*)) 36 | ((*- endif -*)) 37 | ((*- endfor -*)) 38 | ((* endblock execute_result *)) 39 | 40 | 41 | %============================================================================== 42 | % Support Macros 43 | %============================================================================== 44 | 45 | % Name: draw_prompt 46 | % Purpose: Renders an output/input prompt 47 | ((* macro add_prompt(text, cell, prompt) -*)) 48 | ((*- if cell.execution_count is defined -*)) 49 | ((*- set execution_count = "" ~ (cell.execution_count | replace(None, " ")) -*)) 50 | ((*- else -*)) 51 | ((*- set execution_count = " " -*)) 52 | ((*- endif -*)) 53 | ((*- set indentation = " " * (execution_count | length + 7) -*)) 54 | \begin{verbatim} 55 | (((- text | add_prompts(first=prompt ~ '[' ~ execution_count ~ ']: ', cont=indentation) -))) 56 | \end{verbatim} 57 | ((*- endmacro *)) 58 | -------------------------------------------------------------------------------- /tests/filters/test_pandoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for Pandoc filters 3 | """ 4 | 5 | # Copyright (c) Jupyter Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | 8 | import json 9 | 10 | from nbconvert.filters.pandoc import ConvertExplicitlyRelativePaths, convert_pandoc 11 | from tests.base import TestsBase 12 | from tests.testutils import onlyif_cmds_exist 13 | 14 | 15 | class TestPandocFilters(TestsBase): 16 | @onlyif_cmds_exist("pandoc") 17 | def test_convert_explicitly_relative_paths(self): 18 | """ 19 | Do the image links in a markdown file located in dir get processed correctly? 20 | """ 21 | inp_dir = "/home/user/src" 22 | fltr = ConvertExplicitlyRelativePaths(texinputs=inp_dir) 23 | 24 | # pairs of input, expected 25 | tests = { 26 | # TEXINPUTS is enough, abs_path not needed 27 | "im.png": "im.png", 28 | "./im.png": "im.png", 29 | "./images/im.png": "images/im.png", 30 | # TEXINPUTS is not enough, abs_path needed 31 | "../im.png": "/home/user/im.png", 32 | "../images/im.png": "/home/user/images/im.png", 33 | "../../images/im.png": "/home/images/im.png", 34 | } 35 | 36 | # this shouldn't be modified by the filter 37 | # since it is a code block inside markdown, 38 | # not an image link itself 39 | fake = """ 40 | ``` 41 | \\includegraphics{../fake.png} 42 | ``` 43 | """ 44 | 45 | # convert to markdown image 46 | def foo(filename): 47 | return f"![]({filename})" 48 | 49 | # create input markdown and convert to pandoc json 50 | inp = convert_pandoc(fake + "\n".join(map(foo, tests.keys())), "markdown", "json") 51 | expected = convert_pandoc(fake + "\n".join(map(foo, tests.values())), "markdown", "json") 52 | # Do this to fix string formatting 53 | expected = json.dumps(json.loads(expected)) 54 | self.assertEqual(expected, fltr(inp)) 55 | 56 | def test_convert_explicitly_relative_paths_no_texinputs(self): 57 | # no texinputs should lead to just returning 58 | fltr = ConvertExplicitlyRelativePaths(texinputs="") 59 | self.assertEqual("test", fltr("test")) 60 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================================= 2 | nbconvert: Convert Notebooks to other formats 3 | ============================================= 4 | 5 | Using ``nbconvert`` enables: 6 | 7 | - **presentation** of information in familiar formats, such as PDF. 8 | - **publishing** of research using LaTeX and opens the door for embedding 9 | notebooks in papers. 10 | - **collaboration** with others who may not use the notebook in their 11 | work. 12 | - **sharing** contents with many people via the web using HTML. 13 | 14 | Overall, notebook conversion and the ``nbconvert`` tool give scientists and 15 | researchers the flexibility to deliver information in a timely way across 16 | different formats. 17 | 18 | Primarily, the ``nbconvert`` tool allows you to convert a Jupyter ``.ipynb`` 19 | notebook document file into another static format including HTML, LaTeX, PDF, 20 | Markdown, reStructuredText, and more. ``nbconvert`` can also add productivity 21 | to your workflow when used to execute notebooks programmatically. 22 | 23 | If used as a Python library (``import nbconvert``), ``nbconvert`` adds 24 | notebook conversion within a project. For example, ``nbconvert`` is used to 25 | implement the "Download as" feature within the Jupyter Notebook web 26 | application. When used as a command line tool (invoked as 27 | ``jupyter nbconvert ...``), users can conveniently convert just one or a 28 | batch of notebook files to another format. 29 | 30 | 31 | **Contents:** 32 | 33 | .. toctree:: 34 | :maxdepth: 2 35 | :caption: User Documentation 36 | 37 | install 38 | usage 39 | nbconvert_library 40 | dejavu 41 | latex_citations 42 | removing_cells 43 | execute_api 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | :caption: Configuration 48 | 49 | config_options 50 | customizing 51 | external_exporters 52 | highlighting 53 | 54 | .. toctree:: 55 | :maxdepth: 2 56 | :caption: Developer Documentation 57 | 58 | architecture 59 | api/index 60 | development_release 61 | 62 | .. toctree:: 63 | :maxdepth: 2 64 | :caption: About nbconvert 65 | 66 | changelog 67 | 68 | .. toctree:: 69 | :maxdepth: 2 70 | :caption: Questions? Suggestions? 71 | 72 | need_help 73 | 74 | Indices and tables 75 | ================== 76 | 77 | * :ref:`genindex` 78 | * :ref:`modindex` 79 | * :ref:`search` 80 | -------------------------------------------------------------------------------- /share/templates/markdown/index.md.j2: -------------------------------------------------------------------------------- 1 | {% extends 'base/display_priority.j2' %} 2 | 3 | 4 | {% block in_prompt %} 5 | {% endblock in_prompt %} 6 | 7 | {% block output_prompt %} 8 | {%- endblock output_prompt %} 9 | 10 | {% block input %} 11 | ``` 12 | {%- if 'magics_language' in cell.metadata -%} 13 | {{ cell.metadata.magics_language}} 14 | {%- elif 'name' in nb.metadata.get('language_info', {}) -%} 15 | {{ nb.metadata.language_info.name }} 16 | {%- endif %} 17 | {{ cell.source}} 18 | ``` 19 | {% endblock input %} 20 | 21 | {% block error %} 22 | {{ super() }} 23 | {% endblock error %} 24 | 25 | {% block traceback_line %} 26 | {{ line | indent | strip_ansi }} 27 | {% endblock traceback_line %} 28 | 29 | {% block execute_result %} 30 | 31 | {% block data_priority scoped %} 32 | {{ super() }} 33 | {% endblock %} 34 | {% endblock execute_result %} 35 | 36 | {% block stream %} 37 | {{ output.text | indent }} 38 | {% endblock stream %} 39 | 40 | {% block data_svg %} 41 | {% if "filenames" in output.metadata %} 42 | ![svg]({{ output.metadata.filenames['image/svg+xml'] | path2url }}) 43 | {% else %} 44 | ![svg](data:image/svg;base64,{{ output.data['image/svg+xml'] }}) 45 | {% endif %} 46 | {% endblock data_svg %} 47 | 48 | {% block data_png %} 49 | {% if "filenames" in output.metadata %} 50 | ![png]({{ output.metadata.filenames['image/png'] | path2url }}) 51 | {% else %} 52 | ![png](data:image/png;base64,{{ output.data['image/png'] }}) 53 | {% endif %} 54 | {% endblock data_png %} 55 | 56 | {% block data_jpg %} 57 | {% if "filenames" in output.metadata %} 58 | ![jpeg]({{ output.metadata.filenames['image/jpeg'] | path2url }}) 59 | {% else %} 60 | ![jpeg](data:image/jpeg;base64,{{ output.data['image/jpeg'] }}) 61 | {% endif %} 62 | {% endblock data_jpg %} 63 | 64 | {% block data_latex %} 65 | {{ output.data['text/latex'] }} 66 | {% endblock data_latex %} 67 | 68 | {% block data_html scoped %} 69 | {{ output.data['text/html'] }} 70 | {% endblock data_html %} 71 | 72 | {% block data_markdown scoped %} 73 | {{ output.data['text/markdown'] }} 74 | {% endblock data_markdown %} 75 | 76 | {% block data_text scoped %} 77 | {{ output.data['text/plain'] | indent }} 78 | {% endblock data_text %} 79 | 80 | {% block markdowncell scoped %} 81 | {{ cell.source }} 82 | {% endblock markdowncell %} 83 | 84 | {% block unknowncell scoped %} 85 | unknown type {{ cell.type }} 86 | {% endblock unknowncell %} 87 | -------------------------------------------------------------------------------- /nbconvert/exporters/qt_exporter.py: -------------------------------------------------------------------------------- 1 | """A qt exporter.""" 2 | 3 | import os 4 | import sys 5 | import tempfile 6 | 7 | from traitlets import default 8 | 9 | from .html import HTMLExporter 10 | 11 | 12 | class QtExporter(HTMLExporter): 13 | """A qt exporter.""" 14 | 15 | paginate = None 16 | format = "" 17 | 18 | @default("file_extension") 19 | def _file_extension_default(self): 20 | return ".html" 21 | 22 | def _check_launch_reqs(self): 23 | if sys.platform.startswith("win") and self.format == "png": 24 | msg = "Exporting to PNG using Qt is currently not supported on Windows." 25 | raise RuntimeError(msg) 26 | from .qt_screenshot import QT_INSTALLED # noqa: PLC0415 27 | 28 | if not QT_INSTALLED: 29 | msg = ( 30 | f"PyQtWebEngine is not installed to support Qt {self.format.upper()} conversion. " 31 | f"Please install `nbconvert[qt{self.format}]` to enable." 32 | ) 33 | raise RuntimeError(msg) 34 | from .qt_screenshot import QtScreenshot # noqa: PLC0415 35 | 36 | return QtScreenshot 37 | 38 | def _run_pyqtwebengine(self, html): 39 | ext = ".html" 40 | temp_file = tempfile.NamedTemporaryFile( # noqa: SIM115 41 | suffix=ext, delete=False 42 | ) 43 | filename = f"{temp_file.name[: -len(ext)]}.{self.format}" 44 | with temp_file: 45 | temp_file.write(html.encode("utf-8")) 46 | try: 47 | QtScreenshot = self._check_launch_reqs() 48 | s = QtScreenshot() 49 | s.capture(f"file://{temp_file.name}", filename, self.paginate) 50 | finally: 51 | # Ensure the file is deleted even if pyqtwebengine raises an exception 52 | os.unlink(temp_file.name) 53 | return s.data 54 | 55 | def from_notebook_node(self, nb, resources=None, **kw): 56 | """Convert from notebook node.""" 57 | self._check_launch_reqs() 58 | html, resources = super().from_notebook_node(nb, resources=resources, **kw) 59 | 60 | self.log.info("Building %s", self.format.upper()) 61 | data = self._run_pyqtwebengine(html) 62 | self.log.info("%s successfully created", self.format.upper()) 63 | 64 | # convert output extension 65 | # the writer above required it to be html 66 | resources["output_extension"] = f".{self.format}" 67 | 68 | return data, resources 69 | -------------------------------------------------------------------------------- /tests/exporters/test_webpdf.py: -------------------------------------------------------------------------------- 1 | """Tests for the latex preprocessor""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import builtins 7 | from unittest.mock import patch 8 | 9 | import pytest 10 | 11 | from nbconvert.exporters.exporter import Exporter 12 | from nbconvert.exporters.webpdf import PLAYWRIGHT_INSTALLED, WebPDFExporter 13 | 14 | from .base import ExportersTestsBase 15 | 16 | real_import = builtins.__import__ 17 | 18 | 19 | class FakeBrowser: 20 | executable_path: str = "" 21 | 22 | 23 | def monkey_import_notfound(name, globals_ctx=None, locals_ctx=None, fromlist=(), level=0): 24 | if name == "playwright.async_api": 25 | msg = "Fake missing" 26 | raise ModuleNotFoundError(msg) 27 | return real_import(name, globals=globals_ctx, locals=locals_ctx, fromlist=fromlist, level=level) 28 | 29 | 30 | @pytest.mark.skipif(not PLAYWRIGHT_INSTALLED, reason="Playwright not installed") 31 | class TestWebPDFExporter(ExportersTestsBase): 32 | """Contains test functions for webpdf.py""" 33 | 34 | exporter_class = WebPDFExporter # type:ignore 35 | 36 | @pytest.mark.network 37 | def test_export(self): 38 | """ 39 | Can a TemplateExporter export something? 40 | """ 41 | output, _resources = WebPDFExporter(allow_chromium_download=True).from_filename( 42 | self._get_notebook() 43 | ) 44 | assert len(output) > 0 45 | 46 | @patch("playwright.async_api._generated.Playwright.chromium", return_value=FakeBrowser()) 47 | def test_webpdf_without_chromium(self, mock_chromium): 48 | """ 49 | Generate PDFs if chromium not present? 50 | """ 51 | with pytest.raises(RuntimeError): 52 | WebPDFExporter(allow_chromium_download=False).from_filename(self._get_notebook()) 53 | 54 | @patch("builtins.__import__", monkey_import_notfound) 55 | def test_webpdf_without_playwright(self): 56 | """ 57 | Generate PDFs if playwright not installed? 58 | """ 59 | with pytest.raises(RuntimeError): # noqa 60 | base_exporter = Exporter() 61 | exporter = WebPDFExporter() 62 | with open(self._get_notebook(), encoding="utf-8") as f: 63 | nb = base_exporter.from_file(f, resources={})[0] 64 | # Have to do this as the very last action as traitlets do dynamic importing often 65 | exporter.from_notebook_node(nb) 66 | -------------------------------------------------------------------------------- /docs/autogen_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | autogen_config.py 3 | 4 | Create config_options.rst, a Sphinx documentation source file. 5 | Documents the options that may be set in nbconvert's configuration file, 6 | jupyter_nbconvert_config.py. 7 | 8 | """ 9 | 10 | import os.path 11 | 12 | from nbconvert.nbconvertapp import NbConvertApp 13 | 14 | header = """\ 15 | 16 | .. This is an automatically generated file. 17 | .. do not modify by hand. 18 | 19 | Configuration options 20 | ===================== 21 | 22 | Configuration options may be set in a file, ``~/.jupyter/jupyter_nbconvert_config.py``, 23 | or at the command line when starting nbconvert, i.e. ``jupyter nbconvert --Application.log_level=10``. 24 | 25 | The most specific setting will always be used. For example, the LatexExporter 26 | and the HTMLExporter both inherit from TemplateExporter. With the following config 27 | 28 | .. code-block:: python 29 | 30 | c.TemplateExporter.exclude_input_prompt = False # The default 31 | c.PDFExporter.exclude_input_prompt = True 32 | 33 | input prompts will not appear when converting to PDF, but they will appear when 34 | exporting to HTML. 35 | 36 | CLI Flags and Aliases 37 | --------------------- 38 | 39 | The dynamic loading of exporters can be disabled by setting the environment 40 | variable ``NBCONVERT_DISABLE_CONFIG_EXPORTERS``. This causes all exporters 41 | to be loaded regardless of the value of their ``enabled`` attribute. 42 | 43 | When using Nbconvert from the command line, a number of aliases and flags are 44 | defined as shortcuts to configuration options for convenience. 45 | 46 | """ 47 | 48 | try: 49 | indir = os.path.dirname(__file__) 50 | except NameError: 51 | indir = os.path.dirname(os.getcwd()) 52 | destination = os.path.join(indir, "source/config_options.rst") 53 | 54 | with open(destination, "w") as f: 55 | app = NbConvertApp() 56 | f.write(header) 57 | f.write(app.document_flag_help()) 58 | f.write(app.document_alias_help()) 59 | f.write(app.document_config_options()) 60 | 61 | 62 | # Workaround until https://github.com/jupyter/nbclient/pull/216 is released 63 | with open(destination) as f: 64 | data = f.read() 65 | 66 | data = data.replace("`CellExecutionError`", "``CellExecutionError``") 67 | data = data.replace("`cell`", "``cell``") 68 | data = data.replace("`cell_index`", "``cell_index``") 69 | data = data.replace("`cell_allows_errors`", "``cell_allows_errors``") 70 | data = data.replace("`notebook`", "``notebook``") 71 | 72 | with open(destination, "w") as f: 73 | f.write(data) 74 | -------------------------------------------------------------------------------- /docs/api_examples/template_path/quiz_notebook.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # cell_metadata_filter: all 5 | # formats: ipynb,py:percent 6 | # notebook_metadata_filter: all,-language_info,-toc,-latex_envs 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # jupytext_version: 1.3.2 12 | # kernelspec: 13 | # display_name: Python 3 14 | # language: python 15 | # name: python3 16 | # latex_metadata: 17 | # chead: Quiz questions, January 18, 2020 18 | # lhead: E340 Day 5 19 | # pdf_metadata: 20 | # text1: html header success 21 | # --- 22 | 23 | # %% [markdown] ctype="question" qnum="1" 24 | # 1. Suppose we have a one layer atmosphere with ε=1 above a black 25 | # surface as in Dessler Fig. 4.6. Does adding a second opaque (ε=1) 26 | # layer (Dessler Figure 4.7) increase the greenhouse effect, decrease 27 | # the greenhouse effect, or leave the greenhouse effect unchanged? 28 | # 29 | # A. increase 30 | # 31 | # B. decrease 32 | # 33 | # C. no change 34 | 35 | # %% [markdown] ctype="question" qnum="2" 36 | # 2. Suppose a planet's average surface temperature is about 320 K. In the one-layer 37 | # semi-transparent atmosphere shown below (and seen in your reading), 38 | # what would the approximate long-wave atmospheric emissivity ε need to be in 39 | # order for $T_g$=320 K, if the average shorwave flux was $I_0=400\ W\,m^{-2}$. (Hint, look at equations 5-6 in the reading). 40 | # 41 | # A. 0.54 42 | # 43 | # B. 0.65 44 | # 45 | # C. 0.77 46 | # 47 | # D. 0.88 48 | # 49 | # E. 0.95 50 | 51 | # %% [markdown] ctype="question" qnum="2" 52 | # ![fig1](media/image1.png) 53 | 54 | # %% [markdown] ctype="question" qnum="3" 55 | # 3. Given a one-layer atmosphere like problem 2, but with $\epsilon=0.8$, $T_G$=306 K, $T_1$=258 K, $I_0$ = 300 $W\,m^{-2}$, calculate the greenouse effect. (choose the closest number) 56 | # 57 | # A. -121 $W\,m^2$ 58 | # 59 | # B. +121 $W\,m^2$ 60 | # 61 | # C. +197 $W\,m^2$ 62 | # 63 | # D. +221 $W\,m^2$ 64 | # 65 | # E. +324 $W\,m^2$ 66 | 67 | # %% [markdown] ctype="question" qnum="4" 68 | # 4. According to the figure in Trenberth et al. 2009 below, how big is the Earth's greenhouse effect? 69 | # 70 | # A. 85 $W\,m^{-2}$ 71 | # 72 | # B. 157 $W\,m^{-2}$ 73 | # 74 | # C. 254 $W\,m^{-2}$ 75 | # 76 | # D. 285 $W\,m^{-2}$ 77 | # 78 | # E. 333 $W\,m^{-2}$ 79 | 80 | # %% [markdown] ctype="question" qnum="3" 81 | # ![fig2](media/image2.png) 82 | -------------------------------------------------------------------------------- /tests/exporters/test_pdf.py: -------------------------------------------------------------------------------- 1 | """Tests for PDF export""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import os 7 | import shutil 8 | from tempfile import TemporaryDirectory 9 | 10 | from nbconvert.exporters.pdf import PDFExporter 11 | from nbconvert.utils import _contextlib_chdir 12 | from tests.testutils import onlyif_cmds_exist 13 | 14 | from .base import ExportersTestsBase 15 | 16 | # ----------------------------------------------------------------------------- 17 | # Class 18 | # ----------------------------------------------------------------------------- 19 | 20 | 21 | class TestPDF(ExportersTestsBase): 22 | """Test PDF export""" 23 | 24 | exporter_class = PDFExporter # type:ignore 25 | 26 | def test_constructor(self): 27 | """Can a PDFExporter be constructed?""" 28 | self.exporter_class() # type:ignore 29 | 30 | @onlyif_cmds_exist("xelatex", "pandoc") 31 | def test_export(self): 32 | """Smoke test PDFExporter""" 33 | with TemporaryDirectory() as td: 34 | file_name = os.path.basename(self._get_notebook()) 35 | newpath = os.path.join(td, file_name) 36 | shutil.copy(self._get_notebook(), newpath) 37 | (output, _resources) = self.exporter_class(latex_count=1).from_filename( # type:ignore 38 | newpath 39 | ) 40 | self.assertIsInstance(output, bytes) 41 | assert len(output) > 0 42 | # all temporary file should be cleaned up 43 | assert {file_name} == set(os.listdir(td)) 44 | 45 | @onlyif_cmds_exist("xelatex", "pandoc") 46 | def test_texinputs(self): 47 | """ 48 | Is TEXINPUTS set properly when we are converting 49 | - in the same directory, and 50 | - in a different directory? 51 | """ 52 | with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): 53 | os.mkdir("folder") 54 | file_name = os.path.basename(self._get_notebook()) 55 | nb1 = os.path.join(td, file_name) 56 | nb2 = os.path.join(td, "folder", file_name) 57 | ex1 = self.exporter_class(latex_count=1) # type:ignore 58 | ex2 = self.exporter_class(latex_count=1) # type:ignore 59 | shutil.copy(self._get_notebook(), nb1) 60 | shutil.copy(self._get_notebook(), nb2) 61 | _ = ex1.from_filename(nb1) 62 | _ = ex2.from_filename(nb2) 63 | assert ex1.texinputs == os.path.abspath(".") 64 | assert ex2.texinputs == os.path.abspath("./folder") 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We follow the 4 | [Jupyter Contribution Workflow](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html) 5 | and the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md). 6 | 7 | ## Testing 8 | 9 | In order to test all the features of nbconvert you need to have `pandoc` and 10 | `TexLive` installed. 11 | 12 | In your environment `pip install -e '.[all]'` will be needed to be able to 13 | run all of the tests and to test all of the features. 14 | 15 | If you only want to run some of the tests run `pip install -e '.[test]'`. 16 | 17 | ## Documentation 18 | 19 | NbConvert includes a substantial amount of both user and API documentation. 20 | 21 | We use sphinx to build the API documentation. 22 | 23 | Much of the user documentation is written in Jupyter Notebooks and converted on the fly with nbsphinx. 24 | 25 | To build nbconvert's documentation you need to have `pandoc` and 26 | `TexLive` installed. 27 | 28 | If you want to build the docs you will need to install the docs dependencies in addition to 29 | the standard dependencies. You can get all of the dependencies by running `pip install -e '.[all]'` and if you want only those needed to run the docs you can access them with `pip install -e '.[docs]'`. 30 | 31 | Full build instructions can be found at [docs/README.md](docs/README.md). 32 | 33 | ## Code Styling 34 | 35 | `nbconvert` has adopted automatic code formatting so you shouldn't 36 | need to worry too much about your code style. 37 | As long as your code is valid, 38 | the pre-commit hook should take care of how it should look. 39 | `pre-commit` and its associated hooks will automatically be installed when 40 | you run `pip install -e ".[test]"` 41 | 42 | To install `pre-commit` manually, run the following: 43 | 44 | ```bash 45 | pip install pre-commit 46 | pre-commit install 47 | ``` 48 | 49 | You can invoke the pre-commit hook by hand at any time with: 50 | 51 | ```bash 52 | pre-commit run 53 | ``` 54 | 55 | which should run any autoformatting on your code 56 | and tell you about any errors it couldn't fix automatically. 57 | You may also install [black integration](https://github.com/psf/black#editor-integration) 58 | into your text editor to format code automatically. 59 | 60 | If you have already committed files before setting up the pre-commit 61 | hook with `pre-commit install`, you can fix everything up using 62 | `pre-commit run --all-files`. You need to make the fixing commit 63 | yourself after that. 64 | 65 | Some of the hooks only run on CI by default, but you can invoke them by 66 | running with the `--hook-stage manual` argument. 67 | -------------------------------------------------------------------------------- /tests/exporters/test_script.py: -------------------------------------------------------------------------------- 1 | """Tests for ScriptExporter""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import os 7 | import sys 8 | 9 | from nbformat import v4 10 | 11 | import tests 12 | from nbconvert.exporters.script import ScriptExporter 13 | 14 | from .base import ExportersTestsBase 15 | 16 | 17 | class TestScriptExporter(ExportersTestsBase): 18 | """Tests for ScriptExporter""" 19 | 20 | exporter_class = ScriptExporter # type:ignore 21 | 22 | def test_constructor(self): 23 | """Construct ScriptExporter""" 24 | self.exporter_class() # type:ignore 25 | 26 | def test_export(self): 27 | """ScriptExporter can export something""" 28 | (output, _resources) = self.exporter_class().from_filename( # type:ignore 29 | self._get_notebook() 30 | ) 31 | assert len(output) > 0 32 | 33 | def test_export_python(self): 34 | """delegate to custom exporter from language_info""" 35 | self.exporter_class() # type:ignore 36 | 37 | pynb = v4.new_notebook() 38 | (output, _resources) = self.exporter_class().from_notebook_node(pynb) # type:ignore 39 | self.assertNotIn("# coding: utf-8", output) 40 | 41 | pynb.metadata.language_info = { 42 | "name": "python", 43 | "mimetype": "text/x-python", 44 | "nbconvert_exporter": "python", 45 | } 46 | (output, _resources) = self.exporter_class().from_notebook_node(pynb) # type:ignore 47 | self.assertIn("# coding: utf-8", output) 48 | 49 | def test_export_config_transfer(self): 50 | """delegate config to custom exporter from language_info""" 51 | nb = v4.new_notebook() 52 | nb.metadata.language_info = { 53 | "name": "python", 54 | "mimetype": "text/x-python", 55 | "nbconvert_exporter": "python", 56 | } 57 | 58 | exporter = self.exporter_class() # type:ignore 59 | exporter.from_notebook_node(nb) 60 | assert exporter._exporters["python"] != exporter 61 | assert exporter._exporters["python"].config == exporter.config 62 | 63 | 64 | def test_script_exporter_entrypoint(): 65 | nb = v4.new_notebook() 66 | nb.metadata.language_info = { 67 | "name": "dummy", 68 | "mimetype": "text/x-dummy", 69 | } 70 | 71 | p = os.path.join(os.path.dirname(tests.__file__), "exporter_entrypoint") 72 | sys.path.insert(0, p) 73 | try: 74 | output, _ = ScriptExporter().from_notebook_node(nb) 75 | assert output == "dummy-script-exported" 76 | finally: 77 | sys.path.remove(p) 78 | -------------------------------------------------------------------------------- /share/templates/asciidoc/index.asciidoc.j2: -------------------------------------------------------------------------------- 1 | {% extends 'display_priority.j2' %} 2 | 3 | 4 | {% block input %} 5 | {% if resources.global_content_filter.include_input_prompt %} 6 | {% if cell.execution_count is defined -%} 7 | +*In[{{ cell.execution_count|replace(None, " ") }}]:*+ 8 | {% else %} 9 | +*In[]:*+ 10 | {%- endif -%} 11 | {%- endif -%} 12 | [source 13 | {%- if 'magics_language' in cell.metadata -%} 14 | , {{ cell.metadata.magics_language}} 15 | {%- elif 'pygments_lexer' in nb.metadata.get('language_info', {}) -%} 16 | , {{ nb.metadata.language_info.pygments_lexer }} 17 | {%- elif 'name' in nb.metadata.get('language_info', {}) -%} 18 | , {{ nb.metadata.language_info.name }} 19 | {%- endif -%}] 20 | ---- 21 | {{ cell.source}} 22 | ---- 23 | {% endblock input %} 24 | 25 | {% block output_group %} 26 | {% if resources.global_content_filter.include_output_prompt %} 27 | {% if cell.execution_count is defined -%} 28 | +*Out[{{ cell.execution_count|replace(None, " ") }}]:*+ 29 | {%- else -%} 30 | +*Out[]:*+ 31 | {%- endif -%} 32 | {%- endif %} 33 | ---- 34 | {{- super() -}} 35 | ---- 36 | {% endblock output_group %} 37 | 38 | {% block error %} 39 | {{ super() }} 40 | {% endblock error %} 41 | 42 | {% block traceback_line %} 43 | {{ line | indent | strip_ansi }} 44 | {% endblock traceback_line %} 45 | 46 | {%- block execute_result %} 47 | {%- block data_priority scoped %} 48 | {{- super() -}} 49 | {%- endblock %} 50 | {%- endblock execute_result %} 51 | 52 | {% block stream %} 53 | {{ output.text -}} 54 | {% endblock stream %} 55 | 56 | {% block data_svg %} 57 | ![svg]({{ output.metadata.filenames['image/svg+xml'] | path2url }}) 58 | {% endblock data_svg %} 59 | 60 | {% block data_png %} 61 | ![png]({{ output.metadata.filenames['image/png'] | path2url }}) 62 | {% endblock data_png %} 63 | 64 | {% block data_jpg %} 65 | ![jpeg]({{ output.metadata.filenames['image/jpeg'] | path2url }}) 66 | {% endblock data_jpg %} 67 | 68 | {% block data_latex %} 69 | {{ output.data['text/latex'] | convert_pandoc(from_format="latex", to_format="asciidoc")}} 70 | {% endblock data_latex %} 71 | 72 | {% block data_html scoped %} 73 | {{ output.data['text/html'] | convert_pandoc(from_format='html', to_format='asciidoc')}} 74 | {% endblock data_html %} 75 | 76 | {% block data_markdown scoped %} 77 | {{ output.data['text/markdown'] | markdown2asciidoc}} 78 | {% endblock data_markdown %} 79 | 80 | {% block data_text scoped %} 81 | {{-output.data['text/plain']-}} 82 | {% endblock data_text %} 83 | 84 | {% block markdowncell scoped %} 85 | {{ cell.source | markdown2asciidoc}} 86 | {% endblock markdowncell %} 87 | 88 | {% block unknowncell scoped %} 89 | unknown type {{ cell.type }} 90 | {% endblock unknowncell %} 91 | -------------------------------------------------------------------------------- /tests/exporters/test_asciidoc.py: -------------------------------------------------------------------------------- 1 | """Tests for ASCIIDocExporter`""" 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (c) 2016, the IPython Development Team. 5 | # 6 | # Distributed under the terms of the Modified BSD License. 7 | # 8 | # The full license is in the file COPYING.txt, distributed with this software. 9 | # ----------------------------------------------------------------------------- 10 | 11 | # ----------------------------------------------------------------------------- 12 | # Imports 13 | # ----------------------------------------------------------------------------- 14 | 15 | import re 16 | 17 | from traitlets.config import Config 18 | 19 | from nbconvert.exporters.asciidoc import ASCIIDocExporter 20 | from tests.testutils import onlyif_cmds_exist 21 | 22 | from .base import ExportersTestsBase 23 | 24 | # ----------------------------------------------------------------------------- 25 | # Class 26 | # ----------------------------------------------------------------------------- 27 | in_regex = r"In\[(.*)\]:" 28 | out_regex = r"Out\[(.*)\]:" 29 | 30 | 31 | class TestASCIIDocExporter(ExportersTestsBase): 32 | """Tests for ASCIIDocExporter""" 33 | 34 | exporter_class = ASCIIDocExporter # type:ignore 35 | 36 | def test_constructor(self): 37 | """ 38 | Can a ASCIIDocExporter be constructed? 39 | """ 40 | ASCIIDocExporter() 41 | 42 | @onlyif_cmds_exist("pandoc") 43 | def test_export(self): 44 | """ 45 | Can a ASCIIDocExporter export something? 46 | """ 47 | (output, _resources) = ASCIIDocExporter().from_filename(self._get_notebook()) 48 | assert len(output) > 0 49 | 50 | assert re.findall(in_regex, output) 51 | assert re.findall(out_regex, output) 52 | 53 | # Assert that the Markdown header from our test notebook made it into the output. 54 | # This can fail when nbconvert invokes pandoc incorrectly, as in issue #2017. 55 | assert "== NumPy and Matplotlib examples" in output 56 | 57 | @onlyif_cmds_exist("pandoc") 58 | def test_export_no_prompt(self): 59 | """ 60 | Can a ASCIIDocExporter export something without prompts? 61 | """ 62 | no_prompt = { 63 | "TemplateExporter": { 64 | "exclude_input_prompt": True, 65 | "exclude_output_prompt": True, 66 | } 67 | } 68 | c_no_prompt = Config(no_prompt) 69 | exporter = ASCIIDocExporter(config=c_no_prompt) 70 | (output, _resources) = exporter.from_filename( 71 | self._get_notebook(nb_name="prompt_numbers.ipynb") 72 | ) 73 | 74 | assert not re.findall(in_regex, output) 75 | assert not re.findall(out_regex, output) 76 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/regexremove.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module containing a preprocessor that removes cells if they match 3 | one or more regular expression. 4 | """ 5 | 6 | # Copyright (c) IPython Development Team. 7 | # Distributed under the terms of the Modified BSD License. 8 | from __future__ import annotations 9 | 10 | import re 11 | 12 | from traitlets import List, Unicode 13 | 14 | from .base import Preprocessor 15 | 16 | 17 | class RegexRemovePreprocessor(Preprocessor): 18 | """ 19 | Removes cells from a notebook that match one or more regular expression. 20 | 21 | For each cell, the preprocessor checks whether its contents match 22 | the regular expressions in the ``patterns`` traitlet which is a list 23 | of unicode strings. If the contents match any of the patterns, the cell 24 | is removed from the notebook. 25 | 26 | To modify the list of matched patterns, 27 | modify the patterns traitlet. For example, execute the following command 28 | to convert a notebook to html and remove cells containing only whitespace:: 29 | 30 | jupyter nbconvert --RegexRemovePreprocessor.patterns="['\\s*\\Z']" mynotebook.ipynb 31 | 32 | The command line argument 33 | sets the list of patterns to ``'\\s*\\Z'`` which matches an arbitrary number 34 | of whitespace characters followed by the end of the string. 35 | 36 | See https://regex101.com/ for an interactive guide to regular expressions 37 | (make sure to select the python flavor). See 38 | https://docs.python.org/library/re.html for the official regular expression 39 | documentation in python. 40 | """ 41 | 42 | patterns = List(Unicode()).tag(config=True) 43 | 44 | def check_conditions(self, cell): 45 | """ 46 | Checks that a cell matches the pattern. 47 | 48 | Returns: Boolean. 49 | True means cell should *not* be removed. 50 | """ 51 | 52 | # Compile all the patterns into one: each pattern is first wrapped 53 | # by a non-capturing group to ensure the correct order of precedence 54 | # and the patterns are joined with a logical or 55 | pattern = re.compile("|".join("(?:%s)" % pattern for pattern in self.patterns)) 56 | 57 | # Filter out cells that meet the pattern and have no outputs 58 | return not pattern.match(cell.source) 59 | 60 | def preprocess(self, nb, resources): 61 | """ 62 | Preprocessing to apply to each notebook. See base.py for details. 63 | """ 64 | # Skip preprocessing if the list of patterns is empty 65 | if not self.patterns: 66 | return nb, resources 67 | 68 | # Filter out cells that meet the conditions 69 | nb.cells = [cell for cell in nb.cells if self.check_conditions(cell)] 70 | 71 | return nb, resources 72 | -------------------------------------------------------------------------------- /tests/preprocessors/test_regexremove.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for the RegexRemovePreprocessor. 3 | """ 4 | 5 | # Copyright (c) IPython Development Team. 6 | # Distributed under the terms of the Modified BSD License. 7 | 8 | import re 9 | 10 | from nbformat import v4 as nbformat 11 | 12 | from nbconvert.preprocessors.regexremove import RegexRemovePreprocessor 13 | 14 | from .base import PreprocessorTestsBase 15 | 16 | 17 | class TestRegexRemove(PreprocessorTestsBase): 18 | """Contains test functions for regexremove.py""" 19 | 20 | def build_notebook(self): 21 | notebook = super().build_notebook() 22 | # Add a few empty cells 23 | notebook.cells.extend( 24 | [ 25 | nbformat.new_code_cell(""), 26 | nbformat.new_markdown_cell(" "), 27 | nbformat.new_raw_cell("\n"), 28 | nbformat.new_raw_cell("\t"), 29 | ] 30 | ) 31 | 32 | return notebook 33 | 34 | def build_preprocessor(self): 35 | """Make an instance of a preprocessor""" 36 | preprocessor = RegexRemovePreprocessor() 37 | preprocessor.enabled = True 38 | return preprocessor 39 | 40 | def test_constructor(self): 41 | """Can a RegexRemovePreprocessor be constructed?""" 42 | self.build_preprocessor() 43 | 44 | def test_output(self): 45 | """Test the output of the RegexRemovePreprocessor""" 46 | pattern_lookup = { 47 | "disallow_whitespace": [r"\s*\Z"], 48 | "disallow_tab_newline": [r"\t\Z", r"\n\Z"], 49 | } 50 | expected_cell_count = { 51 | "default": 6, # nothing is removed 52 | "disallow_whitespace": 2, # all "empty" cells are removed 53 | "disallow_tab_newline": 4, # cells with tab and newline are removed 54 | "none": 6, 55 | } 56 | for method in ["default", "disallow_whitespace", "disallow_tab_newline", "none"]: 57 | nb = self.build_notebook() 58 | res = self.build_resources() 59 | 60 | # Build the preprocessor and extend the list of patterns or use an empty list 61 | preprocessor = self.build_preprocessor() 62 | if method == "none": 63 | preprocessor.patterns = [] 64 | else: 65 | preprocessor.patterns.extend(pattern_lookup.get(method, [])) 66 | nb, res = preprocessor(nb, res) 67 | 68 | self.assertEqual(len(nb.cells), expected_cell_count[method]) 69 | 70 | # Make sure none of the cells match the pattern 71 | patterns = list(map(re.compile, preprocessor.patterns)) 72 | for cell in nb.cells: 73 | for pattern in patterns: 74 | self.assertFalse(pattern.match(cell.source)) 75 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: monthly 3 | autoupdate_commit_msg: "chore: update pre-commit hooks" 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v6.0.0 8 | hooks: 9 | - id: check-case-conflict 10 | - id: check-ast 11 | - id: check-docstring-first 12 | - id: check-executables-have-shebangs 13 | - id: check-added-large-files 14 | - id: check-case-conflict 15 | - id: check-merge-conflict 16 | - id: check-json 17 | - id: check-toml 18 | - id: check-yaml 19 | - id: debug-statements 20 | - id: end-of-file-fixer 21 | - id: trailing-whitespace 22 | 23 | - repo: https://github.com/python-jsonschema/check-jsonschema 24 | rev: 0.34.1 25 | hooks: 26 | - id: check-github-workflows 27 | 28 | - repo: https://github.com/hukkin/mdformat 29 | rev: 1.0.0 30 | hooks: 31 | - id: mdformat 32 | 33 | - repo: https://github.com/pre-commit/mirrors-prettier 34 | rev: "v4.0.0-alpha.8" 35 | hooks: 36 | - id: prettier 37 | types_or: [yaml, html, json] 38 | 39 | - repo: https://github.com/adamchainz/blacken-docs 40 | rev: "1.20.0" 41 | hooks: 42 | - id: blacken-docs 43 | additional_dependencies: [black==23.7.0] 44 | 45 | - repo: https://github.com/codespell-project/codespell 46 | rev: "v2.4.1" 47 | hooks: 48 | - id: codespell 49 | args: ["-L", "sur,nd,assertin"] 50 | 51 | - repo: https://github.com/pre-commit/mirrors-mypy 52 | rev: "v1.18.2" 53 | hooks: 54 | - id: mypy 55 | files: "^nbconvert" 56 | stages: [manual] 57 | args: ["--install-types", "--non-interactive"] 58 | additional_dependencies: 59 | [ 60 | "traitlets>=5.13", 61 | "jupyter_core>=5.3", 62 | "jinja2", 63 | "nbformat", 64 | "markupsafe", 65 | "mistune", 66 | "nbclient>=0.9", 67 | "defusedxml", 68 | "ipython", 69 | "packaging", 70 | "pandocfilters", 71 | "jupyterlab_pygments", 72 | ] 73 | 74 | - repo: https://github.com/pre-commit/pygrep-hooks 75 | rev: "v1.10.0" 76 | hooks: 77 | - id: rst-backticks 78 | - id: rst-directive-colons 79 | - id: rst-inline-touching-normal 80 | 81 | - repo: https://github.com/astral-sh/ruff-pre-commit 82 | rev: v0.14.0 83 | hooks: 84 | - id: ruff-check 85 | types_or: [python, jupyter] 86 | args: ["--fix", "--show-fixes"] 87 | - id: ruff-format 88 | types_or: [python, jupyter] 89 | 90 | - repo: https://github.com/scientific-python/cookie 91 | rev: "2025.10.01" 92 | hooks: 93 | - id: sp-repo-review 94 | additional_dependencies: ["repo-review[cli]"] 95 | -------------------------------------------------------------------------------- /tests/exporters/test_rst.py: -------------------------------------------------------------------------------- 1 | """Tests for RSTExporter""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import re 7 | 8 | import nbformat 9 | from nbformat import v4 10 | 11 | from nbconvert.exporters.rst import RSTExporter 12 | from tests.testutils import onlyif_cmds_exist 13 | 14 | from .base import ExportersTestsBase 15 | 16 | 17 | class TestRSTExporter(ExportersTestsBase): 18 | """Tests for RSTExporter""" 19 | 20 | exporter_class = RSTExporter # type:ignore 21 | should_include_raw = ["rst"] # type:ignore 22 | 23 | def test_constructor(self): 24 | """ 25 | Can a RSTExporter be constructed? 26 | """ 27 | RSTExporter() 28 | 29 | @onlyif_cmds_exist("pandoc") 30 | def test_export(self): 31 | """ 32 | Can a RSTExporter export something? 33 | """ 34 | (output, _resources) = RSTExporter().from_filename(self._get_notebook()) 35 | assert len(output) > 0 36 | 37 | @onlyif_cmds_exist("pandoc") 38 | def test_empty_code_cell(self): 39 | """No empty code cells in rst""" 40 | nbname = self._get_notebook() 41 | with open(nbname, encoding="utf8") as f: 42 | nb = nbformat.read(f, 4) 43 | 44 | nb = v4.upgrade(nb) 45 | exporter = self.exporter_class() # type:ignore 46 | 47 | (output, _resources) = exporter.from_notebook_node(nb) 48 | # add an empty code cell 49 | nb.cells.append(v4.new_code_cell(source="")) 50 | 51 | (output2, _resources) = exporter.from_notebook_node(nb) 52 | # adding an empty code cell shouldn't change output 53 | self.assertEqual(output.strip(), output2.strip()) 54 | 55 | @onlyif_cmds_exist("pandoc") 56 | def test_png_metadata(self): 57 | """ 58 | Does RSTExporter treat pngs with width/height metadata correctly? 59 | """ 60 | (output, _resources) = RSTExporter().from_filename( 61 | self._get_notebook(nb_name="pngmetadata.ipynb") 62 | ) 63 | assert len(output) > 0 64 | check_for_png = re.compile(r".. image::.*?\n\s+(.*?)\n\s*\n", re.DOTALL) 65 | result = check_for_png.search(output) 66 | assert result is not None 67 | attr_string = result.group(1) 68 | assert ":width:" in attr_string 69 | assert ":height:" in attr_string 70 | assert "px" in attr_string 71 | 72 | def test_rst_output(self): 73 | """ 74 | Is native text/x-rst output included when converting 75 | """ 76 | (output, _resources) = RSTExporter().from_filename( 77 | self._get_notebook(nb_name="rst_output.ipynb") 78 | ) 79 | assert len(output) > 0 80 | assert "\n.. note::" in output 81 | assert ".. raw:: html" not in output # rst should shadow html output 82 | -------------------------------------------------------------------------------- /share/templates/latex/style_ipython.tex.j2: -------------------------------------------------------------------------------- 1 | ((= IPython input/output style =)) 2 | 3 | ((*- extends 'base.tex.j2' -*)) 4 | 5 | % Custom definitions 6 | ((* block definitions *)) 7 | ((( super() ))) 8 | 9 | % Pygments definitions 10 | ((( resources.latex.pygments_definitions ))) 11 | 12 | % Exact colors from NB 13 | \definecolor{incolor}{rgb}{0.0, 0.0, 0.5} 14 | \definecolor{outcolor}{rgb}{0.545, 0.0, 0.0} 15 | 16 | ((* endblock definitions *)) 17 | 18 | %=============================================================================== 19 | % Input 20 | %=============================================================================== 21 | 22 | ((* block input scoped *)) 23 | ((*- if resources.global_content_filter.include_input_prompt *)) 24 | ((( add_prompt(cell.source | highlight_code(strip_verbatim=True, metadata=cell.metadata), cell, 'In ', 'incolor') ))) 25 | ((*- else *)) 26 | ((( cell.source | highlight_code(strip_verbatim=True, metadata=cell.metadata) ))) 27 | ((* endif *)) 28 | ((* endblock input *)) 29 | 30 | 31 | %=============================================================================== 32 | % Output 33 | %=============================================================================== 34 | 35 | ((* block execute_result scoped *)) 36 | ((*- for type in output.data | filter_data_type -*)) 37 | ((*- if resources.global_content_filter.include_output_prompt -*)) 38 | ((*- if type in ['text/plain'] *)) 39 | ((( add_prompt(output.data['text/plain'] | escape_latex, cell, 'Out', 'outcolor') ))) 40 | ((* else -*)) 41 | \texttt{\color{outcolor}Out[{\color{outcolor}((( cell.execution_count )))}]:}((( super() ))) 42 | ((*- endif -*)) 43 | ((*- else -*)) 44 | ((*- if type in ['text/plain'] *)) 45 | ((( output.data['text/plain'] | escape_latex ))) 46 | ((* else -*)) 47 | ((( super() ))) 48 | ((*- endif -*)) 49 | ((*- endif -*)) 50 | ((*- endfor -*)) 51 | ((* endblock execute_result *)) 52 | 53 | 54 | %============================================================================== 55 | % Support Macros 56 | %============================================================================== 57 | 58 | % Name: draw_prompt 59 | % Purpose: Renders an output/input prompt 60 | ((* macro add_prompt(text, cell, prompt, prompt_color) -*)) 61 | ((*- if cell.execution_count is defined -*)) 62 | ((*- set execution_count = "" ~ (cell.execution_count | replace(None, " ")) -*)) 63 | ((*- else -*)) 64 | ((*- set execution_count = " " -*)) 65 | ((*- endif -*)) 66 | ((*- set indention = " " * (execution_count | length + 7) -*)) 67 | \begin{Verbatim}[commandchars=\\\{\}] 68 | ((( text | add_prompts(first='{\\color{' ~ prompt_color ~ '}' ~ prompt ~ '[{\\color{' ~ prompt_color ~ '}' ~ execution_count ~ '}]:} ', cont=indention) ))) 69 | \end{Verbatim} 70 | ((*- endmacro *)) 71 | -------------------------------------------------------------------------------- /tests/preprocessors/base.py: -------------------------------------------------------------------------------- 1 | """utility functions for preprocessor tests""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from base64 import b64encode 7 | 8 | from nbformat import v4 as nbformat 9 | 10 | from nbconvert.exporters.exporter import ResourcesDict 11 | from tests.base import TestsBase 12 | 13 | 14 | class PreprocessorTestsBase(TestsBase): 15 | """Contains test functions preprocessor tests""" 16 | 17 | def build_notebook(self, with_json_outputs=False, with_attachment=False): 18 | """Build a notebook in memory for use with preprocessor tests""" 19 | 20 | outputs = [ 21 | nbformat.new_output("stream", name="stdout", text="a"), 22 | nbformat.new_output("display_data", data={"text/plain": "b"}), 23 | nbformat.new_output("stream", name="stdout", text="c"), 24 | nbformat.new_output("stream", name="stdout", text="d"), 25 | nbformat.new_output("stream", name="stderr", text="e"), 26 | nbformat.new_output("stream", name="stderr", text="f"), 27 | nbformat.new_output("display_data", data={"image/png": "Zw=="}), # g 28 | nbformat.new_output("display_data", data={"application/pdf": "aA=="}), # h 29 | ] 30 | if with_json_outputs: 31 | outputs.extend( 32 | [ 33 | nbformat.new_output("display_data", data={"application/json": [1, 2, 3]}), # j 34 | nbformat.new_output( 35 | "display_data", data={"application/json": {"a": 1, "c": {"b": 2}}} 36 | ), # k 37 | nbformat.new_output("display_data", data={"application/json": "abc"}), # l 38 | nbformat.new_output("display_data", data={"application/json": 15.03}), # m 39 | ] 40 | ) 41 | 42 | cells = [ 43 | nbformat.new_code_cell(source="$ e $", execution_count=1, outputs=outputs), 44 | nbformat.new_markdown_cell(source="$ e $"), 45 | ] 46 | 47 | if with_attachment: 48 | data = b"test" 49 | encoded_data = b64encode(data) 50 | # this is conversion of bytes to string, not base64 decoding 51 | attachments = {"image.png": {"image/png": encoded_data.decode()}} 52 | cells.extend( 53 | [ 54 | nbformat.new_markdown_cell( 55 | source="![image.png](attachment:image.png)", attachments=attachments 56 | ) 57 | ] 58 | ) 59 | 60 | return nbformat.new_notebook(cells=cells) 61 | 62 | def build_resources(self): 63 | """Build an empty resources dictionary.""" 64 | 65 | res = ResourcesDict() 66 | res["metadata"] = ResourcesDict() 67 | return res 68 | -------------------------------------------------------------------------------- /hatch_build.py: -------------------------------------------------------------------------------- 1 | """Custom build script for hatch backend""" 2 | 3 | import os 4 | import sys 5 | from urllib.request import urlopen 6 | 7 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 8 | 9 | notebook_css_version = "5.4.0" 10 | notebook_css_url = "https://cdn.jupyter.org/notebook/%s/style/style.min.css" % notebook_css_version 11 | 12 | jupyterlab_css_version = "4.0.2" 13 | jupyterlab_css_url = ( 14 | "https://unpkg.com/@jupyterlab/nbconvert-css@%s/style/index.css" % jupyterlab_css_version 15 | ) 16 | 17 | jupyterlab_theme_light_version = "4.0.2" 18 | jupyterlab_theme_light_url = ( 19 | "https://unpkg.com/@jupyterlab/theme-light-extension@%s/style/variables.css" 20 | % jupyterlab_theme_light_version 21 | ) 22 | 23 | jupyterlab_theme_dark_version = "4.0.2" 24 | jupyterlab_theme_dark_url = ( 25 | "https://unpkg.com/@jupyterlab/theme-dark-extension@%s/style/variables.css" 26 | % jupyterlab_theme_dark_version 27 | ) 28 | 29 | template_css_urls = { 30 | "lab": [ 31 | (jupyterlab_css_url, "index.css"), 32 | (jupyterlab_theme_light_url, "theme-light.css"), 33 | (jupyterlab_theme_dark_url, "theme-dark.css"), 34 | ], 35 | "classic": [(notebook_css_url, "style.css")], 36 | } 37 | 38 | osp = os.path 39 | here = osp.abspath(osp.dirname(__file__)) 40 | templates_dir = osp.join(here, "share", "templates") 41 | 42 | 43 | def _get_css_file(template_name, url, filename): 44 | """Get a css file and download it to the templates dir""" 45 | directory = osp.join(templates_dir, template_name, "static") 46 | dest = osp.join(directory, filename) 47 | if osp.exists(dest): 48 | print("Already have CSS: %s, moving on." % dest) 49 | return 50 | if not osp.exists(directory): 51 | os.makedirs(directory) 52 | print("Downloading CSS: %s" % url) 53 | try: 54 | css = urlopen(url).read() # noqa: S310 55 | except Exception as e: 56 | msg = f"Failed to download css from {url}: {e}" 57 | print(msg, file=sys.stderr) 58 | msg = "Need CSS to proceed." 59 | raise OSError(msg) from None 60 | return 61 | 62 | with open(dest, "wb") as f: 63 | f.write(css) 64 | print("Downloaded Notebook CSS to %s" % dest) 65 | 66 | 67 | def _get_css_files(): 68 | """Get all of the css files if necessary""" 69 | in_checkout = osp.exists(osp.abspath(osp.join(here, "..", ".git"))) 70 | if in_checkout: 71 | print("Not running from git, nothing to do") 72 | return 73 | 74 | for template_name, resources in template_css_urls.items(): 75 | for url, filename in resources: 76 | _get_css_file(template_name, url, filename) 77 | 78 | 79 | class CustomHook(BuildHookInterface): 80 | """A custom build hook for nbconvert.""" 81 | 82 | def initialize(self, version, build_data): 83 | """Initialize the hook.""" 84 | if self.target_name not in ["wheel", "sdist"]: 85 | return 86 | _get_css_files() 87 | -------------------------------------------------------------------------------- /nbconvert/utils/iso639_1.py: -------------------------------------------------------------------------------- 1 | """List of ISO639-1 language code""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | iso639_1 = [ 7 | "aa", 8 | "ab", 9 | "ae", 10 | "af", 11 | "ak", 12 | "am", 13 | "an", 14 | "ar", 15 | "as", 16 | "av", 17 | "ay", 18 | "az", 19 | "ba", 20 | "be", 21 | "bg", 22 | "bh", 23 | "bi", 24 | "bm", 25 | "bn", 26 | "bo", 27 | "br", 28 | "bs", 29 | "ca", 30 | "ce", 31 | "ch", 32 | "co", 33 | "cr", 34 | "cs", 35 | "cu", 36 | "cv", 37 | "cy", 38 | "da", 39 | "de", 40 | "dv", 41 | "dz", 42 | "ee", 43 | "el", 44 | "en", 45 | "eo", 46 | "es", 47 | "et", 48 | "eu", 49 | "fa", 50 | "ff", 51 | "fi", 52 | "fj", 53 | "fo", 54 | "fr", 55 | "fy", 56 | "ga", 57 | "gd", 58 | "gl", 59 | "gn", 60 | "gu", 61 | "gv", 62 | "ha", 63 | "he", 64 | "hi", 65 | "ho", 66 | "hr", 67 | "ht", 68 | "hu", 69 | "hy", 70 | "hz", 71 | "ia", 72 | "id", 73 | "ie", 74 | "ig", 75 | "ii", 76 | "ik", 77 | "io", 78 | "is", 79 | "it", 80 | "iu", 81 | "ja", 82 | "jv", 83 | "ka", 84 | "kg", 85 | "ki", 86 | "kj", 87 | "kk", 88 | "kl", 89 | "km", 90 | "kn", 91 | "ko", 92 | "kr", 93 | "ks", 94 | "ku", 95 | "kv", 96 | "kw", 97 | "ky", 98 | "la", 99 | "lb", 100 | "lg", 101 | "li", 102 | "ln", 103 | "lo", 104 | "lt", 105 | "lu", 106 | "lv", 107 | "mg", 108 | "mh", 109 | "mi", 110 | "mk", 111 | "ml", 112 | "mn", 113 | "mr", 114 | "ms", 115 | "mt", 116 | "my", 117 | "na", 118 | "nb", 119 | "nd", 120 | "ne", 121 | "ng", 122 | "nl", 123 | "nn", 124 | "no", 125 | "nr", 126 | "nv", 127 | "ny", 128 | "oc", 129 | "oj", 130 | "om", 131 | "or", 132 | "os", 133 | "pa", 134 | "pi", 135 | "pl", 136 | "ps", 137 | "pt", 138 | "qu", 139 | "rm", 140 | "rn", 141 | "ro", 142 | "ru", 143 | "rw", 144 | "sa", 145 | "sc", 146 | "sd", 147 | "se", 148 | "sg", 149 | "si", 150 | "sk", 151 | "sl", 152 | "sm", 153 | "sn", 154 | "so", 155 | "sq", 156 | "sr", 157 | "ss", 158 | "st", 159 | "su", 160 | "sv", 161 | "sw", 162 | "ta", 163 | "te", 164 | "tg", 165 | "th", 166 | "ti", 167 | "tk", 168 | "tl", 169 | "tn", 170 | "to", 171 | "tr", 172 | "ts", 173 | "tt", 174 | "tw", 175 | "ty", 176 | "ug", 177 | "uk", 178 | "ur", 179 | "uz", 180 | "ve", 181 | "vi", 182 | "vo", 183 | "wa", 184 | "wo", 185 | "xh", 186 | "yi", 187 | "yo", 188 | "za", 189 | "zh", 190 | "zu", 191 | ] 192 | -------------------------------------------------------------------------------- /share/templates/reveal/static/custom_reveal.css: -------------------------------------------------------------------------------- 1 | /* Overrides of notebook CSS for static HTML export */ 2 | .reveal { 3 | font-size: 160%; 4 | } 5 | .reveal table { 6 | font-size: var(--jp-ui-font-size1); 7 | } 8 | .reveal pre { 9 | width: inherit; 10 | padding: 0.4em; 11 | margin: 0px; 12 | font-family: monospace, sans-serif; 13 | font-size: 80%; 14 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0); 15 | } 16 | .reveal pre code { 17 | padding: 0px; 18 | } 19 | .reveal section img { 20 | border: 0px solid black; 21 | box-shadow: 0 0 10px rgba(0, 0, 0, 0); 22 | } 23 | .reveal .slides { 24 | text-align: left; 25 | } 26 | .reveal.fade { 27 | opacity: 1; 28 | } 29 | .reveal .progress { 30 | position: static; 31 | } 32 | 33 | div.jp-InputArea-editor { 34 | padding: 0.06em; 35 | } 36 | 37 | div.code_cell { 38 | background-color: transparent; 39 | } 40 | 41 | div.output_area pre { 42 | font-family: monospace, sans-serif; 43 | font-size: 80%; 44 | } 45 | 46 | div.jp-OutputPrompt { 47 | /* 5px right shift to account for margin in parent container */ 48 | margin: 5px 5px 0 0; 49 | } 50 | 51 | .reveal div.highlight { 52 | margin: 0; 53 | } 54 | 55 | .reveal div.highlight > pre { 56 | margin: 0; 57 | width: 100%; 58 | font-size: var(--jp-code-font-size); 59 | } 60 | 61 | .reveal div.jp-OutputArea-output > pre { 62 | margin: 0; 63 | width: 90%; 64 | font-size: var(--jp-code-font-size); 65 | box-shadow: none; 66 | } 67 | 68 | main { 69 | height: 100%; 70 | } 71 | 72 | /* Reveal navigation controls */ 73 | 74 | .reveal .controls .navigate-left, 75 | .reveal .controls .navigate-left.enabled { 76 | border-right-color: #727272; 77 | } 78 | .reveal .controls .navigate-left.enabled:hover, 79 | .reveal .controls .navigate-left.enabled.enabled:hover { 80 | border-right-color: #dfdfdf; 81 | } 82 | .reveal .controls .navigate-right, 83 | .reveal .controls .navigate-right.enabled { 84 | border-left-color: #727272; 85 | } 86 | .reveal .controls .navigate-right.enabled:hover, 87 | .reveal .controls .navigate-right.enabled.enabled:hover { 88 | border-left-color: #dfdfdf; 89 | } 90 | .reveal .controls .navigate-up, 91 | .reveal .controls .navigate-up.enabled { 92 | border-bottom-color: #727272; 93 | } 94 | .reveal .controls .navigate-up.enabled:hover, 95 | .reveal .controls .navigate-up.enabled.enabled:hover { 96 | border-bottom-color: #dfdfdf; 97 | } 98 | .reveal .controls .navigate-down, 99 | .reveal .controls .navigate-down.enabled { 100 | border-top-color: #727272; 101 | } 102 | .reveal .controls .navigate-down.enabled:hover, 103 | .reveal .controls .navigate-down.enabled.enabled:hover { 104 | border-top-color: #dfdfdf; 105 | } 106 | .reveal .progress span { 107 | background: #727272; 108 | } 109 | 110 | /* Scrollbars */ 111 | 112 | ::-webkit-scrollbar { 113 | width: 6px; 114 | height: 6px; 115 | } 116 | ::-webkit-scrollbar * { 117 | background: transparent; 118 | } 119 | ::-webkit-scrollbar-thumb { 120 | background: #727272 !important; 121 | } 122 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/latex.py: -------------------------------------------------------------------------------- 1 | """Module that allows latex output notebooks to be conditioned before 2 | they are converted. 3 | """ 4 | # ----------------------------------------------------------------------------- 5 | # Copyright (c) 2013, the IPython Development Team. 6 | # 7 | # Distributed under the terms of the Modified BSD License. 8 | # 9 | # The full license is in the file COPYING.txt, distributed with this software. 10 | # ----------------------------------------------------------------------------- 11 | 12 | # ----------------------------------------------------------------------------- 13 | # Imports 14 | # ----------------------------------------------------------------------------- 15 | 16 | from traitlets import List, Unicode 17 | 18 | from .base import Preprocessor 19 | 20 | # ----------------------------------------------------------------------------- 21 | # Classes 22 | # ----------------------------------------------------------------------------- 23 | 24 | 25 | class LatexPreprocessor(Preprocessor): 26 | """Preprocessor for latex destined documents. 27 | 28 | Populates the ``latex`` key in the resources dict, 29 | adding definitions for pygments highlight styles. 30 | 31 | Sets the authors, date and title of the latex document, 32 | overriding the values given in the metadata. 33 | """ 34 | 35 | date = Unicode( 36 | None, 37 | help=("Date of the LaTeX document"), 38 | allow_none=True, 39 | ).tag(config=True) 40 | 41 | title = Unicode(None, help=("Title of the LaTeX document"), allow_none=True).tag(config=True) 42 | 43 | author_names = List( 44 | Unicode(), 45 | default_value=None, 46 | help=("Author names to list in the LaTeX document"), 47 | allow_none=True, 48 | ).tag(config=True) 49 | 50 | style = Unicode("default", help="Name of the pygments style to use").tag(config=True) 51 | 52 | def preprocess(self, nb, resources): 53 | """Preprocessing to apply on each notebook. 54 | 55 | Parameters 56 | ---------- 57 | nb : NotebookNode 58 | Notebook being converted 59 | resources : dictionary 60 | Additional resources used in the conversion process. Allows 61 | preprocessors to pass variables into the Jinja engine. 62 | """ 63 | # Generate Pygments definitions for Latex 64 | from pygments.formatters import LatexFormatter # noqa: PLC0415 65 | 66 | resources.setdefault("latex", {}) 67 | resources["latex"].setdefault( 68 | "pygments_definitions", LatexFormatter(style=self.style).get_style_defs() 69 | ) 70 | resources["latex"].setdefault("pygments_style_name", self.style) 71 | 72 | if self.author_names is not None: 73 | nb.metadata["authors"] = [{"name": author} for author in self.author_names] 74 | 75 | if self.date is not None: 76 | nb.metadata["date"] = self.date 77 | 78 | if self.title is not None: 79 | nb.metadata["title"] = self.title 80 | 81 | return nb, resources 82 | -------------------------------------------------------------------------------- /share/templates/latex/document_contents.tex.j2: -------------------------------------------------------------------------------- 1 | ((*- extends 'display_priority.j2' -*)) 2 | 3 | %=============================================================================== 4 | % Support blocks 5 | %=============================================================================== 6 | 7 | % Displaying simple data text 8 | ((* block data_text *)) 9 | \begin{Verbatim}[commandchars=\\\{\}] 10 | ((( output.data['text/plain'] | escape_latex | ansi2latex ))) 11 | \end{Verbatim} 12 | ((* endblock data_text *)) 13 | 14 | % Display python error text with colored frame (saves printer ink vs bkgnd) 15 | ((* block error *)) 16 | \begin{Verbatim}[commandchars=\\\{\}, frame=single, framerule=2mm, rulecolor=\color{outerrorbackground}] 17 | (((- super() ))) 18 | \end{Verbatim} 19 | ((* endblock error *)) 20 | % Display error lines with coloring 21 | ((*- block traceback_line *)) 22 | ((( line | escape_latex | ansi2latex ))) 23 | ((*- endblock traceback_line *)) 24 | 25 | % Display stream ouput with coloring 26 | ((* block stream *)) 27 | \begin{Verbatim}[commandchars=\\\{\}] 28 | ((( output.text | escape_latex | ansi2latex ))) 29 | \end{Verbatim} 30 | ((* endblock stream *)) 31 | 32 | % Display latex 33 | ((* block data_latex -*)) 34 | ((( output.data['text/latex'] | strip_files_prefix ))) 35 | ((* endblock data_latex *)) 36 | 37 | % Display markdown 38 | ((* block data_markdown -*)) 39 | ((( output.data['text/markdown'] | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'latex')))) 40 | ((* endblock data_markdown *)) 41 | 42 | % Default mechanism for rendering figures 43 | ((*- block data_png -*))((( draw_figure(output.metadata.filenames['image/png']) )))((*- endblock -*)) 44 | ((*- block data_jpg -*))((( draw_figure(output.metadata.filenames['image/jpeg']) )))((*- endblock -*)) 45 | ((*- block data_svg -*))((( draw_figure(output.metadata.filenames['image/svg+xml']) )))((*- endblock -*)) 46 | ((*- block data_pdf -*))((( draw_figure(output.metadata.filenames['application/pdf']) )))((*- endblock -*)) 47 | 48 | % Draw a figure using the graphicx package. 49 | ((* macro draw_figure(filename) -*)) 50 | ((* set filename = filename | posix_path *)) 51 | ((*- block figure scoped -*)) 52 | \begin{center} 53 | \adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{((( filename )))} 54 | \end{center} 55 | { \hspace*{\fill} \\} 56 | ((*- endblock figure -*)) 57 | ((*- endmacro *)) 58 | 59 | % Redirect execute_result to display data priority. 60 | ((* block execute_result scoped *)) 61 | ((* block data_priority scoped *)) 62 | ((( super() ))) 63 | ((* endblock *)) 64 | ((* endblock execute_result *)) 65 | 66 | % Render markdown 67 | ((* block markdowncell scoped *)) 68 | ((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'json',extra_args=[]) | resolve_references | convert_explicitly_relative_paths | convert_pandoc('json','latex')))) 69 | ((* endblock markdowncell *)) 70 | 71 | % Don't display unknown types 72 | ((* block unknowncell scoped *)) 73 | ((* endblock unknowncell *)) 74 | -------------------------------------------------------------------------------- /nbconvert/filters/widgetsdatatypefilter.py: -------------------------------------------------------------------------------- 1 | """Filter used to select the first preferred output format available, 2 | excluding interactive widget format if the widget state is not available. 3 | 4 | The filter contained in the file allows the converter templates to select 5 | the output format that is most valuable to the active export format. The 6 | value of the different formats is set via 7 | NbConvertBase.display_data_priority 8 | """ 9 | # ----------------------------------------------------------------------------- 10 | # Copyright (c) 2013, the IPython Development Team. 11 | # 12 | # Distributed under the terms of the Modified BSD License. 13 | # 14 | # The full license is in the file COPYING.txt, distributed with this software. 15 | # ----------------------------------------------------------------------------- 16 | 17 | # ----------------------------------------------------------------------------- 18 | # Classes and functions 19 | # ----------------------------------------------------------------------------- 20 | 21 | import os 22 | from warnings import warn 23 | 24 | from nbconvert.utils.base import NbConvertBase 25 | 26 | __all__ = ["WidgetsDataTypeFilter"] 27 | 28 | 29 | WIDGET_VIEW_MIMETYPE = "application/vnd.jupyter.widget-view+json" 30 | WIDGET_STATE_MIMETYPE = "application/vnd.jupyter.widget-state+json" 31 | 32 | 33 | class WidgetsDataTypeFilter(NbConvertBase): 34 | """Returns the preferred display format, excluding the widget output if 35 | there is no widget state available""" 36 | 37 | def __init__(self, notebook_metadata=None, resources=None, **kwargs): 38 | """Initialize the filter.""" 39 | self.metadata = notebook_metadata 40 | self.notebook_path = "" 41 | if resources is not None: 42 | name = resources.get("metadata", {}).get("name", "") 43 | path = resources.get("metadata", {}).get("path", "") 44 | self.notebook_path = os.path.join(path, name) 45 | 46 | super().__init__(**kwargs) 47 | 48 | def __call__(self, output): 49 | """Return the first available format in the priority. 50 | 51 | Produces a UserWarning if no compatible mimetype is found. 52 | 53 | `output` is dict with structure {mimetype-of-element: value-of-element} 54 | 55 | """ 56 | metadata = self.metadata.get(self.notebook_path, {}) 57 | widgets_state = ( 58 | metadata["widgets"][WIDGET_STATE_MIMETYPE]["state"] 59 | if metadata.get("widgets") is not None 60 | else {} 61 | ) 62 | for fmt in self.display_data_priority: 63 | if fmt in output: 64 | # If there is no widget state available, we skip this mimetype 65 | if ( 66 | fmt == WIDGET_VIEW_MIMETYPE 67 | and output[WIDGET_VIEW_MIMETYPE]["model_id"] not in widgets_state 68 | ): 69 | continue 70 | 71 | return [fmt] 72 | warn( 73 | f"Your element with mimetype(s) {output.keys()} is not able to be represented.", 74 | stacklevel=2, 75 | ) 76 | 77 | return [] 78 | -------------------------------------------------------------------------------- /tests/filters/test_highlight.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with tests for Highlight 3 | """ 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Copyright (c) 2013, the IPython Development Team. 7 | # 8 | # Distributed under the terms of the Modified BSD License. 9 | # 10 | # The full license is in the file COPYING.txt, distributed with this software. 11 | # ----------------------------------------------------------------------------- 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Imports 15 | # ----------------------------------------------------------------------------- 16 | 17 | import xml 18 | 19 | import pytest 20 | 21 | from nbconvert.filters.highlight import Highlight2HTML, Highlight2Latex 22 | from tests.base import TestsBase 23 | 24 | # ----------------------------------------------------------------------------- 25 | # Class 26 | # ----------------------------------------------------------------------------- 27 | 28 | highlight2html = Highlight2HTML() 29 | highlight2latex = Highlight2Latex() 30 | highlight2html_ruby = Highlight2HTML(pygments_lexer="ruby") 31 | 32 | 33 | class TestHighlight(TestsBase): 34 | """Contains test functions for highlight.py""" 35 | 36 | # Hello world test, magics test, blank string test 37 | tests = [ 38 | """ 39 | #Hello World Example 40 | 41 | import foo 42 | 43 | def say(text): 44 | foo.bar(text) 45 | 46 | end 47 | 48 | say('Hello World!') 49 | """, 50 | """ 51 | %%pylab 52 | plot(x,y, 'r') 53 | """, 54 | ] 55 | 56 | tokens = [["Hello World Example", "say", "text", "import", "def"], ["pylab", "plot"]] 57 | 58 | def test_highlight2html(self): 59 | """highlight2html test""" 60 | for index, test in enumerate(self.tests): 61 | self._try_highlight(highlight2html, test, self.tokens[index]) 62 | 63 | def test_highlight2latex(self): 64 | """highlight2latex test""" 65 | for index, test in enumerate(self.tests): 66 | self._try_highlight(highlight2latex, test, self.tokens[index]) 67 | 68 | def test_parse_html_many_lang(self): 69 | ht = highlight2html(self.tests[0]) 70 | rb = highlight2html_ruby(self.tests[0]) 71 | 72 | for lang, tkns in [(ht, ("def",)), (rb, ("def", "end"))]: 73 | root = xml.etree.ElementTree.fromstring(lang) # noqa 74 | self.assertEqual(self._extract_tokens(root, "k"), set(tkns)) 75 | 76 | @pytest.mark.filterwarnings("ignore") 77 | def test_inject_html(self): 78 | out = highlight2html(self.tests[0], 'ipython3-foo">') 79 | assert "" not in out 80 | 81 | def _extract_tokens(self, root, cls): 82 | return set(map(lambda x: x.text, root.findall(".//*[@class='" + cls + "']"))) # noqa 83 | 84 | def _try_highlight(self, method, test, tokens): 85 | """Try highlighting source, look for key tokens""" 86 | results = method(test) 87 | for token in tokens: 88 | assert token in results 89 | -------------------------------------------------------------------------------- /nbconvert/preprocessors/base.py: -------------------------------------------------------------------------------- 1 | """Base class for preprocessors""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from traitlets import Bool 7 | 8 | from nbconvert.utils.base import NbConvertBase 9 | 10 | 11 | class Preprocessor(NbConvertBase): 12 | """A configurable preprocessor 13 | 14 | Inherit from this class if you wish to have configurability for your 15 | preprocessor. 16 | 17 | Any configurable traitlets this class exposed will be configurable in 18 | profiles using c.SubClassName.attribute = value 19 | 20 | You can overwrite `preprocess_cell()` to apply a transformation 21 | independently on each cell or `preprocess()` if you prefer your own 22 | logic. See corresponding docstring for information. 23 | 24 | Disabled by default and can be enabled via the config by 25 | 'c.YourPreprocessorName.enabled = True' 26 | """ 27 | 28 | enabled = Bool(False).tag(config=True) 29 | 30 | def __init__(self, **kw): 31 | """ 32 | Public constructor 33 | 34 | Parameters 35 | ---------- 36 | config : Config 37 | Configuration file structure 38 | `**kw` 39 | Additional keyword arguments passed to parent 40 | """ 41 | 42 | super().__init__(**kw) 43 | 44 | def __call__(self, nb, resources): 45 | """Apply the preprocessor.""" 46 | if self.enabled: 47 | self.log.debug("Applying preprocessor: %s", self.__class__.__name__) 48 | return self.preprocess(nb, resources) 49 | return nb, resources 50 | 51 | def preprocess(self, nb, resources): 52 | """ 53 | Preprocessing to apply on each notebook. 54 | 55 | Must return modified nb, resources. 56 | 57 | If you wish to apply your preprocessing to each cell, you might want 58 | to override preprocess_cell method instead. 59 | 60 | Parameters 61 | ---------- 62 | nb : NotebookNode 63 | Notebook being converted 64 | resources : dictionary 65 | Additional resources used in the conversion process. Allows 66 | preprocessors to pass variables into the Jinja engine. 67 | """ 68 | for index, cell in enumerate(nb.cells): 69 | nb.cells[index], resources = self.preprocess_cell(cell, resources, index) 70 | return nb, resources 71 | 72 | def preprocess_cell(self, cell, resources, index): 73 | """ 74 | Override if you want to apply some preprocessing to each cell. 75 | Must return modified cell and resource dictionary. 76 | 77 | Parameters 78 | ---------- 79 | cell : NotebookNode cell 80 | Notebook cell being processed 81 | resources : dictionary 82 | Additional resources used in the conversion process. Allows 83 | preprocessors to pass variables into the Jinja engine. 84 | index : int 85 | Index of the cell being processed 86 | """ 87 | msg = "should be implemented by subclass" 88 | raise NotImplementedError(msg) 89 | --------------------------------------------------------------------------------