├── .cookiecutter.json ├── .darglint ├── .flake8 ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.yml ├── dependabot.yml ├── labels.yml ├── release-drafter.yml └── workflows │ ├── constraints.txt │ ├── labeler.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codecov.yml ├── docs ├── _static │ ├── css │ │ └── custom.css │ ├── examples │ │ ├── html │ │ │ ├── cell_magic_syntax_highlight.html │ │ │ ├── dark_plot_block.html │ │ │ ├── dark_plot_braille.html │ │ │ ├── dark_plot_negative_character.html │ │ │ ├── dark_plot_positive_character.html │ │ │ ├── dataframe.html │ │ │ ├── example_notebook.html │ │ │ ├── html.html │ │ │ ├── images_block.html │ │ │ ├── images_braille.html │ │ │ ├── images_character.html │ │ │ ├── julia_syntax_highlight.html │ │ │ ├── latex.html │ │ │ ├── links.html │ │ │ ├── long_code.html │ │ │ ├── markdown.html │ │ │ ├── stderr.html │ │ │ ├── theme_dracula.html │ │ │ ├── theme_material.html │ │ │ ├── theme_monokai.html │ │ │ ├── theme_one_dark.html │ │ │ ├── theme_paraiso_light.html │ │ │ ├── theme_rainbow_dash.html │ │ │ ├── traceback.html │ │ │ └── vega.html │ │ └── svg │ │ │ ├── cell_magic_syntax_highlight.svg │ │ │ ├── dark_plot_block.svg │ │ │ ├── dark_plot_braille.svg │ │ │ ├── dark_plot_negative_character.svg │ │ │ ├── dark_plot_positive_character.svg │ │ │ ├── dataframe.svg │ │ │ ├── example_notebook.svg │ │ │ ├── html.svg │ │ │ ├── images_block.svg │ │ │ ├── images_braille.svg │ │ │ ├── images_character.svg │ │ │ ├── julia_syntax_highlight.svg │ │ │ ├── latex.svg │ │ │ ├── links.svg │ │ │ ├── long_code.svg │ │ │ ├── markdown.svg │ │ │ ├── stderr.svg │ │ │ ├── theme_dracula.svg │ │ │ ├── theme_material.svg │ │ │ ├── theme_monokai.svg │ │ │ ├── theme_one_dark.svg │ │ │ ├── theme_paraiso_light.svg │ │ │ ├── theme_rainbow_dash.svg │ │ │ ├── traceback.svg │ │ │ └── vega.svg │ ├── fonts │ │ ├── FiraCode-Bold.woff │ │ ├── FiraCode-Bold.woff2 │ │ ├── FiraCode-Regular.woff │ │ └── FiraCode-Regular.woff2 │ ├── generated_html │ │ ├── dataframe.html │ │ ├── example_notebook_dataframe.html │ │ ├── example_notebook_posterior_predictive_progress_bar.html │ │ ├── example_notebook_sampling_progress_bar.html │ │ ├── html.html │ │ ├── links_table.html │ │ ├── links_vega.html │ │ ├── stderr.html │ │ └── vega.html │ └── images │ │ ├── LICENSE │ │ ├── dark_plot.png │ │ ├── emoji_u1f422.png │ │ ├── example_notebook_hdi_plot.png │ │ ├── example_notebook_length_distribution_plot.png │ │ ├── example_notebook_length_scatter.png │ │ ├── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── favicon.svg │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ │ ├── hero_image.png │ │ ├── image_plot.png │ │ ├── links_plot.png │ │ ├── logo_dark.svg │ │ └── logo_light.svg ├── codeofconduct.md ├── conf.py ├── configure.md ├── contributing.md ├── credits.md ├── dependencies.md ├── example_notebook_cells │ ├── content │ │ ├── bash_cell_magic.json │ │ ├── code.json │ │ ├── dark_plot.json │ │ ├── dataframe.json │ │ ├── example_notebook.json │ │ ├── html.json │ │ ├── images.json │ │ ├── julia_code.json │ │ ├── latex.json │ │ ├── links.json │ │ ├── long_code.json │ │ ├── markdown.json │ │ ├── stderr.json │ │ ├── traceback.json │ │ └── vega.json │ ├── hero_notebook.ipynb │ ├── model.py │ ├── plots.py │ └── templates │ │ ├── html_template.jinja │ │ └── svg_template.jinja ├── examples.toml ├── features.md ├── generate_examples.py ├── index.md ├── installation.md ├── license.md ├── nbpreview_logo.fig ├── prior_art.md ├── reference.md ├── requirements.txt └── usage.rst ├── justfile ├── noxfile.py ├── poetry.lock ├── pyproject.toml ├── src └── nbpreview │ ├── __init__.py │ ├── __main__.py │ ├── _color_typer.py │ ├── component │ ├── __init__.py │ ├── content │ │ ├── __init__.py │ │ ├── input.py │ │ └── output │ │ │ ├── __init__.py │ │ │ ├── error.py │ │ │ ├── output.py │ │ │ ├── result │ │ │ ├── __init__.py │ │ │ ├── display_data.py │ │ │ ├── drawing.py │ │ │ ├── execution_indicator.py │ │ │ ├── latex.py │ │ │ ├── link.py │ │ │ ├── markdown_extensions.py │ │ │ ├── result.py │ │ │ └── table.py │ │ │ └── stream.py │ ├── markdown.py │ └── row.py │ ├── data │ └── __init__.py │ ├── errors.py │ ├── notebook.py │ ├── option_values.py │ ├── parameters.py │ ├── py.typed │ └── templates │ └── vega_template.jinja └── tests ├── __init__.py ├── conftest.py ├── leaves.jpg ├── unit ├── __init__.py ├── assets │ ├── LICENSE │ ├── LICENSE.md │ ├── bad_image.xyz │ ├── link_encoded_image.ipynb │ ├── matplotlibrc │ ├── notebook.ipynb │ └── outline_article_white_48dp.png ├── component │ ├── __init__.py │ ├── content │ │ ├── __init__.py │ │ └── output │ │ │ ├── __init__.py │ │ │ ├── result │ │ │ ├── __init__.py │ │ │ ├── test_drawing.py │ │ │ ├── test_link.py │ │ │ └── test_markdown_extensions.py │ │ │ ├── test_error.py │ │ │ └── test_stream.py │ ├── test_markdown.py │ └── test_row.py ├── expected_outputs │ ├── test_braille_drawing.txt │ ├── test_change_theme_notebook_file[--theme-light-None].txt │ ├── test_change_theme_notebook_file[-t-dark-None].txt │ ├── test_change_theme_notebook_file[-t-monokai-None].txt │ ├── test_change_theme_notebook_file[None-None-default].txt │ ├── test_charater_drawing.txt │ ├── test_code_wrap_notebook_file[--code-wrap-None].txt │ ├── test_code_wrap_notebook_file[-q-None].txt │ ├── test_code_wrap_notebook_file[None-1].txt │ ├── test_color_notebook_file[--color-None-None].txt │ ├── test_color_notebook_file[--no-color-None-None].txt │ ├── test_color_notebook_file[-c-None-None].txt │ ├── test_color_notebook_file[-o-None-None].txt │ ├── test_color_notebook_file[None-NBPREVIEW_COLOR-0].txt │ ├── test_color_notebook_file[None-NBPREVIEW_NO_COLOR-true].txt │ ├── test_color_notebook_file[None-NO_COLOR-1].txt │ ├── test_color_notebook_file[None-TERM-dumb].txt │ ├── test_color_system_notebook_file[--color-system-none-None].txt │ ├── test_color_system_notebook_file[--color-system-standard-None].txt │ ├── test_color_system_notebook_file[--cs-256-None].txt │ ├── test_color_system_notebook_file[None-None-windows].txt │ ├── test_color_system_notebook_file[None-nbpreview_color_system-windows].txt │ ├── test_files_output_notebook_file[--no-files-None].txt │ ├── test_files_output_notebook_file[-l-None].txt │ ├── test_files_output_notebook_file[None-1].txt │ ├── test_hide_output_notebook_file[--hide-output-None].txt │ ├── test_hide_output_notebook_file[-h-None].txt │ ├── test_hide_output_notebook_file[None-1].txt │ ├── test_html_encoded_image_link_text.txt │ ├── test_hyperlink_hints_output_notebook_file[--hide-hyperlink-hints-None].txt │ ├── test_hyperlink_hints_output_notebook_file[-y-None].txt │ ├── test_hyperlink_hints_output_notebook_file[None-1].txt │ ├── test_hyperlinks_output_notebook_file[--hyperlinks-None].txt │ ├── test_hyperlinks_output_notebook_file[--no-hyperlinks-None].txt │ ├── test_hyperlinks_output_notebook_file[-k-None].txt │ ├── test_hyperlinks_output_notebook_file[-r-None].txt │ ├── test_hyperlinks_output_notebook_file[None-0].txt │ ├── test_hyperlinks_output_notebook_file[None-1].txt │ ├── test_image_drawing_notebook_file[--id-character-None].txt │ ├── test_image_drawing_notebook_file[--image-drawing-block-None].txt │ ├── test_image_drawing_notebook_file[--image-drawing-braille-None].txt │ ├── test_image_drawing_notebook_file[None-None-braille].txt │ ├── test_image_link_markdown_cell.txt │ ├── test_image_markdown_cell.txt │ ├── test_image_notebook_file[--images-None].txt │ ├── test_image_notebook_file[--no-images-None].txt │ ├── test_image_notebook_file[-e-None].txt │ ├── test_image_notebook_file[-i-None].txt │ ├── test_image_notebook_file[-images-None].txt │ ├── test_image_notebook_file[None-0].txt │ ├── test_image_notebook_file[None-1].txt │ ├── test_image_notebook_file[None-None].txt │ ├── test_line_numbers_notebook_file[--line-numbers-None].txt │ ├── test_line_numbers_notebook_file[-m-None].txt │ ├── test_line_numbers_notebook_file[None-1].txt │ ├── test_list_themes.txt │ ├── test_nerd_font_output_notebook_file[--nerd-font-None].txt │ ├── test_nerd_font_output_notebook_file[-n-None].txt │ ├── test_nerd_font_output_notebook_file[None-1].txt │ ├── test_no_color_no_image.txt │ ├── test_paging_notebook_stdout_file[None].txt │ ├── test_paging_notebook_stdout_file[True].txt │ ├── test_plain_output_notebook_file[--plain-None].txt │ ├── test_plain_output_notebook_file[-p-None].txt │ ├── test_plain_output_notebook_file[None-1].txt │ ├── test_positive_space_output_notebook_file[--positive-space-None].txt │ ├── test_positive_space_output_notebook_file[-s-None].txt │ ├── test_positive_space_output_notebook_file[None-1].txt │ ├── test_render_block_image.txt │ ├── test_render_debugger_output.txt │ ├── test_render_error_traceback_no_hang.txt │ ├── test_render_height_constrained_block_image.txt │ ├── test_render_narrow_notebook[--image-drawing-block].txt │ ├── test_render_narrow_notebook[--image-drawing-braille].txt │ ├── test_render_notebook_file.txt │ ├── test_render_stdin[-].txt │ ├── test_render_stdin[None].txt │ ├── test_unicode_output_notebook_file[--no-unicode-None].txt │ ├── test_unicode_output_notebook_file[--unicode-None].txt │ ├── test_unicode_output_notebook_file[-u-None].txt │ ├── test_unicode_output_notebook_file[-x-None].txt │ └── test_unicode_output_notebook_file[None-0].txt ├── test_main.py ├── test_notebook.py └── test_option_values.py └── util ├── __ini__.py ├── debug.py └── process_output.py /.cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "_template": "gh:cjolowicz/cookiecutter-hypermodern-python", 3 | "author": "Paulo S. Costa", 4 | "email": "Paulo.S.Costa5@gmail.com", 5 | "friendly_name": "nbpreview", 6 | "github_user": "paw-lu", 7 | "license": "MIT", 8 | "package_name": "nbpreview", 9 | "project_name": "nbpreview", 10 | "version": "0.0.0", 11 | "development_status": "Development Status :: 3 - Alpha" 12 | } 13 | -------------------------------------------------------------------------------- /.darglint: -------------------------------------------------------------------------------- 1 | [darglint] 2 | strictness = long 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | select = B,B9,C,D,DAR,E,F,N,RST,S,W 3 | ignore = E203,E501,RST201,RST203,RST210,RST213,RST301,S410,W503 4 | max-line-length = 80 5 | max-complexity = 10 6 | docstring-convention = google 7 | per-file-ignores = 8 | tests/*:S101,S404 9 | rst-roles = class,const,func,meth,mod,py:class,py:data,ref 10 | rst-directives = deprecated 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: [bug] 4 | body: 5 | - type: textarea 6 | id: what-happened 7 | attributes: 8 | label: What happened? 9 | description: Also tell us, what did you expect to happen? 10 | placeholder: Tell us what you see! 11 | validations: 12 | required: true 13 | - type: input 14 | id: nbpreview-version 15 | attributes: 16 | label: nbpreview version 17 | description: | 18 | What version of nbpreview are you using? 19 | 20 | Tip: Run `nbpreview --version` 21 | placeholder: "0.5.0" 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: output 26 | attributes: 27 | label: Output 28 | description: Display the output of the program. 29 | placeholder: Share a rendered notebook, traceback, screenshot, or file of captured output. 30 | validations: 31 | required: false 32 | - type: textarea 33 | id: notebook 34 | attributes: 35 | label: Notebook 36 | description: | 37 | Upload the notebook that caused the issue. Reduce the notebook to the minimal number of cells and content that can be used to reproduce the issue. 38 | placeholder: You can attach files by clicking this area to highlight it and then dragging files in. 39 | validations: 40 | required: false 41 | - type: input 42 | id: os 43 | attributes: 44 | label: OS 45 | description: What OS are you running on when you experience the issue? 46 | placeholder: "macOS" 47 | validations: 48 | required: false 49 | - type: input 50 | id: terminal 51 | attributes: 52 | label: Terminal 53 | description: What terminal are you using when you experience the issue? 54 | placeholder: "iTerm2" 55 | validations: 56 | required: false 57 | - type: input 58 | id: python-version 59 | attributes: 60 | label: Python version 61 | description: What version of python is nbpreview running on? 62 | placeholder: "3.9.2" 63 | validations: 64 | required: false 65 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: pip 8 | directory: "/.github/workflows" 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: pip 12 | directory: "/docs" 13 | schedule: 14 | interval: daily 15 | - package-ecosystem: pip 16 | directory: "/" 17 | schedule: 18 | interval: daily 19 | versioning-strategy: lockfile-only 20 | allow: 21 | - dependency-type: "all" 22 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | - name: breaking 8 | description: Breaking Changes 9 | color: bfd4f2 10 | - name: bug 11 | description: Something isn't working 12 | color: d73a4a 13 | - name: build 14 | description: Build System and Dependencies 15 | color: bfdadc 16 | - name: ci 17 | description: Continuous Integration 18 | color: 4a97d6 19 | - name: dependencies 20 | description: Pull requests that update a dependency file 21 | color: 0366d6 22 | - name: documentation 23 | description: Improvements or additions to documentation 24 | color: 0075ca 25 | - name: duplicate 26 | description: This issue or pull request already exists 27 | color: cfd3d7 28 | - name: enhancement 29 | description: New feature or request 30 | color: a2eeef 31 | - name: github_actions 32 | description: Pull requests that update Github_actions code 33 | color: "000000" 34 | - name: good first issue 35 | description: Good for newcomers 36 | color: 7057ff 37 | - name: help wanted 38 | description: Extra attention is needed 39 | color: 008672 40 | - name: invalid 41 | description: This doesn't seem right 42 | color: e4e669 43 | - name: performance 44 | description: Performance 45 | color: "016175" 46 | - name: python 47 | description: Pull requests that update Python code 48 | color: 2b67c6 49 | - name: question 50 | description: Further information is requested 51 | color: d876e3 52 | - name: refactoring 53 | description: Refactoring 54 | color: ef67c4 55 | - name: removal 56 | description: Removals and Deprecations 57 | color: 9ae7ea 58 | - name: style 59 | description: Style 60 | color: c120e5 61 | - name: testing 62 | description: Testing 63 | color: b1fc6f 64 | - name: wontfix 65 | description: This will not be worked on 66 | color: ffffff 67 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: ":boom: Breaking Changes" 3 | label: "breaking" 4 | - title: ":rocket: Features" 5 | label: "enhancement" 6 | - title: ":fire: Removals and Deprecations" 7 | label: "removal" 8 | - title: ":beetle: Fixes" 9 | label: "bug" 10 | - title: ":racehorse: Performance" 11 | label: "performance" 12 | - title: ":rotating_light: Testing" 13 | label: "testing" 14 | - title: ":construction_worker: Continuous Integration" 15 | label: "ci" 16 | - title: ":books: Documentation" 17 | label: "documentation" 18 | - title: ":hammer: Refactoring" 19 | label: "refactoring" 20 | - title: ":lipstick: Style" 21 | label: "style" 22 | - title: ":package: Dependencies" 23 | labels: 24 | - "dependencies" 25 | - "build" 26 | template: | 27 | ## Changes 28 | 29 | $CHANGES 30 | -------------------------------------------------------------------------------- /.github/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==22.2.2 2 | nox==2022.8.7 3 | nox-poetry==1.0.1 4 | poetry==1.1.15 5 | virtualenv==20.16.5 6 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | labeler: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Run Labeler 17 | uses: crazy-max/ghaction-github-labeler@v4.0.0 18 | with: 19 | skip-delete: true 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out the repository 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.10" 23 | 24 | - name: Upgrade pip 25 | run: | 26 | pip install --constraint=.github/workflows/constraints.txt pip 27 | pip --version 28 | 29 | - name: Install Poetry 30 | run: | 31 | pip install --constraint=.github/workflows/constraints.txt poetry 32 | poetry --version 33 | 34 | - name: Check if there is a parent commit 35 | id: check-parent-commit 36 | run: | 37 | echo "::set-output name=sha::$(git rev-parse --verify --quiet HEAD^)" 38 | 39 | - name: Detect and tag new version 40 | id: check-version 41 | if: steps.check-parent-commit.outputs.sha 42 | uses: salsify/action-detect-and-tag-new-version@v2.0.1 43 | with: 44 | version-command: | 45 | bash -o pipefail -c "poetry version | awk '{ print \$2 }'" 46 | 47 | - name: Bump version for developmental release 48 | if: "! steps.check-version.outputs.tag" 49 | run: | 50 | poetry version patch && 51 | version=$(poetry version | awk '{ print $2 }') && 52 | poetry version $version.dev.$(date +%s) 53 | 54 | - name: Build package 55 | run: | 56 | poetry build --ansi 57 | 58 | - name: Publish package on PyPI 59 | if: steps.check-version.outputs.tag 60 | uses: pypa/gh-action-pypi-publish@v1.5.1 61 | with: 62 | user: __token__ 63 | password: ${{ secrets.PYPI_TOKEN }} 64 | 65 | - name: Publish package on TestPyPI 66 | if: "! steps.check-version.outputs.tag" 67 | uses: pypa/gh-action-pypi-publish@v1.5.1 68 | with: 69 | user: __token__ 70 | password: ${{ secrets.TEST_PYPI_TOKEN }} 71 | repository_url: https://test.pypi.org/legacy/ 72 | 73 | - name: Publish the release notes 74 | uses: release-drafter/release-drafter@v5.21.0 75 | with: 76 | publish: ${{ steps.check-version.outputs.tag != '' }} 77 | tag: ${{ steps.check-version.outputs.tag }} 78 | env: 79 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 80 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | inputs: 8 | debug_enabled: 9 | description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" 10 | required: false 11 | default: false 12 | 13 | jobs: 14 | tests: 15 | name: ${{ matrix.session }} ${{ matrix.python }} / ${{ matrix.os }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | - { python: "3.10", os: "ubuntu-latest", session: "pre-commit" } 22 | - { python: "3.10", os: "ubuntu-latest", session: "safety" } 23 | - { python: "3.10", os: "ubuntu-latest", session: "mypy" } 24 | - { python: 3.9, os: "ubuntu-latest", session: "mypy" } 25 | - { python: 3.8, os: "ubuntu-latest", session: "mypy" } 26 | - { python: "3.10", os: "ubuntu-latest", session: "tests" } 27 | - { python: 3.9, os: "ubuntu-latest", session: "tests" } 28 | - { python: 3.8, os: "ubuntu-latest", session: "tests" } 29 | - { python: "3.10", os: "windows-latest", session: "tests" } 30 | - { python: "3.10", os: "macos-latest", session: "tests" } 31 | - { python: "3.10", os: "ubuntu-latest", session: "typeguard" } 32 | - { python: "3.10", os: "ubuntu-latest", session: "xdoctest" } 33 | - { python: "3.10", os: "ubuntu-latest", session: "docs-build" } 34 | 35 | env: 36 | NOXSESSION: ${{ matrix.session }} 37 | FORCE_COLOR: "1" 38 | PRE_COMMIT_COLOR: "always" 39 | 40 | steps: 41 | - name: Check out the repository 42 | uses: actions/checkout@v3 43 | 44 | - name: Set up Python ${{ matrix.python }} 45 | uses: actions/setup-python@v4 46 | with: 47 | python-version: ${{ matrix.python }} 48 | 49 | - name: Upgrade pip 50 | run: | 51 | pip install --constraint=.github/workflows/constraints.txt pip 52 | pip --version 53 | 54 | - name: Upgrade pip in virtual environments 55 | shell: python 56 | run: | 57 | import os 58 | import pip 59 | 60 | with open(os.environ["GITHUB_ENV"], mode="a") as io: 61 | print(f"VIRTUALENV_PIP={pip.__version__}", file=io) 62 | 63 | - name: Install Poetry 64 | run: | 65 | pipx install --pip-args=--constraint=.github/workflows/constraints.txt poetry 66 | poetry --version 67 | 68 | - name: Install Nox 69 | run: | 70 | pipx install --pip-args=--constraint=.github/workflows/constraints.txt nox 71 | pipx inject --pip-args=--constraint=.github/workflows/constraints.txt nox nox-poetry 72 | nox --version 73 | 74 | - name: Compute pre-commit cache key 75 | if: matrix.session == 'pre-commit' 76 | id: pre-commit-cache 77 | shell: python 78 | run: | 79 | import hashlib 80 | import sys 81 | 82 | python = "py{}.{}".format(*sys.version_info[:2]) 83 | payload = sys.version.encode() + sys.executable.encode() 84 | digest = hashlib.sha256(payload).hexdigest() 85 | result = "${{ runner.os }}-{}-{}-pre-commit".format(python, digest[:8]) 86 | 87 | print("::set-output name=result::{}".format(result)) 88 | 89 | - name: Restore pre-commit cache 90 | uses: actions/cache@v3 91 | if: matrix.session == 'pre-commit' 92 | with: 93 | path: ~/.cache/pre-commit 94 | key: ${{ steps.pre-commit-cache.outputs.result }}-${{ hashFiles('.pre-commit-config.yaml') }} 95 | restore-keys: | 96 | ${{ steps.pre-commit-cache.outputs.result }}- 97 | 98 | - name: Run Nox 99 | run: | 100 | nox --python=${{ matrix.python }} 101 | 102 | - name: Setup tmate session 103 | uses: mxschmitt/action-tmate@v3 104 | with: 105 | limit-access-to-actor: true 106 | if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled && failure()}} 107 | 108 | - name: Upload coverage data 109 | if: always() && matrix.session == 'tests' 110 | uses: "actions/upload-artifact@v3" 111 | with: 112 | name: coverage-data 113 | path: ".coverage.*" 114 | 115 | - name: Upload documentation 116 | if: matrix.session == 'docs-build' 117 | uses: actions/upload-artifact@v3 118 | with: 119 | name: docs 120 | path: docs/_build 121 | 122 | coverage: 123 | runs-on: ubuntu-latest 124 | needs: tests 125 | steps: 126 | - name: Check out the repository 127 | uses: actions/checkout@v3 128 | 129 | - name: Set up Python 130 | uses: actions/setup-python@v4 131 | with: 132 | python-version: "3.10" 133 | 134 | - name: Upgrade pip 135 | run: | 136 | pip install --constraint=.github/workflows/constraints.txt pip 137 | pip --version 138 | 139 | - name: Install Poetry 140 | run: | 141 | pipx install --pip-args=--constraint=.github/workflows/constraints.txt poetry 142 | poetry --version 143 | 144 | - name: Install Nox 145 | run: | 146 | pipx install --pip-args=--constraint=.github/workflows/constraints.txt nox 147 | pipx inject --pip-args=--constraint=.github/workflows/constraints.txt nox nox-poetry 148 | nox --version 149 | 150 | - name: Download coverage data 151 | uses: actions/download-artifact@v3 152 | with: 153 | name: coverage-data 154 | 155 | - name: Combine coverage data and display human readable report 156 | run: | 157 | nox --session=coverage 158 | 159 | - name: Create coverage report 160 | run: | 161 | nox --session=coverage -- xml 162 | 163 | - name: Upload coverage report 164 | uses: codecov/codecov-action@v3 165 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | .mypy_cache/ 3 | .tryceratops-errors.log 4 | /.coverage 5 | /.coverage.* 6 | /coverage.xml 7 | /.nox/ 8 | /.python-version 9 | /.pytype/ 10 | /scratch/ 11 | /.virtual_documents/ 12 | /dist/ 13 | /docs/_build/ 14 | /src/*.egg-info/ 15 | /htmlcov 16 | __pycache__/ 17 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: black 5 | name: black 6 | entry: black 7 | language: system 8 | types: [python, jupyter] 9 | require_serial: true 10 | - id: check-added-large-files 11 | name: Check for added large files 12 | entry: check-added-large-files 13 | language: system 14 | exclude: docs/_static/images/hero_image.png 15 | - id: check-toml 16 | name: Check Toml 17 | entry: check-toml 18 | language: system 19 | types: [toml] 20 | - id: check-yaml 21 | name: Check Yaml 22 | entry: check-yaml 23 | language: system 24 | types: [yaml] 25 | - id: darglint 26 | name: darglint 27 | entry: darglint 28 | language: system 29 | types: [python] 30 | stages: [manual] 31 | - id: end-of-file-fixer 32 | name: Fix End of Files 33 | entry: end-of-file-fixer 34 | language: system 35 | types: [text] 36 | stages: [commit, push, manual] 37 | - id: flake8 38 | name: flake8 39 | entry: flake8 40 | language: system 41 | types: [python] 42 | require_serial: true 43 | args: [--darglint-ignore-regex, .*] 44 | - id: autoflake 45 | name: autoflake 46 | entry: autoflake 47 | language: system 48 | types: [python] 49 | require_serial: true 50 | args: ["--in-place", "--remove-all-unused-imports"] 51 | - id: interrogate 52 | name: interrogate 53 | entry: interrogate 54 | language: system 55 | types: [python] 56 | - id: isort 57 | name: isort 58 | entry: isort 59 | require_serial: true 60 | language: system 61 | types_or: [cython, pyi, python] 62 | args: ["--filter-files"] 63 | - id: nbqa-flake8 64 | name: nbqa-flake8 65 | entry: nbqa flake8 66 | language: system 67 | require_serial: true 68 | types: [jupyter] 69 | - id: nbqa-pyupgrade 70 | name: nbqa-pyupgrade 71 | entry: nbqa pyupgrade 72 | language: system 73 | require_serial: true 74 | types: [jupyter] 75 | - id: nbqa-isort 76 | name: nbqa-isort 77 | entry: nbqa isort 78 | language: system 79 | require_serial: true 80 | types: [jupyter] 81 | args: [--project=model, --project=plots] 82 | - id: pyupgrade 83 | name: pyupgrade 84 | description: Automatically upgrade syntax for newer versions. 85 | entry: pyupgrade 86 | language: system 87 | types: [python] 88 | args: [--py38-plus] 89 | - id: trailing-whitespace 90 | name: Trim Trailing Whitespace 91 | entry: trailing-whitespace-fixer 92 | language: system 93 | types: [text] 94 | stages: [commit, push, manual] 95 | - repo: https://github.com/pre-commit/mirrors-prettier 96 | rev: v2.3.0 97 | hooks: 98 | - id: prettier 99 | exclude: "docs/_static/generated_html/.*vega.html" 100 | exclude: "^tests/unit/expected_outputs" 101 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-20.04 4 | tools: 5 | python: "3.10" 6 | sphinx: 7 | configuration: docs/conf.py 8 | formats: all 9 | python: 10 | install: 11 | - requirements: docs/requirements.txt 12 | - path: . 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | - Demonstrating empathy and kindness toward other people 14 | - Being respectful of differing opinions, viewpoints, and experiences 15 | - Giving and gracefully accepting constructive feedback 16 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | - Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | - The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | - Trolling, insulting or derogatory comments, and personal or political attacks 24 | - Public or private harassment 25 | - Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | - Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at . 78 | 79 | Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | For answers to common questions about this code of conduct, see the FAQ at 82 | . Translations are available at . 83 | 84 | [homepage]: https://www.contributor-covenant.org 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor guide 2 | 3 | Thank you for your interest in improving this project. 4 | This project is open-source under the [MIT license] and 5 | welcomes contributions in the form of bug reports, feature requests, and pull requests. 6 | 7 | Here is a list of important resources for contributors: 8 | 9 | - [Source Code] 10 | - [Documentation] 11 | - [Issue Tracker] 12 | - [Code of Conduct] 13 | 14 | ## How to report a bug 15 | 16 | Report bugs on the [Issue Tracker]. 17 | 18 | When filing an issue, make sure to answer these questions: 19 | 20 | - Which operating system and Python version are you using? 21 | - Which version of this project are you using? 22 | - What did you do? 23 | - What did you expect to see? 24 | - What did you see instead? 25 | 26 | The best way to get your bug fixed is to provide a test case, 27 | and/or steps to reproduce the issue. 28 | 29 | ## How to request a feature 30 | 31 | Request features on the [Issue Tracker]. 32 | 33 | ## How to set up your development environment 34 | 35 | You need Python 3.8+ and the following tools: 36 | 37 | - [Poetry] 38 | - [Nox] 39 | - [nox-poetry] 40 | 41 | Install the package with development requirements: 42 | 43 | ```console 44 | $ poetry install 45 | ``` 46 | 47 | You can now run an interactive Python session, 48 | or the command-line interface: 49 | 50 | ```console 51 | $ poetry run python 52 | $ poetry run nbpreview 53 | ``` 54 | 55 | ## How to test the project 56 | 57 | Run the full test suite: 58 | 59 | ```console 60 | $ nox 61 | ``` 62 | 63 | List the available Nox sessions: 64 | 65 | ```console 66 | $ nox --list-sessions 67 | ``` 68 | 69 | You can also run a specific Nox session. 70 | For example, invoke the unit test suite like this: 71 | 72 | ```console 73 | $ nox --session=tests 74 | ``` 75 | 76 | Unit tests are located in the `tests` directory, 77 | and are written using the [pytest] testing framework. 78 | 79 | ## How to submit changes 80 | 81 | Open a [pull request] to submit changes to this project. 82 | 83 | Your pull request needs to meet the following guidelines for acceptance: 84 | 85 | - The Nox test suite must pass without errors and warnings. 86 | - Include unit tests. This project maintains 100% code coverage. 87 | - If your changes add functionality, update the documentation accordingly. 88 | 89 | Feel free to submit early, though—we can always iterate on this. 90 | 91 | To run linting and code formatting checks before committing your change, you can install pre-commit as a Git hook by running the following command: 92 | 93 | ```console 94 | $ nox --session=pre-commit -- install 95 | ``` 96 | 97 | It is recommended to open an issue before starting work on anything. 98 | This will allow a chance to talk it over with the owners and validate your approach. 99 | 100 | [documentation]: https://nbpreview.readthedocs.io/ 101 | [issue tracker]: https://github.com/paw-lu/nbpreview/issues 102 | [mit license]: https://opensource.org/licenses/MIT 103 | [nox]: https://nox.thea.codes/ 104 | [nox-poetry]: https://nox-poetry.readthedocs.io/ 105 | [poetry]: https://python-poetry.org/ 106 | [pull request]: https://github.com/paw-lu/nbpreview/pulls 107 | [pytest]: https://pytest.readthedocs.io/ 108 | [source code]: https://github.com/paw-lu/nbpreview 109 | 110 | 111 | 112 | [code of conduct]: CODE_OF_CONDUCT.md 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Paulo S. Costa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | coverage: 3 | status: 4 | project: 5 | default: 6 | target: "100" 7 | patch: 8 | default: 9 | target: "100" 10 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"); 2 | @font-face { 3 | font-family: "Fira Code"; 4 | src: url("../fonts/FiraCode-Regular.woff2") format("woff2"), 5 | url("../fonts/FiraCode-Regular.woff") format("woff"); 6 | font-weight: 400; 7 | font-style: normal; 8 | } 9 | @font-face { 10 | font-family: "Fira Code"; 11 | src: url("../fonts/FiraCode-Bold.woff2") format("woff2"), 12 | url("../fonts/FiraCode-Bold.woff") format("woff"); 13 | font-weight: 700; 14 | font-style: bold; 15 | } 16 | h1 { 17 | font-size: 2.8125rem; 18 | font-weight: 400; 19 | letter-spacing: 0rem; 20 | line-height: 3.25rem; 21 | margin-block-end: 2px; 22 | margin-block-start: 0px; 23 | margin-bottom: 2px; 24 | margin-inline-end: 0px; 25 | margin-inline-start: 0px; 26 | margin-left: 0px; 27 | margin-right: 0px; 28 | margin-top: 0px; 29 | text-decoration-line: none; 30 | text-decoration-style: solid; 31 | text-decoration-thickness: auto; 32 | text-transform: none; 33 | padding-left: 0px; 34 | } 35 | h2 { 36 | font-size: 2rem; 37 | font-weight: 400; 38 | line-height: 2.5rem; 39 | letter-spacing: 0rem; 40 | margin-block-end: 1rem; 41 | margin-block-start: 0px; 42 | margin-bottom: 1rem; 43 | margin-inline-end: 0px; 44 | margin-inline-start: 0px; 45 | margin-left: 0px; 46 | margin-right: 0px; 47 | margin-top: 0px; 48 | position: relative; 49 | text-decoration-line: none; 50 | text-decoration-style: solid; 51 | text-decoration-thickness: auto; 52 | text-transform: none; 53 | padding-left: 0px; 54 | } 55 | h3 { 56 | font-size: 1.5rem; 57 | font-weight: 400; 58 | letter-spacing: 0rem; 59 | line-height: 2rem; 60 | margin-block-end: 2px; 61 | margin-block-start: 0px; 62 | margin-bottom: 2px; 63 | margin-inline-end: 0px; 64 | margin-inline-start: 0px; 65 | margin-left: 0px; 66 | margin-right: 0px; 67 | margin-top: 0px; 68 | text-decoration-line: none; 69 | text-decoration-style: solid; 70 | text-decoration-thickness: auto; 71 | text-transform: none; 72 | padding-left: 0px; 73 | } 74 | h4 { 75 | font-size: 1.375rem; 76 | font-weight: 400; 77 | letter-spacing: 0rem; 78 | line-height: 1.75rem; 79 | margin-block-end: 2rem; 80 | margin-block-start: 0px; 81 | margin-bottom: 2rem; 82 | margin-inline-end: 0px; 83 | margin-inline-start: 0px; 84 | margin-left: 0px; 85 | margin-right: 0px; 86 | margin-top: 0px; 87 | pointer-events: auto; 88 | text-decoration-line: none; 89 | text-decoration-style: solid; 90 | text-decoration-thickness: auto; 91 | text-transform: none; 92 | padding-left: 0px; 93 | } 94 | .content { 95 | font-size: 1rem; 96 | font-weight: 500; 97 | line-height: 1.5rem; 98 | letter-spacing: 0.01rem; 99 | margin-block-end: 1rem; 100 | margin-block-start: 1rem; 101 | margin-bottom: 1rem; 102 | margin-inline-end: 0px; 103 | margin-inline-start: 0px; 104 | margin-left: 0px; 105 | margin-right: 0px; 106 | margin-top: 1rem; 107 | text-decoration-line: none; 108 | text-decoration-style: solid; 109 | text-decoration-thickness: auto; 110 | } 111 | .footnote { 112 | font-size: 0.875rem; 113 | font-weight: 500; 114 | line-height: 1.25rem; 115 | letter-spacing: 0.015rem; 116 | } 117 | p.admonition-title { 118 | font-size: 1rem; 119 | font-weight: 700; 120 | } 121 | .reference:hover { 122 | border-radius: 18px; 123 | } 124 | .sig:hover { 125 | border-radius: 18px; 126 | } 127 | .sidebar-drawer { 128 | border-right-color: transparent; 129 | } 130 | .sidebar-container { 131 | padding-left: 4px; 132 | padding-right: 4px; 133 | } 134 | .sidebar-search { 135 | border-left: 1px solid var(--color-sidebar-search-border); 136 | border-right: 1px solid var(--color-sidebar-search-border); 137 | border-radius: 18px; 138 | } 139 | .terminal-container { 140 | display: flex; 141 | font-weight: 400; 142 | letter-spacing: 0.114286px; 143 | line-height: 24px; 144 | margin-bottom: 16px; 145 | margin-left: 0px; 146 | margin-right: 0px; 147 | margin-top: 16px; 148 | text-decoration-thickness: auto; 149 | text-size-adjust: 100%; 150 | } 151 | .highlight, 152 | .terminal { 153 | border-bottom-style: none; 154 | border-bottom-width: 0px; 155 | border-image-outset: 0; 156 | border-image-repeat: stretch; 157 | border-image-slice: 100%; 158 | border-image-source: none; 159 | border-image-width: 1; 160 | border-left-style: none; 161 | border-left-width: 0px; 162 | border-right-style: none; 163 | border-right-width: 0px; 164 | border-top-style: none; 165 | border-top-width: 0px; 166 | box-sizing: border-box; 167 | cursor: text; 168 | direction: ltr; 169 | display: block; 170 | font-size: 14px; 171 | font-stretch: 100%; 172 | font-style: normal; 173 | font-variant-caps: normal; 174 | font-variant-east-asian: normal; 175 | font-variant-ligatures: normal; 176 | font-variant-numeric: normal; 177 | font-weight: 400; 178 | line-height: 16px; 179 | margin-bottom: 0px; 180 | margin-left: 0px; 181 | margin-right: 0px; 182 | margin-top: 0px; 183 | min-height: 1px; 184 | padding-left: 0px; 185 | padding-right: 0px; 186 | border-radius: 8px; 187 | overflow: auto; 188 | } 189 | .terminal { 190 | width: 100%; 191 | } 192 | .dark-terminal { 193 | color: #e9e9f4; 194 | background-color: #263238; 195 | } 196 | .light-terminal { 197 | color: #90a4ae; 198 | background-color: #fafafa; 199 | } 200 | pre[id^="codecell"], 201 | .terminal-content { 202 | font-size: 14px; 203 | font-stretch: 100%; 204 | font-style: normal; 205 | font-variant-caps: normal; 206 | font-variant-east-asian: normal; 207 | font-variant-ligatures: contextual; 208 | font-variant-numeric: normal; 209 | font-weight: 400; 210 | margin-left: 0px; 211 | margin-right: 0px; 212 | padding-bottom: 16px; 213 | padding-left: 24px; 214 | padding-right: 4px; 215 | padding-top: 16px; 216 | position: relative; 217 | vertical-align: baseline; 218 | overflow: auto; 219 | } 220 | .terminal .top { 221 | padding: 10px; 222 | border-radius: 5px 5px 0 0; 223 | } 224 | .terminal .buttons { 225 | top: 7px; 226 | left: 5px; 227 | } 228 | .terminal .circle { 229 | width: 12px; 230 | height: 12px; 231 | display: inline-block; 232 | border-radius: 15px; 233 | margin-left: 4px; 234 | } 235 | .dark-red { 236 | background: #f07178; 237 | } 238 | .light-red { 239 | background: #e53935; 240 | } 241 | .dark-green { 242 | background: #c3e88d; 243 | } 244 | .light-green { 245 | background: #91b859; 246 | } 247 | .dark-yellow { 248 | background: #ffcb6b; 249 | } 250 | .light-yellow { 251 | background: #e2931d; 252 | } 253 | -------------------------------------------------------------------------------- /docs/_static/examples/html/cell_magic_syntax_highlight.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [ ]:%%bash                                                                 │
16 |      │ for file in *.csv; do                                                  │
17 |      │     echo "$file"                                                       │
18 |      │     awk -F ',' '{print $5}' "$file" | sort | uniq -c                   │
19 |      │ done                                                                   │
20 |      ╰────────────────────────────────────────────────────────────────────────╯
21 | 
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /docs/_static/examples/html/html.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material notebook.ipynb
14 |       ╭───────────────────────────────────────────────────────────────────────╮
15 | [15]:%%html                                                                │
16 |       │ <p>                                                                   │
17 |       │     Lorem <em>ipsum</em> dolor sit amet,                              │
18 |       │     consectetur adipiscing elit,                                      │
19 |       │     sed do eiusmod tempor                                             │
20 |       │     <q>incididunt ut labore et dolore magna aliqua.</q>               │
21 |       │ </p>                                                                  │
22 |       │ <p>                                                                   │
23 |       │     Sit amet consectetur <b>adipiscing</b> elit pellentesque habitan… │
24 |       │ </p>                                                                  │
25 |       ╰───────────────────────────────────────────────────────────────────────╯
26 | 
27 |        🌐 Click to view HTML
28 | 
29 |        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
30 |        tempor "incididunt ut labore et dolore magna aliqua."
31 | 
32 |        Sit amet consectetur adipiscing elit pellentesque habitant.
33 | 
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /docs/_static/examples/html/julia_syntax_highlight.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [2]:function printx(x)                                                     │
16 |      │     println("x = $x")                                                  │
17 |      │     return nothing                                                     │
18 |      │ end                                                                    │
19 |      ╰────────────────────────────────────────────────────────────────────────╯
20 | 
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /docs/_static/examples/html/long_code.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material --code-wrap --line-numbers notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [5]:1 (                                                                  │
16 |      │   2     df.loc[lambda _df: (_df["sepal.length"] < 6.0) &               │
17 |      │     (_df["petal.length"] < 3.5)]                                       │
18 |      │   3     .groupby("variety")["petal.width"]                             │
19 |      │   4     .mean()                                                        │
20 |      │   5 )                                                                  │
21 |      ╰────────────────────────────────────────────────────────────────────────╯
22 | 
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /docs/_static/examples/html/stderr.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [5]:with pm.Model() as model:                                              │
16 |      │     pm.Normal("normal", mu=0, sd=1)                                    │
17 |      │     trace = pm.sample(return_inferencedata=True)                       │
18 |      ╰────────────────────────────────────────────────────────────────────────╯
19 | 
20 |                                                                                
21 |        Auto-assigning NUTS sampler...                                          
22 |        Initializing NUTS using jitter+adapt_diag...                            
23 |        Multiprocess sampling (4 chains in 4 jobs)                              
24 |        NUTS: [normal]                                                          
25 |                                                                                
26 | 
27 |       🌐 Click to view HTML
28 | 
29 |       100.00% [8000/8000 00:01<00:00 Sampling 4 chains, 0 divergences]
30 | 
31 |                                                                                
32 |        Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 +     
33 |        4_000 draws total) took 12 seconds.                                     
34 |                                                                                
35 | 
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /docs/_static/examples/html/theme_monokai.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme monokai notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [ ]:from typing import Iterator                                            │
16 |      │                                                                        │
17 |      │                                                                        │
18 |      │ class Math:                                                            │
19 |      │     """An example class."""                                            │
20 |      │                                                                        │
21 |      │     @staticmethod                                                      │
22 |      │     def fib(n: int) -> Iterator[int]:                                  │
23 |      │         """Fibonacci series up to n."""                                │
24 |      │         a, b = 0, 1  # Manually set first two terms                    │
25 |      │         while a < n:                                                   │
26 |      │             yield a                                                    │
27 |      │             a, b = b, a + b                                            │
28 |      │                                                                        │
29 |      │                                                                        │
30 |      │ result = sum(Math.fib(42))                                             │
31 |      │ print(f"The answer is {result}")                                       │
32 |      ╰────────────────────────────────────────────────────────────────────────╯
33 | 
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /docs/_static/examples/html/traceback.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
% nbpreview --theme material notebook.ipynb
14 |      ╭────────────────────────────────────────────────────────────────────────╮
15 | [1]:1 / 0                                                                  │
16 |      ╰────────────────────────────────────────────────────────────────────────╯
17 | 
18 |       ------------------------------------------------------------------------…
19 |       ZeroDivisionError                         Traceback (most recent call
20 |       last)
21 |       <ipython-input-1-bc757c3fda29> in <module>
22 |       ----> 1 1 / 0
23 | 
24 |       ZeroDivisionError: division by zero
25 | 
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /docs/_static/examples/svg/cell_magic_syntax_highlight.svg: -------------------------------------------------------------------------------- 1 | 3 | 96 | 97 | 98 |
99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 |
Rich
107 |
108 |
109 |
% nbpreview --theme material notebook.ipynb
110 |
╭────────────────────────────────────────────────────────────────────────╮
111 |
[ ]:%%bash
112 |
for file in *.csv; do
113 |
echo "$file"
114 |
awk -F ',' '{print $5}' "$file" | sort | uniq -c
115 |
done
116 |
╰────────────────────────────────────────────────────────────────────────╯
117 |
118 |
119 |
120 |
121 | 122 |
123 |
124 | -------------------------------------------------------------------------------- /docs/_static/examples/svg/julia_syntax_highlight.svg: -------------------------------------------------------------------------------- 1 | 3 | 96 | 97 | 98 |
99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 |
Rich
107 |
108 |
109 |
% nbpreview --theme material notebook.ipynb
110 |
╭────────────────────────────────────────────────────────────────────────╮
111 |
[2]:function printx(x)
112 |
println("x = $x")
113 |
return nothing
114 |
end
115 |
╰────────────────────────────────────────────────────────────────────────╯
116 |
117 |
118 |
119 |
120 | 121 |
122 |
123 | -------------------------------------------------------------------------------- /docs/_static/fonts/FiraCode-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/fonts/FiraCode-Bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/FiraCode-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/fonts/FiraCode-Bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/FiraCode-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/fonts/FiraCode-Regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/FiraCode-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/fonts/FiraCode-Regular.woff2 -------------------------------------------------------------------------------- /docs/_static/generated_html/dataframe.html: -------------------------------------------------------------------------------- 1 |
2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
Model:Decision TreeRegression
Predicted:TumourNon-TumourTumourNon-Tumour
Actual Label:
Tumour (Positive)38.02.018.022.0
Non-Tumour (Negative)19.0439.06.0452.0
58 |
59 | -------------------------------------------------------------------------------- /docs/_static/generated_html/example_notebook_dataframe.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
mean_lengthmedian_lengthmean_stdmeasurement_count
Month
049.45833349.251.82428548
462.06060662.502.54876733
868.80952469.002.67771442
1274.45652274.502.54912223
63 |
64 | -------------------------------------------------------------------------------- /docs/_static/generated_html/example_notebook_posterior_predictive_progress_bar.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 21 | 100.00% [4000/4000 00:00<00:00] 22 |
23 | -------------------------------------------------------------------------------- /docs/_static/generated_html/example_notebook_sampling_progress_bar.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 21 | 100.00% [12000/12000 00:07<00:00 Sampling 4 chains, 0 divergences] 22 |
23 | -------------------------------------------------------------------------------- /docs/_static/generated_html/html.html: -------------------------------------------------------------------------------- 1 |

2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 3 | eiusmod tempor 4 | incididunt ut labore et dolore magna aliqua. 5 |

6 |

Sit amet consectetur adipiscing elit pellentesque habitant.

7 | -------------------------------------------------------------------------------- /docs/_static/generated_html/links_table.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
sepalLengthsepalWidthpetalLengthpetalWidthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa
69 |
70 | -------------------------------------------------------------------------------- /docs/_static/generated_html/links_vega.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vega chart 8 | 9 | 10 | 11 | {"$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"url": "https://raw.githubusercontent.com/vega/vega-datasets/next/data/unemployment-across-industries.json"}, "encoding": {"color": {"field": "series", "scale": {"scheme": "category20b"}}, "x": {"axis": {"format": "%Y"}, "field": "date", "timeUnit": "yearmonth"}, "y": {"aggregate": "sum", "field": "count"}}, "height": 200, "mark": "area", "width": 300} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/_static/generated_html/stderr.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 21 | 100.00% [8000/8000 00:01<00:00 Sampling 4 chains, 0 divergences] 22 |
23 | -------------------------------------------------------------------------------- /docs/_static/generated_html/vega.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vega chart 8 | 9 | 10 | 11 | {"$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"url": "https://raw.githubusercontent.com/vega/vega-datasets/next/data/stocks.csv"}, "description": "Google's stock price over time.", "encoding": {"x": {"field": "date", "type": "temporal"}, "y": {"field": "price", "type": "quantitative"}}, "mark": {"color": {"gradient": "linear", "stops": [{"color": "white", "offset": 0}, {"color": "darkgreen", "offset": 1}], "x1": 1, "x2": 1, "y1": 1, "y2": 0}, "line": {"color": "darkgreen"}, "type": "area"}, "transform": [{"filter": "datum.symbol==='GOOG'"}]} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/_static/images/dark_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/dark_plot.png -------------------------------------------------------------------------------- /docs/_static/images/emoji_u1f422.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/emoji_u1f422.png -------------------------------------------------------------------------------- /docs/_static/images/example_notebook_hdi_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/example_notebook_hdi_plot.png -------------------------------------------------------------------------------- /docs/_static/images/example_notebook_length_distribution_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/example_notebook_length_distribution_plot.png -------------------------------------------------------------------------------- /docs/_static/images/example_notebook_length_scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/example_notebook_length_scatter.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /docs/_static/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/favicon/favicon.ico -------------------------------------------------------------------------------- /docs/_static/images/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/_static/images/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/_static/images/hero_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/hero_image.png -------------------------------------------------------------------------------- /docs/_static/images/image_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/image_plot.png -------------------------------------------------------------------------------- /docs/_static/images/links_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/_static/images/links_plot.png -------------------------------------------------------------------------------- /docs/codeofconduct.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CODE_OF_CONDUCT.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | # Configure 2 | 3 | Every option in {program}`nbpreview` has an associated environmental variable 4 | that can be set to provide a default value. 5 | For example, 6 | to set the theme to `'material'`, 7 | run: 8 | 9 | ```console 10 | % nbpreview --theme='material' notebook.ipynb 11 | ``` 12 | 13 | To apply the `'material'` theme 14 | without having to specify it in the {option}`--theme ` option, 15 | set the environmental variable associated with the command-line option. 16 | The environmental variables for each option 17 | are explicitly listed at the end of the [command-line usage]. 18 | They may also be found in the {option}`--help` message under `env var:`. 19 | 20 | ```console 21 | % nbpreview --help 22 | ⋮ 23 | -t, --theme 24 | The theme to use for syntax highlighting. 25 | Call '--list-themes' to preview all 26 | available themes. [env var: 27 | NBPREVIEW_THEME; default: dark] 28 | ``` 29 | 30 | In the case of {option}`--theme `, 31 | the environmental variable is {ref}`NBPREVIEW_THEME `. 32 | Set it by running 33 | 34 | ```console 35 | % export NBPREVIEW_THEME='material' 36 | ``` 37 | 38 | Now, whenever nbpreview is run, 39 | it will automatically set the {option}`--theme ` value to `'material'`. 40 | To set this permanently, 41 | set the environmental variable in the shell's startup file—such as 42 | `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.bash_profile`, etc. 43 | Environmental variables set the new default for nbpreview, 44 | but they can still be overridden anytime by manually the relevant command-line option. 45 | 46 | [command-line usage]: usage 47 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CONTRIBUTING.md 2 | --- 3 | end-before: 4 | --- 5 | ``` 6 | 7 | [code of conduct]: codeofconduct 8 | -------------------------------------------------------------------------------- /docs/credits.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ```{include} ../README.md 4 | --- 5 | start-after: 6 | end-before: 7 | --- 8 | ``` 9 | 10 | [dependencies]: dependencies 11 | -------------------------------------------------------------------------------- /docs/dependencies.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | ```{literalinclude} ../pyproject.toml 4 | --- 5 | language: toml 6 | start-after: dependencies-start 7 | end-before: dependencies-end 8 | --- 9 | ``` 10 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/bash_cell_magic.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": null, 5 | "id": "a942b2f6-670c-4f47-b3be-e677a468b449", 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%%bash\n", 10 | "for file in *.csv; do\n", 11 | " echo \"$file\"\n", 12 | " awk -F ',' '{print $5}' \"$file\" | sort | uniq -c\n", 13 | "done" 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/code.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": null, 5 | "id": "b010271b-f5ee-4d81-aa17-a1c48b701ed0", 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from typing import Iterator\n", 10 | "\n", 11 | "\n", 12 | "class Math:\n", 13 | " \"\"\"An example class.\"\"\"\n", 14 | "\n", 15 | " @staticmethod\n", 16 | " def fib(n: int) -> Iterator[int]:\n", 17 | " \"\"\"Fibonacci series up to n.\"\"\"\n", 18 | " a, b = 0, 1 # Manually set first two terms\n", 19 | " while a < n:\n", 20 | " yield a\n", 21 | " a, b = b, a + b\n", 22 | "\n", 23 | "\n", 24 | "result = sum(Math.fib(42))\n", 25 | "print(f\"The answer is {result}\")" 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/dataframe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 2, 5 | "id": "3057f1a5-85d4-4ab7-ac6e-6cadc0cd2e98", 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "
\n", 12 | "\n", 29 | "\n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | "
Model:Decision TreeRegression
Predicted:TumourNon-TumourTumourNon-Tumour
Actual Label:
Tumour (Positive)38.02.018.022.0
Non-Tumour (Negative)19.0439.06.0452.0
\n", 68 | "
" 69 | ], 70 | "text/plain": [ 71 | "Model: Decision Tree Regression \n", 72 | "Predicted: Tumour Non-Tumour Tumour Non-Tumour\n", 73 | "Actual Label: \n", 74 | "Tumour (Positive) 38.0 2.0 18.0 22.0\n", 75 | "Non-Tumour (Negative) 19.0 439.0 6.0 452.0" 76 | ] 77 | }, 78 | "execution_count": 2, 79 | "metadata": {}, 80 | "output_type": "execute_result" 81 | } 82 | ], 83 | "source": [ 84 | "pd.DataFrame(\n", 85 | " [\n", 86 | " [38.0, 2.0, 18.0, 22.0],\n", 87 | " [19, 439, 6, 452],\n", 88 | " ],\n", 89 | " index=pd.Index(\n", 90 | " [\"Tumour (Positive)\", \"Non-Tumour (Negative)\"],\n", 91 | " name=\"Actual Label:\",\n", 92 | " ),\n", 93 | " columns=pd.MultiIndex.from_product(\n", 94 | " [[\"Decision Tree\", \"Regression\"], [\"Tumour\", \"Non-Tumour\"]],\n", 95 | " names=[\"Model:\", \"Predicted:\"],\n", 96 | " ),\n", 97 | ")" 98 | ] 99 | } 100 | ] 101 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/html.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 15, 5 | "id": "54c45742-5f28-4b50-8c53-913fcb75d623", 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/html": [ 11 | "

\n", 12 | " Lorem ipsum dolor sit amet,\n", 13 | " consectetur adipiscing elit,\n", 14 | " sed do eiusmod tempor\n", 15 | " incididunt ut labore et dolore magna aliqua.\n", 16 | "

\n", 17 | "

\n", 18 | " Sit amet consectetur adipiscing elit pellentesque habitant.\n", 19 | "

\n" 20 | ], 21 | "text/plain": [""] 22 | }, 23 | "metadata": {}, 24 | "output_type": "display_data" 25 | } 26 | ], 27 | "source": [ 28 | "%%html\n", 29 | "

\n", 30 | " Lorem ipsum dolor sit amet,\n", 31 | " consectetur adipiscing elit,\n", 32 | " sed do eiusmod tempor\n", 33 | " incididunt ut labore et dolore magna aliqua.\n", 34 | "

\n", 35 | "

\n", 36 | " Sit amet consectetur adipiscing elit pellentesque habitant.\n", 37 | "

" 38 | ] 39 | } 40 | ] 41 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/julia_code.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 2, 5 | "id": "925471a9-c56e-4e04-8e46-276d62ce00e2", 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "function printx(x)\n", 10 | " println(\"x = $x\")\n", 11 | " return nothing\n", 12 | "end" 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/latex.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 22, 5 | "id": "74fddfe7-a49d-40ad-af6f-0ca5d5d8faab", 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/latex": [ 11 | "$\\text{y_pred} \\sim \\text{Normal}(\\mathit{mu}=\\text{mu},~\\mathit{sigma}=f(\\text{epsilon}))$" 12 | ], 13 | "text/plain": ["y_pred ~ Normal"] 14 | }, 15 | "execution_count": 22, 16 | "metadata": {}, 17 | "output_type": "execute_result" 18 | } 19 | ], 20 | "source": [ 21 | "with pm.Model() as model:\n", 22 | " alpha = pm.Normal(\"alpha\", mu=0, sd=10)\n", 23 | " beta = pm.Normal(\"beta\", mu=0, sd=1)\n", 24 | " epsilon = pm.HalfCauchy(\"epsilon\", beta=5)\n", 25 | "\n", 26 | " mu = pm.Deterministic(\"mu\", var=alpha + beta * x)\n", 27 | " y_pred = pm.Normal(\"y_pred\", mu=mu, sd=epsilon, observed=y)\n", 28 | "y_pred" 29 | ] 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/long_code.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 5, 5 | "id": "74fddfe7-a49d-40ad-af6f-0ca5d5d8faab", 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "(\n", 10 | " df.loc[lambda _df: (_df[\"sepal.length\"] < 6.0) & (_df[\"petal.length\"] < 3.5)]\n", 11 | " .groupby(\"variety\")[\"petal.width\"]\n", 12 | " .mean()\n", 13 | ")" 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/markdown.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "markdown", 4 | "id": "fdc518a2-2c5d-4313-b987-098d90b673da", 5 | "metadata": {}, 6 | "source": [ 7 | "# Lorem ipsum\n", 8 | "\n", 9 | "Lorem ipsum dolor sit amet,\n", 10 | "consectetur **adipiscing** elit,\n", 11 | "sed do eiusmod tempor incididunt\n", 12 | "ut labore et dolore magna [aliqua](https://github.com/paw-lu/nbpreview).\n", 13 | "\n", 14 | "$$\n", 15 | "\\alpha \\sim \\text{Normal(0, 1)}\n", 16 | "$$\n", 17 | "\n", 18 | "\n", 19 | "_Ut enim ad minim veniam_,\n", 20 | "quis nostrud exercitation ullamco\n", 21 | "Excepteur sint occaecat `cupidatat` non proident,\n", 22 | "sunt in culpa qui.\n", 23 | "\n", 24 | "![Turtle](emoji_u1f422.png)\n", 25 | "\n", 26 | "## At ultrices\n", 27 | "\n", 28 | "```python\n", 29 | "def add(x: float, y: float) -> float:\n", 30 | " \"\"\"Add two numbers.\"\"\"\n", 31 | " return x + y\n", 32 | "```\n", 33 | "\n", 34 | "| Lorep | ipsum | doret |\n", 35 | "| ----- | ----- | ----- |\n", 36 | "| 1 | 2 | 3 |\n", 37 | "| 4 | 5 | 6 |" 38 | ] 39 | } 40 | ] 41 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/stderr.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 5, 5 | "id": "03e2bbd7-62bf-4f49-94d2-3ff808ff345c", 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Auto-assigning NUTS sampler...\n", 13 | "Initializing NUTS using jitter+adapt_diag...\n", 14 | "Multiprocess sampling (4 chains in 4 jobs)\n", 15 | "NUTS: [normal]\n" 16 | ] 17 | }, 18 | { 19 | "data": { 20 | "text/html": [ 21 | "\n", 22 | "
\n", 23 | " \n", 35 | " \n", 36 | " 100.00% [8000/8000 00:01<00:00 Sampling 4 chains, 0 divergences]\n", 37 | "
\n", 38 | " " 39 | ], 40 | "text/plain": [""] 41 | }, 42 | "metadata": {}, 43 | "output_type": "display_data" 44 | }, 45 | { 46 | "name": "stderr", 47 | "output_type": "stream", 48 | "text": [ 49 | "Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 12 seconds.\n" 50 | ] 51 | } 52 | ], 53 | "source": [ 54 | "with pm.Model() as model:\n", 55 | " pm.Normal(\"normal\", mu=0, sd=1)\n", 56 | " trace = pm.sample(return_inferencedata=True)" 57 | ] 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/content/traceback.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cell_type": "code", 4 | "execution_count": 1, 5 | "id": "798ad0a3-4c8b-4957-bd3d-8cae4502e09e", 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "ename": "ZeroDivisionError", 10 | "evalue": "division by zero", 11 | "output_type": "error", 12 | "traceback": [ 13 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 14 | "\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", 15 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;36m1\u001b[0m \u001b[1;33m/\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 16 | "\u001b[1;31mZeroDivisionError\u001b[0m: division by zero" 17 | ] 18 | } 19 | ], 20 | "source": ["1 / 0"] 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/model.py: -------------------------------------------------------------------------------- 1 | """Create a model for baby length.""" 2 | import pymc as pm 3 | from pandas import DataFrame 4 | from pymc import Model 5 | 6 | 7 | def model_baby_length(babies_data: DataFrame) -> Model: 8 | """Create model for baby length.""" 9 | with pm.Model(coords={"time_idx": babies_data.index}) as babies_model: 10 | alpha = pm.Normal("alpha", sigma=10) 11 | beta = pm.Normal("beta", sigma=10) 12 | gamma = pm.HalfNormal("gamma", sigma=10) 13 | sigma = pm.HalfNormal("sigma", sigma=10) 14 | 15 | month = pm.MutableData("month", value=babies_data["Month"].astype(float)) 16 | 17 | mu = pm.Deterministic("mu", alpha + beta * month**0.5, dims="time_idx") 18 | epsilon = pm.Deterministic( 19 | "epsilon", 20 | gamma + sigma * month, 21 | dims="time_idx", 22 | ) 23 | 24 | pm.Normal( 25 | "length", 26 | mu=mu, 27 | sigma=epsilon, 28 | observed=babies_data["Length"], 29 | dims="time_idx", 30 | ) 31 | return babies_model 32 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/plots.py: -------------------------------------------------------------------------------- 1 | """Create plots for babies model.""" 2 | from typing import Optional, Sequence, Tuple 3 | 4 | import arviz as az 5 | import matplotlib.pyplot as plt 6 | from arviz import InferenceData 7 | from matplotlib.axes import Axes 8 | from matplotlib.axes._subplots import Subplot 9 | from pandas import DataFrame 10 | 11 | 12 | def plot_length_dist( 13 | babies_idata: InferenceData, 14 | babies_data: DataFrame, 15 | month: int, 16 | ax: Optional[Axes] = None, 17 | color: Optional[str] = None, 18 | ) -> Subplot: 19 | """Plot the length given an age in months.""" 20 | if ax is None: 21 | ax = plt.gca() 22 | 23 | length_data = babies_idata.sel( 24 | time_idx=babies_data.loc[lambda df: df["Month"] == month].index 25 | )["posterior_predictive"].stack(dim=["chain", "draw", "time_idx"])["length"] 26 | plot = az.plot_dist( 27 | length_data, 28 | fill_kwargs={"alpha": 1}, 29 | ax=ax, 30 | color=color, 31 | ) 32 | return plot 33 | 34 | 35 | def make_length_comparison_plot( 36 | babies_idata: InferenceData, 37 | babies_data: DataFrame, 38 | months: Sequence[int], 39 | ax: Optional[Axes] = None, 40 | ) -> Subplot: 41 | """Given a sequence of months, compare their distribution.""" 42 | if ax is None: 43 | ax = plt.gca() 44 | 45 | for idx, month in enumerate(months): 46 | color = f"C{idx}" 47 | plot = plot_length_dist( 48 | babies_idata, babies_data=babies_data, month=month, ax=ax, color=color 49 | ) 50 | 51 | return plot 52 | 53 | 54 | def plot_length_hdi( 55 | babies_data: DataFrame, 56 | babies_idata: InferenceData, 57 | hdi_prob: float = 0.95, 58 | color: str = "C0", 59 | ax: Optional[Axes] = None, 60 | alpha: float = 1.0, 61 | ) -> Subplot: 62 | """Plot HDI intervals for baby length fit.""" 63 | if ax is None: 64 | ax = plt.gca() 65 | 66 | plot = az.plot_hdi( 67 | x=babies_data["Month"], 68 | y=babies_idata["posterior_predictive"]["length"], 69 | hdi_prob=hdi_prob, 70 | color=color, 71 | fill_kwargs={"alpha": alpha}, 72 | ) 73 | return plot 74 | 75 | 76 | def make_length_hdi_plot( 77 | babies_data: DataFrame, 78 | babies_idata: InferenceData, 79 | hdi_probs: Sequence[float], 80 | ax: Optional[Axes] = None, 81 | alpha: float = 1.0, 82 | ) -> Subplot: 83 | """Plot HDI of baby length over age.""" 84 | if ax is None: 85 | ax = plt.gca() 86 | 87 | sorted_hdi_probs = sorted(hdi_probs, reverse=True) 88 | for idx, hdi_prob in enumerate(sorted_hdi_probs): 89 | color = f"C{idx}" 90 | plot = plot_length_hdi( 91 | babies_data, 92 | babies_idata=babies_idata, 93 | hdi_prob=hdi_prob, 94 | color=color, 95 | ax=ax, 96 | alpha=alpha, 97 | ) 98 | 99 | return plot 100 | 101 | 102 | def plot_dist_and_hdi( 103 | babies_data: DataFrame, 104 | babies_idata: InferenceData, 105 | months: Sequence[int], 106 | hdi_probs: Sequence[float], 107 | alpha: float = 1.0, 108 | ) -> Tuple[Subplot, Subplot]: 109 | """Plot selected distributions of babie's height along with HDI of trend.""" 110 | fig, (left_ax, right_ax) = plt.subplots( 111 | figsize=(25, 5), 112 | ncols=2, 113 | facecolor="#1C1B1F", 114 | ) 115 | make_length_comparison_plot( 116 | babies_idata, 117 | babies_data=babies_data, 118 | months=months, 119 | ax=left_ax, 120 | ).set(xticks=[], yticks=[]) 121 | make_length_hdi_plot( 122 | babies_data, 123 | babies_idata=babies_idata, 124 | hdi_probs=[0.50, 0.99], 125 | ax=right_ax, 126 | ).set(xticks=[], yticks=[]) 127 | fig.tight_layout() 128 | return (left_ax, right_ax) 129 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/templates/html_template.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | 8 |
9 |
10 | 11 |
{code}
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /docs/example_notebook_cells/templates/svg_template.jinja: -------------------------------------------------------------------------------- 1 | 3 | 93 | 94 | 95 |
96 |
97 |
98 | 99 | 100 | 101 | 102 | 103 |
{title}
104 |
105 |
106 | {code} 107 |
108 |
109 |
110 | 111 |
112 |
113 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ```{image} _static/images/logo_light.svg 2 | :class: only-light 3 | ``` 4 | 5 | ```{image} _static/images/logo_dark.svg 6 | :class: only-dark 7 | ``` 8 | 9 | ```{include} ../README.md 10 | --- 11 | start-after: (http://mypy-lang.org/) 12 | end-before: 13 | --- 14 | ``` 15 | 16 | ```{raw} html 17 | --- 18 | file: _static/examples/html/example_notebook.html 19 | --- 20 | ``` 21 | 22 | ```{toctree} 23 | --- 24 | hidden: true 25 | maxdepth: 1 26 | --- 27 | 28 | installation 29 | usage 30 | features 31 | configure 32 | reference 33 | contributing 34 | prior_art 35 | credits 36 | dependencies 37 | codeofconduct 38 | license 39 | Issues 40 | Changelog 41 | GitHub Repository 42 | ``` 43 | 44 | [features]: features 45 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ```{include} ../README.md 4 | --- 5 | start-after: 6 | end-before: 7 | --- 8 | ``` 9 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ```{literalinclude} ../LICENSE 4 | --- 5 | language: none 6 | --- 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/nbpreview_logo.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/docs/nbpreview_logo.fig -------------------------------------------------------------------------------- /docs/prior_art.md: -------------------------------------------------------------------------------- 1 | # Prior art 2 | 3 | ## Similar tools 4 | 5 | ```{include} ../README.md 6 | --- 7 | start-after: 8 | end-before: 9 | --- 10 | ``` 11 | 12 | ## Complimentary tools 13 | 14 | ```{include} ../README.md 15 | --- 16 | start-after: 17 | end-before: 18 | --- 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | ```{eval-rst} 4 | .. autoclass:: nbpreview.notebook.Notebook 5 | :members: 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | furo==2022.6.21 2 | myst-parser==0.18.0 3 | sphinx-autodoc-typehints==1.19.2 4 | sphinx-click==4.3.0 5 | sphinx-copybutton==0.5.0 6 | sphinx-design==0.2.0 7 | sphinx-favicon==0.2 8 | sphinx==5.1.1 9 | sphinxext-opengraph==0.6.3 10 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | .. Environmental variable references do not work when this is a markdown file 5 | .. See https://github.com/executablebooks/MyST-Parser/issues/513 6 | 7 | nbpreview has only one required argument—:option:`FILE `—which 8 | expects a Jupyter notebook (``.ipynb``) file path. 9 | :option:`FILE ` is a flexible argument. 10 | It can take: 11 | 12 | * A Jupyter notebook (``ipynb``) file path 13 | * Multiple notebook paths 14 | * Take in input from stdin 15 | 16 | For more details, 17 | see `features`_. 18 | 19 | nbpreview also comes with a convenient alias—``nbp``. 20 | Invoke either ``nbpreview`` 21 | 22 | .. code:: console 23 | 24 | % nbpreview notebook.ipynb 25 | 26 | or ``nbp`` 27 | 28 | .. code:: console 29 | 30 | % nbp notebook.ipynb 31 | 32 | on the command-line to run the program. 33 | 34 | .. option:: --help 35 | 36 | To read the documentation on all options, 37 | their effects, 38 | values, 39 | and environmental variables, 40 | run 41 | 42 | .. code:: console 43 | 44 | % nbpreview --help 45 | 46 | .. click:: nbpreview.__main__:typer_click_object 47 | :prog: nbpreview 48 | :nested: full 49 | 50 | 51 | .. _features: features.html#flexible-file-argument 52 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Merge all open and passing dependabot pull requests 2 | merge_dependabot: 3 | gh pr list --state=open --json=author,number \ 4 | | jq '.[]' \ 5 | | jq 'select(.author.login == "dependabot")' \ 6 | | jq '.number' \ 7 | | parallel "gh pr comment --body='@dependabot merge'" 8 | 9 | # Run the test in the clipboad in debug mode 10 | debug_test: 11 | poetry run pytest --pdb -k $(pbpaste) 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nbpreview" 3 | version = "0.9.1" 4 | description = "nbpreview" 5 | authors = ["Paulo S. Costa "] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/paw-lu/nbpreview" 9 | repository = "https://github.com/paw-lu/nbpreview" 10 | documentation = "https://nbpreview.readthedocs.io" 11 | classifiers = [ 12 | "Development Status :: 4 - Beta", 13 | "Programming Language :: Python :: 3.8", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | ] 17 | 18 | [tool.poetry.urls] 19 | Changelog = "https://github.com/paw-lu/nbpreview/releases" 20 | Issues = "https://github.com/paw-lu/nbpreview/issues" 21 | 22 | # dependencies-start 23 | 24 | [tool.poetry.dependencies] 25 | python = "^3.8.0" 26 | rich = ">=12.4.1" 27 | typer = ">=0.4.1,<0.6.0" 28 | nbformat = { extras = ["fast"], version = ">=5.2.0" } 29 | Pygments = ">=2.10.0" 30 | ipython = ">=7.27,<9.0" 31 | lxml = ">=4.6.3" 32 | pylatexenc = ">=2.10" 33 | httpx = ">=0.19,<0.24" 34 | Jinja2 = ">=3.0.1" 35 | html2text = ">=2020.1.16" 36 | types-click = ">=7.1.5" 37 | Pillow = ">=8.3.1,<10.0.0" 38 | picharsso = ">=2.0.1" 39 | validators = ">=0.18.2,<0.21.0" 40 | yarl = ">=1.6.3" 41 | markdown-it-py = ">=1.1,<3.0" 42 | mdit-py-plugins = ">=0.3.0" 43 | click-help-colors = ">=0.9.1" 44 | term-image = ">=0.3.0" 45 | 46 | [tool.poetry.dev-dependencies] 47 | pytest = ">=7.1.2" 48 | coverage = { extras = ["toml"], version = ">=6.4" } 49 | safety = ">=2.0.0" 50 | mypy = ">=0.961" 51 | typeguard = ">=2.13.3" 52 | xdoctest = { extras = ["colors"], version = ">=1.0.0" } 53 | sphinx = ">=5.0.2" 54 | sphinx-autobuild = ">=2021.3.14" 55 | pre-commit = ">=2.19.0" 56 | flake8 = ">=4.0.1" 57 | black = { extras = ["jupyter"], version = ">=21.12b0" } 58 | flake8-bandit = ">=3.0.0" 59 | flake8-bugbear = ">=22.6.22" 60 | flake8-docstrings = ">=1.5.0" 61 | flake8-rst-docstrings = ">=0.2.6" 62 | pep8-naming = ">=0.13.0" 63 | darglint = ">=1.8.1" 64 | pre-commit-hooks = ">=4.3.0" 65 | sphinx-click = ">=4.2.0" 66 | Pygments = ">=2.10.0" 67 | pyupgrade = ">=2.34.0" 68 | furo = ">=2021.11.12" 69 | pdbpp = ">=0.10.3" 70 | ipykernel = ">=6.15.0" 71 | pytest-mock = ">=3.8.1" 72 | interrogate = ">=1.5.0" 73 | isort = ">=5.10.1" 74 | nbqa = ">=1.3.1" 75 | click = ">=8.1.3" 76 | autoflake = ">=1.4" 77 | myst-parser = ">=0.18.0" 78 | sphinxext-opengraph = ">=0.6.3" 79 | sphinx-copybutton = ">=0.5.0" 80 | sphinx-design = ">=0.2.0" 81 | sphinx-autodoc-typehints = ">=1.18.3" 82 | tomli = ">=2.0.1" 83 | sphinx-favicon = ">=0.2" 84 | 85 | # dependencies-end 86 | 87 | [tool.poetry.scripts] 88 | nbpreview = "nbpreview.__main__:app" 89 | nbp = "nbpreview.__main__:app" 90 | 91 | [tool.coverage.paths] 92 | source = ["src", "*/site-packages"] 93 | tests = ["tests", "*/tests"] 94 | 95 | [tool.coverage.run] 96 | branch = true 97 | source = ["nbpreview", "tests"] 98 | 99 | [tool.coverage.report] 100 | show_missing = true 101 | fail_under = 100 102 | exclude_lines = [ 103 | # Have to re-enable the standard pragma 104 | "pragma: no cover", 105 | # Typing overloads are never run 106 | "@typing.overload", 107 | ] 108 | 109 | [tool.mypy] 110 | strict = true 111 | warn_unreachable = true 112 | pretty = true 113 | show_column_numbers = true 114 | show_error_codes = true 115 | show_error_context = true 116 | 117 | [[tool.mypy.overrides]] 118 | module = "click_help_colors,jsonschema,lxml.*,nbformat.*,PIL.*,picharsso.*,pygments,pylatexenc,pytest_mock,term_image,validators" 119 | ignore_missing_imports = true 120 | 121 | [[tool.mypy.overrides]] 122 | module = "mdit_py_plugins.*" 123 | no_implicit_reexport = false 124 | 125 | [build-system] 126 | requires = ["poetry-core>=1.0.0"] 127 | build-backend = "poetry.core.masonry.api" 128 | 129 | [tool.pytest.ini_options] 130 | markers = [ 131 | "no_atty_mock: Test where sys.stdout.isatty should not be mocked", 132 | "no_typeguard: Tests typeguard should ignore", 133 | ] 134 | norecursedirs = "tests/util" 135 | 136 | [tool.interrogate] 137 | verbose = 2 138 | fail-under = 100 139 | color = true 140 | 141 | [tool.isort] 142 | profile = "black" 143 | 144 | [tool.nbqa.dont_skip_bad_cells] 145 | black = true 146 | flake8 = true 147 | pyupgrade = true 148 | isort = true 149 | 150 | [tool.nbqa.addopts] 151 | flake8 = ["--extend-ignore=D100"] 152 | -------------------------------------------------------------------------------- /src/nbpreview/__init__.py: -------------------------------------------------------------------------------- 1 | """nbpreview.""" 2 | from importlib import metadata 3 | 4 | try: 5 | __version__ = metadata.version(__name__) 6 | except metadata.PackageNotFoundError: # pragma: no cover 7 | __version__ = "unknown" 8 | -------------------------------------------------------------------------------- /src/nbpreview/_color_typer.py: -------------------------------------------------------------------------------- 1 | """Typer intergraged with click-help-colors.""" 2 | from typing import Any, Callable, Dict, Optional, Type 3 | 4 | import click_help_colors 5 | import typer 6 | from click import Command 7 | from typer.models import CommandFunctionType 8 | 9 | 10 | class TyperHelpColorsCommand(click_help_colors.HelpColorsCommand): # type: ignore[misc] 11 | """Hard coded command help colors for all Typer commands.""" 12 | 13 | def __init__(self, *args: Any, **kwargs: Any) -> None: 14 | """Constructor.""" 15 | super().__init__(*args, **kwargs) 16 | self.help_headers_color = "magenta" 17 | self.help_options_color = "cyan" 18 | 19 | 20 | class ColorTyper(typer.Typer): 21 | """Typer with a colorized help.""" 22 | 23 | def command( 24 | self, 25 | name: Optional[str] = None, 26 | *, 27 | cls: Optional[Type[Command]] = TyperHelpColorsCommand, 28 | context_settings: Optional[Dict[Any, Any]] = None, 29 | help: Optional[str] = None, 30 | epilog: Optional[str] = None, 31 | short_help: Optional[str] = None, 32 | options_metavar: str = "[OPTIONS]", 33 | add_help_option: bool = True, 34 | no_args_is_help: bool = False, 35 | hidden: bool = False, 36 | deprecated: bool = False, 37 | ) -> Callable[[CommandFunctionType], CommandFunctionType]: 38 | """Add help colors to the Typer command.""" 39 | return super().command( 40 | name=name, 41 | cls=cls, 42 | context_settings=context_settings, 43 | help=help, 44 | epilog=epilog, 45 | short_help=short_help, 46 | options_metavar=options_metavar, 47 | add_help_option=add_help_option, 48 | no_args_is_help=no_args_is_help, 49 | hidden=hidden, 50 | deprecated=deprecated, 51 | ) 52 | -------------------------------------------------------------------------------- /src/nbpreview/component/__init__.py: -------------------------------------------------------------------------------- 1 | """Individual components of a notebook.""" 2 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/__init__.py: -------------------------------------------------------------------------------- 1 | """Jupyter notebook content.""" 2 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/input.py: -------------------------------------------------------------------------------- 1 | """Input notebook cells.""" 2 | import dataclasses 3 | import functools 4 | from pathlib import Path 5 | from typing import Optional, Union 6 | 7 | import pygments 8 | from rich import console, padding, panel, syntax 9 | from rich.console import Group, RenderableType 10 | from rich.padding import Padding, PaddingDimensions 11 | from rich.syntax import Syntax 12 | 13 | from nbpreview.component import markdown 14 | from nbpreview.component.content.output.result.drawing import ImageDrawing 15 | 16 | 17 | def box_cell( 18 | rendered_source: RenderableType, plain: bool, safe_box: Optional[bool] = None 19 | ) -> RenderableType: 20 | """Wrap the content in a box.""" 21 | if plain: 22 | return rendered_source 23 | else: 24 | boxed_cell = panel.Panel(rendered_source, safe_box=safe_box) 25 | return boxed_cell 26 | 27 | 28 | @dataclasses.dataclass 29 | class Cell: 30 | """A generic Jupyter cell.""" 31 | 32 | source: str 33 | plain: bool 34 | safe_box: Optional[bool] = None 35 | 36 | def __rich__(self) -> RenderableType: 37 | """Render the cell.""" 38 | return box_cell(self.source, plain=self.plain, safe_box=self.safe_box) 39 | 40 | 41 | @dataclasses.dataclass(init=False) 42 | class MarkdownCell(Cell): 43 | """A Jupyter markdown cell.""" 44 | 45 | def __init__( 46 | self, 47 | source: str, 48 | theme: str, 49 | pad: PaddingDimensions, 50 | nerd_font: bool, 51 | unicode: bool, 52 | images: bool, 53 | image_drawing: ImageDrawing, 54 | color: bool, 55 | negative_space: bool, 56 | hyperlinks: bool, 57 | files: bool, 58 | hide_hyperlink_hints: bool, 59 | relative_dir: Path, 60 | characters: Optional[str] = None, 61 | ) -> None: 62 | """Constructor.""" 63 | super().__init__(source, plain=True) 64 | self.theme = theme 65 | self.pad = pad 66 | self.nerd_font = nerd_font 67 | self.unicode = unicode 68 | self.images = images 69 | self.image_drawing = image_drawing 70 | self.color = color 71 | self.negative_space = negative_space 72 | self.hyperlinks = hyperlinks 73 | self.files = files 74 | self.hide_hyperlink_hints = hide_hyperlink_hints 75 | self.characters = characters 76 | self.relative_dir = relative_dir 77 | 78 | def __rich__(self) -> Padding: 79 | """Render the markdown cell.""" 80 | rendered_markdown = padding.Padding( 81 | markdown.CustomMarkdown( 82 | self.source, 83 | theme=self.theme, 84 | nerd_font=self.nerd_font, 85 | unicode=self.unicode, 86 | images=self.images, 87 | image_drawing=self.image_drawing, 88 | color=self.color, 89 | negative_space=self.negative_space, 90 | hyperlinks=self.hyperlinks, 91 | files=self.files, 92 | hide_hyperlink_hints=self.hide_hyperlink_hints, 93 | characters=self.characters, 94 | relative_dir=self.relative_dir, 95 | ), 96 | pad=self.pad, 97 | ) 98 | return rendered_markdown 99 | 100 | 101 | @dataclasses.dataclass(init=False) 102 | class CodeCell(Cell): 103 | """A Jupyter code cell.""" 104 | 105 | def __init__( 106 | self, 107 | source: str, 108 | plain: bool, 109 | theme: str, 110 | default_lexer_name: str, 111 | safe_box: Optional[bool] = None, 112 | line_numbers: bool = False, 113 | code_wrap: bool = False, 114 | ) -> None: 115 | """Constructor.""" 116 | super().__init__(source, plain=plain, safe_box=safe_box) 117 | self.theme = theme 118 | self.default_lexer_name = default_lexer_name 119 | self.line_numbers = line_numbers 120 | self.code_wrap = code_wrap 121 | 122 | def __rich__(self) -> RenderableType: 123 | """Render the code cell.""" 124 | rendered_code_cell: Union[Syntax, Group] 125 | code_cell_renderer = functools.partial( 126 | syntax.Syntax, 127 | lexer=self.default_lexer_name, 128 | theme=self.theme, 129 | background_color="default", 130 | line_numbers=self.line_numbers, 131 | word_wrap=self.code_wrap, 132 | ) 133 | rendered_code_cell = code_cell_renderer(self.source) 134 | if self.source.startswith("%%"): 135 | try: 136 | magic, body = self.source.split("\n", 1) 137 | language_name = magic.lstrip("%") 138 | body_lexer_name = pygments.lexers.get_lexer_by_name(language_name).name 139 | rendered_magic = code_cell_renderer(magic) 140 | rendered_body = code_cell_renderer( 141 | body, lexer=body_lexer_name, start_line=2 142 | ) 143 | rendered_code_cell = console.Group(rendered_magic, rendered_body) 144 | 145 | except pygments.util.ClassNotFound: 146 | pass 147 | return box_cell(rendered_code_cell, plain=self.plain, safe_box=self.safe_box) 148 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/__init__.py: -------------------------------------------------------------------------------- 1 | """Jupyter notebook output data.""" 2 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/error.py: -------------------------------------------------------------------------------- 1 | """Notebook error messages.""" 2 | 3 | 4 | import abc 5 | from typing import Iterator, List 6 | 7 | from nbformat.notebooknode import NotebookNode 8 | from rich import ansi, measure 9 | from rich.console import Console, ConsoleOptions, RenderResult 10 | from rich.measure import Measurement 11 | from rich.text import Text 12 | 13 | 14 | class Error(abc.ABC): 15 | """An error output.""" 16 | 17 | content: List[str] 18 | 19 | def __init__(self, content: List[str]) -> None: 20 | """Constructor.""" 21 | self.content = content 22 | 23 | def __repr__(self) -> str: 24 | """String representation of Error.""" 25 | return f"{self.__class__.__qualname__}(content={self.content})" 26 | 27 | @abc.abstractmethod 28 | def __rich_console__( 29 | self, console: Console, options: ConsoleOptions 30 | ) -> RenderResult: 31 | """Render an error.""" 32 | 33 | @abc.abstractmethod 34 | def __rich_measure__( 35 | self, console: Console, options: ConsoleOptions 36 | ) -> Measurement: 37 | """Define the dimensions of the rendered error.""" 38 | 39 | 40 | def render_error(output: NotebookNode) -> Iterator[Error]: 41 | """Render an error type output. 42 | 43 | Args: 44 | output (NotebookNode): The error output. 45 | 46 | Yields: 47 | Generator[Syntax, None, None]: Generate each row of the 48 | traceback. 49 | """ 50 | if "traceback" in output: 51 | error = Traceback.from_output(output) 52 | yield error 53 | 54 | 55 | class Traceback(Error): 56 | """A traceback output.""" 57 | 58 | def __init__(self, content: List[str]) -> None: 59 | """Constructor.""" 60 | super().__init__(content=content) 61 | decoder = ansi.AnsiDecoder() 62 | self.min_length = 0 63 | self.rendered_traceback = [] 64 | for line in self.content: 65 | rendered_traceback_line = decoder.decode_line(line) 66 | self.min_length = max(self.min_length, rendered_traceback_line.cell_len) 67 | self.rendered_traceback.append(rendered_traceback_line) 68 | 69 | def __rich_console__( 70 | self, console: Console, options: ConsoleOptions 71 | ) -> Iterator[Text]: 72 | """Render the traceback.""" 73 | yield from self.rendered_traceback 74 | 75 | def __rich_measure__( 76 | self, console: Console, options: ConsoleOptions 77 | ) -> Measurement: 78 | """Define the dimensions of the rendered traceback.""" 79 | return measure.Measurement(minimum=self.min_length, maximum=options.max_width) 80 | 81 | @classmethod 82 | def from_output(cls, output: NotebookNode) -> "Traceback": 83 | """Create a traceback from a notebook output.""" 84 | content = output["traceback"] 85 | return cls(content) 86 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/output.py: -------------------------------------------------------------------------------- 1 | """Jupyter notebook output data.""" 2 | from typing import Union 3 | 4 | from nbpreview.component.content.output.error import Error 5 | from nbpreview.component.content.output.result.result import Result 6 | from nbpreview.component.content.output.stream import Stream 7 | 8 | Output = Union[Result, Error, Stream] 9 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/__init__.py: -------------------------------------------------------------------------------- 1 | """Execution results from Jupyter notebooks.""" 2 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/execution_indicator.py: -------------------------------------------------------------------------------- 1 | """Jupyter execution indicators.""" 2 | import dataclasses 3 | from typing import Union 4 | 5 | from rich import padding, text 6 | from rich.padding import Padding 7 | from rich.text import Text 8 | 9 | 10 | @dataclasses.dataclass 11 | class Execution: 12 | """The execution count indicator.""" 13 | 14 | execution_count: Union[int, None] 15 | top_pad: bool 16 | execution_indicator: Union[Text, Padding] = dataclasses.field(init=False) 17 | 18 | def __post_init__(self) -> None: 19 | """Initialize execution indicator.""" 20 | execution_indicator: Union[Text, Padding] 21 | if self.execution_count is None: 22 | execution_text = " " 23 | else: 24 | execution_text = str(self.execution_count) 25 | execution_indicator = text.Text(f"[{execution_text}]:", style="color(247)") 26 | 27 | if self.top_pad: 28 | execution_indicator = padding.Padding(execution_indicator, pad=(1, 0, 0, 0)) 29 | 30 | self.execution_indicator = execution_indicator 31 | 32 | def __rich__(self) -> Union[Padding, Text]: 33 | """Render the execution indicator.""" 34 | return self.execution_indicator 35 | 36 | 37 | def choose_execution(execution: Union[None, Execution]) -> Union[str, Execution]: 38 | """Select the execution indicator.""" 39 | return execution if execution is not None else "" 40 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/latex.py: -------------------------------------------------------------------------------- 1 | """Render LaTeX equations.""" 2 | from pylatexenc import latex2text 3 | 4 | 5 | def render_latex(markup: str) -> str: 6 | """Render latex as unicode characters.""" 7 | rendered_latex: str = latex2text.LatexNodes2Text( 8 | math_mode="text", fill_text=True, strict_latex_spaces=False 9 | ).latex_to_text(markup) 10 | return rendered_latex 11 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/markdown_extensions.py: -------------------------------------------------------------------------------- 1 | """Supplement markdown renderer.""" 2 | import dataclasses 3 | import itertools 4 | from typing import Iterable, Iterator, Optional 5 | 6 | import markdown_it 7 | from markdown_it.token import Token 8 | from mdit_py_plugins.dollarmath import dollarmath_plugin 9 | from rich import markdown 10 | from rich.console import RenderableType 11 | from rich.markdown import Markdown 12 | from rich.table import Table 13 | 14 | from nbpreview import errors 15 | from nbpreview.component.content.output.result import latex, table 16 | 17 | 18 | class NotIteratorError(ValueError, errors.NBPreviewError): 19 | """Error when not an iterator.""" 20 | 21 | def __init__(self, arg_name: str) -> None: 22 | """Constructor.""" 23 | super().__init__(f"{arg_name} not an iterator") 24 | 25 | 26 | class UnknownTokenTypeError(ValueError, errors.NBPreviewError): 27 | """Error raised when the token type is unknown.""" 28 | 29 | def __init__(self, token_type: str) -> None: 30 | """Constructor.""" 31 | super().__init__(f"Unknown token type {token_type=}") 32 | 33 | 34 | @dataclasses.dataclass 35 | class MarkdownExtensionSection: 36 | """A section of rendered markdown.""" 37 | 38 | renderable: RenderableType 39 | start_line: int 40 | end_line: int 41 | 42 | 43 | @dataclasses.dataclass 44 | class TokenGroup: 45 | """A pair of token tags that contain a Markdown group.""" 46 | 47 | open_tag: str 48 | close_tag: Optional[str] = None 49 | 50 | def __post_init__(self) -> None: 51 | """Constructor.""" 52 | self.close_tag = self.open_tag if self.close_tag is None else self.close_tag 53 | 54 | 55 | def _group_tokens( 56 | iterator: Iterator[Token], 57 | token_groups: Iterable[TokenGroup], 58 | ) -> Iterator[Iterator[Token]]: 59 | """Group tokens bounded by tags into separate iterators.""" 60 | if not isinstance(iterator, Iterator): 61 | raise NotIteratorError("iterator") 62 | open_close_pairs = { 63 | token_group.open_tag: token_group.close_tag for token_group in token_groups 64 | } 65 | for token in iterator: 66 | if (token_type := token.type) in open_close_pairs: 67 | close_tag = open_close_pairs[token_type] 68 | if token_type == close_tag: 69 | yield iter([token]) 70 | else: 71 | yield itertools.chain( 72 | (token,), 73 | itertools.takewhile( 74 | lambda token_: token_.type != close_tag, iterator # noqa: B023 75 | ), 76 | ) 77 | 78 | 79 | def _render_markdown_table(parsed_group: Iterator[Token], unicode: bool) -> Table: 80 | """Render a parsed Markdown table.""" 81 | is_header = {"th_open": True, "td_open": False} 82 | rich_table = table.create_table(unicode) 83 | for parsed_row in _group_tokens( 84 | parsed_group, 85 | token_groups=[TokenGroup(open_tag="tr_open", close_tag="tr_close")], 86 | ): 87 | row_text = [] 88 | header = False 89 | end_section = True 90 | for token in parsed_row: 91 | if token.type in is_header: 92 | header = is_header[token.type] 93 | if token.type == "td_open": 94 | end_section = False 95 | 96 | elif token.type == "inline": 97 | row_text.append( 98 | _parse_table_element( 99 | token.children[0].content if token.children else token.content, 100 | header=header, 101 | ) 102 | ) 103 | else: 104 | header = False 105 | 106 | rich_table.add_row(*row_text, end_section=end_section) 107 | 108 | if table.is_only_header(rich_table): 109 | rich_table.add_row("") 110 | return rich_table 111 | 112 | 113 | def _parse_table_element(element: str, header: bool) -> Markdown: 114 | """Parse the text in a table element as Markdown.""" 115 | if header and element.strip(): 116 | element = f"**{element}**" 117 | table_element = markdown.Markdown(element) 118 | return table_element 119 | 120 | 121 | def parse_markdown_extensions( 122 | markup: str, unicode: bool 123 | ) -> Iterator[MarkdownExtensionSection]: 124 | """Return parsed tables from markdown.""" 125 | markdown_parser = ( 126 | markdown_it.MarkdownIt("zero") 127 | .enable("table") 128 | .use( 129 | dollarmath_plugin, 130 | allow_labels=False, 131 | allow_space=True, 132 | allow_digits=False, 133 | double_inline=True, 134 | ) 135 | ) 136 | parsed_markup = markdown_parser.parse(markup) 137 | iter_parsed_markup = iter(parsed_markup) 138 | token_groups = [ 139 | TokenGroup(open_tag="table_open", close_tag="table_close"), 140 | ] 141 | if unicode: 142 | token_groups.append(TokenGroup(open_tag="math_block")) 143 | for parsed_group in _group_tokens(iter_parsed_markup, token_groups=token_groups): 144 | open_token = next(parsed_group) 145 | open_token_type = open_token.type 146 | start_line, end_line = open_token.map or (0, 0) 147 | if open_token_type == "math_block": # noqa: S105 148 | rendered_math = latex.render_latex(open_token.content) 149 | yield MarkdownExtensionSection( 150 | rendered_math, start_line=start_line, end_line=end_line 151 | ) 152 | elif open_token_type == "table_open": # noqa: S105 153 | rendered_table = _render_markdown_table(parsed_group, unicode=unicode) 154 | yield MarkdownExtensionSection( 155 | rendered_table, start_line=start_line, end_line=end_line 156 | ) 157 | else: 158 | raise UnknownTokenTypeError(open_token_type) 159 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/result.py: -------------------------------------------------------------------------------- 1 | """Render execution results from Jupyter Notebooks.""" 2 | from pathlib import Path 3 | from typing import Iterator, Union 4 | 5 | from nbformat import NotebookNode 6 | 7 | from nbpreview.component.content.output.result import display_data, link 8 | from nbpreview.component.content.output.result.display_data import DisplayData 9 | from nbpreview.component.content.output.result.drawing import Drawing, ImageDrawing 10 | from nbpreview.component.content.output.result.execution_indicator import Execution 11 | from nbpreview.component.content.output.result.link import FileLink 12 | 13 | Result = Union[FileLink, DisplayData, Drawing] 14 | 15 | 16 | def render_result( 17 | output: NotebookNode, 18 | unicode: bool, 19 | execution: Union[Execution, None], 20 | hyperlinks: bool, 21 | nerd_font: bool, 22 | files: bool, 23 | hide_hyperlink_hints: bool, 24 | theme: str, 25 | images: bool, 26 | image_drawing: ImageDrawing, 27 | color: bool, 28 | negative_space: bool, 29 | relative_dir: Path, 30 | ) -> Iterator[Result]: 31 | """Render executed result outputs.""" 32 | data = output.get("data", {}) 33 | link_result = link.render_link( 34 | data, 35 | unicode=unicode, 36 | hyperlinks=hyperlinks, 37 | execution=execution, 38 | nerd_font=nerd_font, 39 | files=files, 40 | hide_hyperlink_hints=hide_hyperlink_hints, 41 | ) 42 | main_result = display_data.render_display_data( 43 | data, 44 | unicode=unicode, 45 | nerd_font=nerd_font, 46 | theme=theme, 47 | images=images, 48 | image_drawing=image_drawing, 49 | color=color, 50 | negative_space=negative_space, 51 | hyperlinks=hyperlinks, 52 | files=files, 53 | hide_hyperlink_hints=hide_hyperlink_hints, 54 | relative_dir=relative_dir, 55 | ) 56 | for result in (link_result, main_result): 57 | if result is not None: 58 | yield result 59 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/result/table.py: -------------------------------------------------------------------------------- 1 | """Render a table.""" 2 | from typing import Union 3 | 4 | from rich import box, style, table, text 5 | from rich.style import Style 6 | from rich.table import Table 7 | from rich.text import Text 8 | 9 | 10 | def create_table(unicode: bool) -> Table: 11 | """Create a rich table.""" 12 | rich_table = table.Table( 13 | show_edge=False, 14 | show_header=False, 15 | box=box.HORIZONTALS, 16 | show_footer=False, 17 | safe_box=not unicode, 18 | ) 19 | return rich_table 20 | 21 | 22 | def is_only_header(rich_table: Table) -> bool: 23 | """Detect if table is only headers and no content.""" 24 | only_header = 1 <= rich_table.row_count and rich_table.rows[-1].end_section 25 | return only_header 26 | 27 | 28 | def create_table_element(element_data: str, header: bool) -> Text: 29 | """Create a single table element.""" 30 | text_style: Union[str, Style] = style.Style(bold=True) if header else "" 31 | rich_text = text.Text(element_data, style=text_style) 32 | return rich_text 33 | -------------------------------------------------------------------------------- /src/nbpreview/component/content/output/stream.py: -------------------------------------------------------------------------------- 1 | """Notebook stream results.""" 2 | 3 | 4 | import dataclasses 5 | from typing import ClassVar, Iterator, Union 6 | 7 | from nbformat import NotebookNode 8 | from rich import padding, style, text 9 | from rich.console import ConsoleRenderable 10 | from rich.padding import Padding 11 | 12 | 13 | @dataclasses.dataclass 14 | class Stream: 15 | """A stream output.""" 16 | 17 | content: str 18 | name: ClassVar[str] 19 | 20 | def __rich__(self) -> Union[ConsoleRenderable, str]: 21 | """Render the stream.""" 22 | return self.content 23 | 24 | @classmethod 25 | def from_output(cls, output: NotebookNode) -> "Stream": 26 | """Create stream from notebook output.""" 27 | stream_text = output.get("text", "") 28 | text = stream_text[:-1] if stream_text.endswith("\n") else stream_text 29 | return cls(text) 30 | 31 | 32 | def render_stream(output: NotebookNode) -> Iterator[Stream]: 33 | """Render a stream type output. 34 | 35 | Args: 36 | output (NotebookNode): The stream output. 37 | 38 | Yields: 39 | Stream: The rendered stream. 40 | """ 41 | stream: Stream 42 | name = output.get("name") 43 | if name == "stderr": 44 | stream = StdErr.from_output(output) 45 | else: 46 | stream = Stream.from_output(output) 47 | yield stream 48 | 49 | 50 | @dataclasses.dataclass 51 | class StdErr(Stream): 52 | """A stderr stream output.""" 53 | 54 | name: ClassVar[str] = "stderr" 55 | 56 | @classmethod 57 | def from_output(cls, output: NotebookNode) -> "StdErr": 58 | """Create stderr from notebook output.""" 59 | if output["name"] != cls.name: 60 | raise ValueError(f"Output does not contain a {cls.name} stream") 61 | text = output.get("text", "") 62 | return cls(text) 63 | 64 | def __rich__(self) -> Padding: 65 | """Render a stderr stream.""" 66 | stderr_text = text.Text(self.content, style=style.Style(color="color(237)")) 67 | rendered_stderr = padding.Padding( 68 | stderr_text, pad=(1, 1, 0, 1), style=style.Style(bgcolor="color(174)") 69 | ) 70 | return rendered_stderr 71 | -------------------------------------------------------------------------------- /src/nbpreview/data/__init__.py: -------------------------------------------------------------------------------- 1 | """The Jupyter notebook data.""" 2 | from typing import Dict, Union 3 | 4 | from nbformat import NotebookNode 5 | 6 | Data = Dict[str, Union[str, NotebookNode]] 7 | -------------------------------------------------------------------------------- /src/nbpreview/errors.py: -------------------------------------------------------------------------------- 1 | """nbpreview errors.""" 2 | 3 | 4 | class NBPreviewError(Exception): 5 | """Base nbpreview error.""" 6 | 7 | 8 | class InvalidNotebookError(NBPreviewError): 9 | """Error when input notebook in invalid.""" 10 | -------------------------------------------------------------------------------- /src/nbpreview/option_values.py: -------------------------------------------------------------------------------- 1 | """Enums representing option values.""" 2 | import enum 3 | import itertools 4 | from typing import Any, Iterable, List, Literal 5 | 6 | from pygments import styles 7 | 8 | 9 | def get_all_available_themes(list_duplicate_alias: bool = False) -> Iterable[str]: 10 | """Return the available theme names.""" 11 | theme_alias: Iterable[str] = ["light", "dark"] 12 | if list_duplicate_alias: 13 | theme_alias = itertools.chain( 14 | theme_alias, (f"ansi_{alias}" for alias in theme_alias) 15 | ) 16 | available_themes = itertools.chain(styles.get_all_styles(), theme_alias) 17 | yield from available_themes 18 | 19 | 20 | class _ThemeEnum(str, enum.Enum): 21 | """Enum version of available pygment themes.""" 22 | 23 | ... 24 | 25 | 26 | ThemeEnum = _ThemeEnum( # type: ignore[call-overload] 27 | "ThemeEnum", 28 | {theme.upper(): theme for theme in get_all_available_themes(True)}, 29 | ) 30 | 31 | 32 | class LowerNameEnum(enum.Enum): 33 | """Enum base class that sets value to lowercase version of name.""" 34 | 35 | def _generate_next_value_( # type: ignore[override,misc] 36 | name: str, # noqa: B902,N805 37 | start: int, 38 | count: int, 39 | last_values: List[Any], 40 | ) -> str: 41 | """Set member's values as their lowercase name.""" 42 | return name.lower() 43 | 44 | 45 | @enum.unique 46 | class ColorSystemEnum(str, LowerNameEnum): 47 | """The color systems supported by terminals.""" 48 | 49 | STANDARD: Literal["standard"] = enum.auto() # type: ignore[assignment] 50 | EIGHT_BIT: Literal["256"] = "256" 51 | TRUECOLOR: Literal["truecolor"] = enum.auto() # type: ignore[assignment] 52 | WINDOWS: Literal["windows"] = enum.auto() # type: ignore[assignment] 53 | NONE: Literal["none"] = enum.auto() # type: ignore[assignment] 54 | # Add AUTO because callbacks must return values associated with types 55 | AUTO: Literal["auto"] = enum.auto() # type: ignore[assignment] 56 | 57 | 58 | @enum.unique 59 | class ImageDrawingEnum(str, LowerNameEnum): 60 | """Image drawing types.""" 61 | 62 | BLOCK = enum.auto() 63 | CHARACTER = enum.auto() 64 | BRAILLE = enum.auto() 65 | -------------------------------------------------------------------------------- /src/nbpreview/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/src/nbpreview/py.typed -------------------------------------------------------------------------------- /src/nbpreview/templates/vega_template.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ execution_count_indicator }}{{ subject }} 8 | 9 | 10 | 11 | {{ vega_json }} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Test suite for the nbpreview package.""" 2 | -------------------------------------------------------------------------------- /tests/leaves.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/tests/leaves.jpg -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for nbpreview.""" 2 | -------------------------------------------------------------------------------- /tests/unit/assets/bad_image.xyz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/tests/unit/assets/bad_image.xyz -------------------------------------------------------------------------------- /tests/unit/assets/matplotlibrc: -------------------------------------------------------------------------------- 1 | # Place in ~/.matplotlib/matplotlibrc 2 | ## *************************************************************************** 3 | ## * LINES * 4 | ## *************************************************************************** 5 | lines.linewidth : 2.5 6 | lines.dash_joinstyle : round 7 | lines.solid_joinstyle : round 8 | lines.solid_capstyle : round 9 | 10 | 11 | ## *************************************************************************** 12 | ## * PATCHES * 13 | ## *************************************************************************** 14 | patch.edgecolor : C0 15 | 16 | 17 | ## *************************************************************************** 18 | ## * HATCHES * 19 | ## *************************************************************************** 20 | hatch.color : 1C1B1F 21 | hatch.linewidth : 2.5 22 | 23 | 24 | ## *************************************************************************** 25 | ## * BOXPLOT * 26 | ## *************************************************************************** 27 | boxplot.showcaps : False 28 | 29 | boxplot.flierprops.marker : o 30 | boxplot.flierprops.color : C0 31 | boxplot.flierprops.markerfacecolor : C0 32 | boxplot.flierprops.markeredgecolor : C0 33 | boxplot.flierprops.markeredgewidth : 0 34 | boxplot.flierprops.markersize : 9 35 | 36 | boxplot.boxprops.color : C0 37 | boxplot.boxprops.linewidth : 2.5 38 | 39 | boxplot.whiskerprops.color : C0 40 | boxplot.whiskerprops.linewidth : 2.5 41 | 42 | boxplot.capprops.color : C0 43 | boxplot.capprops.linewidth : 2.5 44 | 45 | 46 | boxplot.medianprops.color : C0 47 | boxplot.medianprops.linewidth : 2.5 48 | 49 | boxplot.meanprops.markersize : 9 50 | boxplot.meanprops.linewidth : 2.5 51 | 52 | 53 | ## *************************************************************************** 54 | ## * FONT * 55 | ## *************************************************************************** 56 | font.family : Roboto 57 | font.size : 12 58 | font.weight : 400 59 | 60 | 61 | ## *************************************************************************** 62 | ## * TEXT * 63 | ## *************************************************************************** 64 | text.color : E6E1E5 65 | 66 | 67 | ## *************************************************************************** 68 | ## * AXES * 69 | ## *************************************************************************** 70 | axes.facecolor : 1C1B1F 71 | axes.edgecolor : E6E1E5 72 | axes.linewidth : 1.3 73 | axes.grid : True 74 | axes.grid.axis : y 75 | axes.titlelocation : left 76 | axes.titlesize : 20 77 | axes.titlecolor : E6E1E5 78 | 79 | axes.titlepad : 20 80 | axes.labelsize : 12 81 | axes.labelpad : 20 82 | axes.labelcolor : E6E1E5 83 | axes.axisbelow : True 84 | 85 | axes.spines.left : False 86 | axes.spines.bottom : True 87 | axes.spines.top : False 88 | axes.spines.right : False 89 | 90 | axes.prop_cycle : cycler('color', ['D0BCFF', '70dba8', 'ffb683', 'acc7ff', 'abd371', 'ffb4a3']) 91 | # D0BCFF : purple 92 | # 70dba8 : teal 93 | # ffb683 : orange 94 | # acc7ff : blue 95 | # abd371 : green 96 | # ffb4a3 : peach 97 | 98 | 99 | 100 | ## *************************************************************************** 101 | ## * TICKS * 102 | ## *************************************************************************** 103 | xtick.bottom : False 104 | xtick.major.size : 5 105 | xtick.major.width : 1.0 106 | xtick.major.pad : 10 107 | xtick.color : E6E1E5 108 | xtick.labelsize : 12 109 | 110 | ytick.left : False 111 | ytick.major.size : 7 112 | ytick.major.width : 1.0 113 | ytick.major.pad : 10 114 | ytick.color : E6E1E5 115 | ytick.labelsize : 12 116 | 117 | 118 | ## *************************************************************************** 119 | ## * GRIDS * 120 | ## *************************************************************************** 121 | grid.color : 49454F 122 | grid.linewidth : 1.3 123 | 124 | 125 | ## *************************************************************************** 126 | ## * LEGEND * 127 | ## *************************************************************************** 128 | legend.loc : best 129 | legend.frameon : False 130 | 131 | legend.fontsize : 12 132 | 133 | 134 | ## *************************************************************************** 135 | ## * FIGURE * 136 | ## *************************************************************************** 137 | figure.titlesize : 16 138 | figure.figsize : 9, 5 139 | #figure.dpi : 220 140 | figure.facecolor : 1C1B1F 141 | figure.edgecolor : 1C1B1F 142 | 143 | 144 | ## *************************************************************************** 145 | ## * SAVING FIGURES * 146 | ## *************************************************************************** 147 | savefig.facecolor : 1C1B1F 148 | savefig.edgecolor : 1C1B1F 149 | -------------------------------------------------------------------------------- /tests/unit/assets/outline_article_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paw-lu/nbpreview/d1ea663b84de07fe780cd698960001d80b74d09b/tests/unit/assets/outline_article_white_48dp.png -------------------------------------------------------------------------------- /tests/unit/component/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for nbpreview.component.""" 2 | -------------------------------------------------------------------------------- /tests/unit/component/content/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for nbpreview.component.content.""" 2 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for nbpreview.component.content.output.""" 2 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/result/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for nbpreview.component.content.output.result.""" 2 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/result/test_link.py: -------------------------------------------------------------------------------- 1 | """Test for nbpreview.component.content.output.result.link.""" 2 | import nbformat 3 | 4 | from nbpreview.component.content.output.result import link 5 | from nbpreview.data import Data 6 | 7 | 8 | def test_image_link_no_str_data() -> None: 9 | """It sets the content to None when the data is not a string.""" 10 | image_type = "image" 11 | data: Data = {image_type: nbformat.NotebookNode()} # type: ignore[no-untyped-call] 12 | image_link = link.ImageLink.from_data( 13 | data, 14 | image_type=image_type, 15 | unicode=True, 16 | hyperlinks=True, 17 | nerd_font=True, 18 | files=False, 19 | hide_hyperlink_hints=False, 20 | ) 21 | output = image_link.content 22 | expected_output = None 23 | assert output is expected_output 24 | 25 | 26 | def test_image_link_bad_decode() -> None: 27 | """It fallsback to None when there is a decode error.""" 28 | image_type = "image" 29 | data: Data = {image_type: "123"} 30 | image_link = link.ImageLink.from_data( 31 | data, 32 | image_type=image_type, 33 | unicode=True, 34 | hyperlinks=True, 35 | nerd_font=True, 36 | files=False, 37 | hide_hyperlink_hints=False, 38 | ) 39 | output = image_link.content 40 | expected_output = None 41 | assert output is expected_output 42 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/result/test_markdown_extensions.py: -------------------------------------------------------------------------------- 1 | """Test for nbpreview.component.content.output.result.markdown_extensions.""" 2 | import pytest 3 | from markdown_it import token 4 | from pytest_mock import MockerFixture 5 | 6 | from nbpreview.component.content.output.result import markdown_extensions 7 | 8 | 9 | @pytest.mark.no_typeguard 10 | def test_group_tokens_not_iterator() -> None: 11 | """It raises a NotIteratorError when not an iterator.""" 12 | token_groups = [ 13 | markdown_extensions.TokenGroup( 14 | open_tag="open", 15 | close_tag="close", 16 | ) 17 | ] 18 | with pytest.raises(markdown_extensions.NotIteratorError): 19 | grouped_tokens = markdown_extensions._group_tokens( 20 | [1, 2, 3], # type: ignore[arg-type] 21 | token_groups=token_groups, 22 | ) 23 | next(grouped_tokens) 24 | 25 | 26 | def test_unknown_token_type_error(mocker: MockerFixture) -> None: 27 | """It raises UnknownTokenTypeError when open token is not caught.""" 28 | mocker.patch( 29 | "nbpreview.component.content.output.result.markdown_extensions._group_tokens", 30 | return_value=iter([iter([token.Token(type="unknown", tag="", nesting=0)])]), 31 | ) 32 | parsed_markdown_extensions = markdown_extensions.parse_markdown_extensions( 33 | markup="", unicode=True 34 | ) 35 | with pytest.raises(markdown_extensions.UnknownTokenTypeError): 36 | next(parsed_markdown_extensions) 37 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/test_error.py: -------------------------------------------------------------------------------- 1 | """Test for nbpreview.component.content.output.error.""" 2 | import nbformat 3 | import pytest 4 | 5 | from nbpreview.component.content.output import error 6 | 7 | 8 | def test_render_unknown_error() -> None: 9 | """It does not render an unknown error type.""" 10 | notebook_output = nbformat.from_dict({}) # type: ignore[no-untyped-call] 11 | output = error.render_error(notebook_output) 12 | with pytest.raises(StopIteration): 13 | next(output) 14 | 15 | 16 | def test_error_repr() -> None: 17 | """It has a string representation.""" 18 | content = ["foo", "bar"] 19 | traceback = error.Traceback(content) 20 | expected_output = f"Traceback(content={content})" 21 | output = repr(traceback) 22 | assert output == expected_output 23 | -------------------------------------------------------------------------------- /tests/unit/component/content/output/test_stream.py: -------------------------------------------------------------------------------- 1 | """Test for nbpreview.component.content.output.stream.""" 2 | import nbformat 3 | import pytest 4 | 5 | from nbpreview.component.content.output import stream 6 | 7 | 8 | def test_invalid_stderr_name() -> None: 9 | """It raises a ValueError when the name is not 'stderr'.""" 10 | notebook_output = nbformat.from_dict( # type: ignore[no-untyped-call] 11 | {"name": "unknown"} 12 | ) 13 | with pytest.raises(ValueError): 14 | stream.StdErr.from_output(notebook_output) 15 | -------------------------------------------------------------------------------- /tests/unit/component/test_markdown.py: -------------------------------------------------------------------------------- 1 | """Tests for src.nbpreview.component.row.""" 2 | import pytest 3 | 4 | from nbpreview.component import markdown 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "string, expected_output", [("foobar", "bar"), ("barbar", "barbar")] 9 | ) 10 | def test_remove_prefix(string: str, expected_output: str) -> None: 11 | """It removes the prefix from the string if it exists.""" 12 | output = markdown._remove_prefix(string, "foo") 13 | assert output == expected_output 14 | -------------------------------------------------------------------------------- /tests/unit/component/test_row.py: -------------------------------------------------------------------------------- 1 | """Tests for src.nbpreview.component.row.""" 2 | import pathlib 3 | 4 | import nbformat 5 | import pytest 6 | 7 | from nbpreview.component import row 8 | 9 | 10 | def test_render_unknown_output_type() -> None: 11 | """It does not render an unknown output type.""" 12 | notebook_outputs = [ 13 | nbformat.from_dict({"output_type": "unknown"}) # type: ignore[no-untyped-call] 14 | ] 15 | rendered_output_row = row.render_output_row( 16 | notebook_outputs, 17 | plain=True, 18 | unicode=True, 19 | hyperlinks=True, 20 | nerd_font=True, 21 | files=True, 22 | hide_hyperlink_hints=True, 23 | theme="ansi_dark", 24 | pad=(0, 1, 0, 0), 25 | images=False, 26 | image_drawing="braille", 27 | color=True, 28 | negative_space=True, 29 | relative_dir=pathlib.Path(), 30 | ) 31 | with pytest.raises(StopIteration): 32 | next(rendered_output_row) 33 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_color_notebook_file[-o-None-None].txt: -------------------------------------------------------------------------------- 1 |  Lorem ipsum   2 | ────────────────────────────────────────────────────────────────────────── 3 | 4 | ╭─────────────────────────────────────────────────────────────────────────╮ 5 | [1]: │ import matplotlib.pyplot as plt │ 6 | │ import seaborn as sns │ 7 | ╰─────────────────────────────────────────────────────────────────────────╯ 8 | 9 | 10 | ## Condimentum  11 | ────────────────────────────────────────────────────────────────────────── 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 14 | tempor incididunt ut labore et dolore magna aliqua. Sunt ]8;id=0;https://github.com/paw-lu/nbpreview\in culpa qui ]8;;\ 15 | ]8;id=0;https://github.com/paw-lu/nbpreview\officia]8;;\ deserunt mollit anim id est laborum. 16 | 17 | ╭─────────────────────────────────────────────────────────────────────────╮ 18 | [2]: │ fmri = sns.load_dataset("fmri") │ 19 | │ tips = sns.load_dataset("tips") │ 20 | │ fmri.head() │ 21 | ╰─────────────────────────────────────────────────────────────────────────╯ 22 | 23 | [2]: ]8;id=0;file://{{ tempfile_path }}0.html\🌐 Click to view HTML]8;;\ 24 | 25 | [2]:   subject timepoint event  region  signal 26 | ──────────────────────────────────────────────────────── 27 | 0 s13 18 stim parietal -0.017552 28 | 1 s5 14 stim parietal -0.080883 29 | 2 s12 18 stim parietal -0.081033 30 | 3 s11 18 stim parietal -0.046134 31 | 4 s10 18 stim parietal -0.037970 32 | 33 | ╭─────────────────────────────────────────────────────────────────────────╮ 34 | [3]: │ _, (ax_line, ax_box) = plt.subplots(ncols=2, facecolor="#1C1B1F", figs… │ 35 | │ ( │ 36 | │ fmri.pipe( │ 37 | │ (sns.lineplot, "data"), │ 38 | │ x="timepoint", │ 39 | │ y="signal", │ 40 | │ hue="event", │ 41 | │ ax=ax_line, │ 42 | │ err_kws={"alpha": 1}, │ 43 | │ ).set( │ 44 | │ xlabel=None, │ 45 | │ ylabel=None, │ 46 | │ yticks=[], │ 47 | │ xticks=[], │ 48 | │ ) │ 49 | │ ) │ 50 | │ │ 51 | │ ( │ 52 | │ tips.pipe( │ 53 | │ (sns.boxplot, "data"), │ 54 | │ x="day", │ 55 | │ y="total_bill", │ 56 | │ hue="smoker", │ 57 | │ ax=ax_box, │ 58 | │ ).set( │ 59 | │ xlabel=None, │ 60 | │ ylabel=None, │ 61 | │ yticks=[], │ 62 | │ xticks=[], │ 63 | │ ) │ 64 | │ ) │ 65 | ╰─────────────────────────────────────────────────────────────────────────╯ 66 | 67 | [3]: [Text(0.5, 0, ''), Text(0, 0.5, ''), [], []] 68 | 69 | ]8;id=0;file://{{ tempfile_path }}1.png\🖼 Click to view Image]8;;\ 70 | 71 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 72 | GGGGGGGGGG:::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 73 | GGGGGGGGG:::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 74 | GGGGGGGGG:GG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 75 | GGGGGGGG:GGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 76 | GGGGGGG!:GGGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGG:::GGGGG 77 | GGGGGGG:!!!GGGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG:::GGGGG:::?!!!G 78 | GGGGGG:!!GG!!GGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGP???GGGGG!!!GG:::!!!PG:::?!!!G 79 | GG:G!!GGGGGGG!GG::GGG?!!!!!!!!!!GGGGGGGGG:::P!!!GP:::!!!GG:::!!!PGGGG?!!!G 80 | GGGGGGGGGGGGGG!!!!!!GGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 81 | GGGGGGGGGGGGGGGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 82 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 83 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_color_notebook_file[None-NO_COLOR-1].txt: -------------------------------------------------------------------------------- 1 |  Lorem ipsum   2 | ────────────────────────────────────────────────────────────────────────── 3 | 4 | ╭─────────────────────────────────────────────────────────────────────────╮ 5 | [1]: │ import matplotlib.pyplot as plt │ 6 | │ import seaborn as sns │ 7 | ╰─────────────────────────────────────────────────────────────────────────╯ 8 | 9 | 10 | ## Condimentum  11 | ────────────────────────────────────────────────────────────────────────── 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 14 | tempor incididunt ut labore et dolore magna aliqua. Sunt ]8;id=0;https://github.com/paw-lu/nbpreview\in culpa qui ]8;;\ 15 | ]8;id=0;https://github.com/paw-lu/nbpreview\officia]8;;\ deserunt mollit anim id est laborum. 16 | 17 | ╭─────────────────────────────────────────────────────────────────────────╮ 18 | [2]: │ fmri = sns.load_dataset("fmri") │ 19 | │ tips = sns.load_dataset("tips") │ 20 | │ fmri.head() │ 21 | ╰─────────────────────────────────────────────────────────────────────────╯ 22 | 23 | [2]: ]8;id=0;file://{{ tempfile_path }}0.html\🌐 Click to view HTML]8;;\ 24 | 25 | [2]:   subject timepoint event  region  signal 26 | ──────────────────────────────────────────────────────── 27 | 0 s13 18 stim parietal -0.017552 28 | 1 s5 14 stim parietal -0.080883 29 | 2 s12 18 stim parietal -0.081033 30 | 3 s11 18 stim parietal -0.046134 31 | 4 s10 18 stim parietal -0.037970 32 | 33 | ╭─────────────────────────────────────────────────────────────────────────╮ 34 | [3]: │ _, (ax_line, ax_box) = plt.subplots(ncols=2, facecolor="#1C1B1F", figs… │ 35 | │ ( │ 36 | │ fmri.pipe( │ 37 | │ (sns.lineplot, "data"), │ 38 | │ x="timepoint", │ 39 | │ y="signal", │ 40 | │ hue="event", │ 41 | │ ax=ax_line, │ 42 | │ err_kws={"alpha": 1}, │ 43 | │ ).set( │ 44 | │ xlabel=None, │ 45 | │ ylabel=None, │ 46 | │ yticks=[], │ 47 | │ xticks=[], │ 48 | │ ) │ 49 | │ ) │ 50 | │ │ 51 | │ ( │ 52 | │ tips.pipe( │ 53 | │ (sns.boxplot, "data"), │ 54 | │ x="day", │ 55 | │ y="total_bill", │ 56 | │ hue="smoker", │ 57 | │ ax=ax_box, │ 58 | │ ).set( │ 59 | │ xlabel=None, │ 60 | │ ylabel=None, │ 61 | │ yticks=[], │ 62 | │ xticks=[], │ 63 | │ ) │ 64 | │ ) │ 65 | ╰─────────────────────────────────────────────────────────────────────────╯ 66 | 67 | [3]: [Text(0.5, 0, ''), Text(0, 0.5, ''), [], []] 68 | 69 | ]8;id=0;file://{{ tempfile_path }}1.png\🖼 Click to view Image]8;;\ 70 | 71 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 72 | GGGGGGGGGG:::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 73 | GGGGGGGGG:::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 74 | GGGGGGGGG:GG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 75 | GGGGGGGG:GGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 76 | GGGGGGG!:GGGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGG:::GGGGG 77 | GGGGGGG:!!!GGGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG:::GGGGG:::?!!!G 78 | GGGGGG:!!GG!!GGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGP???GGGGG!!!GG:::!!!PG:::?!!!G 79 | GG:G!!GGGGGGG!GG::GGG?!!!!!!!!!!GGGGGGGGG:::P!!!GP:::!!!GG:::!!!PGGGG?!!!G 80 | GGGGGGGGGGGGGG!!!!!!GGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 81 | GGGGGGGGGGGGGGGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 82 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 83 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_color_system_notebook_file[--color-system-none-None].txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum 2 | ────────────────────────────────────────────────────────────────────────── 3 | 4 | ╭─────────────────────────────────────────────────────────────────────────╮ 5 | [1]: │ import matplotlib.pyplot as plt │ 6 | │ import seaborn as sns │ 7 | ╰─────────────────────────────────────────────────────────────────────────╯ 8 | 9 | 10 | ## Condimentum 11 | ────────────────────────────────────────────────────────────────────────── 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 14 | tempor incididunt ut labore et dolore magna aliqua. Sunt in culpa qui 15 | officia deserunt mollit anim id est laborum. 16 | 17 | ╭─────────────────────────────────────────────────────────────────────────╮ 18 | [2]: │ fmri = sns.load_dataset("fmri") │ 19 | │ tips = sns.load_dataset("tips") │ 20 | │ fmri.head() │ 21 | ╰─────────────────────────────────────────────────────────────────────────╯ 22 | 23 | [2]: 🌐 Click to view HTML 24 | 25 | [2]: subject timepoint event region signal 26 | ──────────────────────────────────────────────────────── 27 | 0 s13 18 stim parietal -0.017552 28 | 1 s5 14 stim parietal -0.080883 29 | 2 s12 18 stim parietal -0.081033 30 | 3 s11 18 stim parietal -0.046134 31 | 4 s10 18 stim parietal -0.037970 32 | 33 | ╭─────────────────────────────────────────────────────────────────────────╮ 34 | [3]: │ _, (ax_line, ax_box) = plt.subplots(ncols=2, facecolor="#1C1B1F", figs… │ 35 | │ ( │ 36 | │ fmri.pipe( │ 37 | │ (sns.lineplot, "data"), │ 38 | │ x="timepoint", │ 39 | │ y="signal", │ 40 | │ hue="event", │ 41 | │ ax=ax_line, │ 42 | │ err_kws={"alpha": 1}, │ 43 | │ ).set( │ 44 | │ xlabel=None, │ 45 | │ ylabel=None, │ 46 | │ yticks=[], │ 47 | │ xticks=[], │ 48 | │ ) │ 49 | │ ) │ 50 | │ │ 51 | │ ( │ 52 | │ tips.pipe( │ 53 | │ (sns.boxplot, "data"), │ 54 | │ x="day", │ 55 | │ y="total_bill", │ 56 | │ hue="smoker", │ 57 | │ ax=ax_box, │ 58 | │ ).set( │ 59 | │ xlabel=None, │ 60 | │ ylabel=None, │ 61 | │ yticks=[], │ 62 | │ xticks=[], │ 63 | │ ) │ 64 | │ ) │ 65 | ╰─────────────────────────────────────────────────────────────────────────╯ 66 | 67 | [3]: [Text(0.5, 0, ''), Text(0, 0.5, ''), [], []] 68 | 69 | 🖼 Click to view Image 70 | 71 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 72 | GGGGGGGGGG:::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 73 | GGGGGGGGG:::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 74 | GGGGGGGGG:GG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 75 | GGGGGGGG:GGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 76 | GGGGGGG!:GGGGG::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGG:::GGGGG 77 | GGGGGGG:!!!GGGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG:::GGGGG:::?!!!G 78 | GGGGGG:!!GG!!GGG:GGGGGGGGGGGGGGGGGGGGGGGGGGGP???GGGGG!!!GG:::!!!PG:::?!!!G 79 | GG:G!!GGGGGGG!GG::GGG?!!!!!!!!!!GGGGGGGGG:::P!!!GP:::!!!GG:::!!!PGGGG?!!!G 80 | GGGGGGGGGGGGGG!!!!!!GGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 81 | GGGGGGGGGGGGGGGGGGG::::::GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPGGGGGGGGGGGGGG 82 | GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG 83 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_no_color_no_image.txt: -------------------------------------------------------------------------------- 1 |  Lorem ipsum   2 | ────────────────────────────────────────────────────────────────────────── 3 | 4 | ╭─────────────────────────────────────────────────────────────────────────╮ 5 | [1]: │ import matplotlib.pyplot as plt │ 6 | │ import seaborn as sns │ 7 | ╰─────────────────────────────────────────────────────────────────────────╯ 8 | 9 | 10 | ## Condimentum  11 | ────────────────────────────────────────────────────────────────────────── 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 14 | tempor incididunt ut labore et dolore magna aliqua. Sunt ]8;id=0;https://github.com/paw-lu/nbpreview\in culpa qui ]8;;\ 15 | ]8;id=0;https://github.com/paw-lu/nbpreview\officia]8;;\ deserunt mollit anim id est laborum. 16 | 17 | ╭─────────────────────────────────────────────────────────────────────────╮ 18 | [2]: │ fmri = sns.load_dataset("fmri") │ 19 | │ tips = sns.load_dataset("tips") │ 20 | │ fmri.head() │ 21 | ╰─────────────────────────────────────────────────────────────────────────╯ 22 | 23 | [2]: ]8;id=0;file://{{ tempfile_path }}0.html\🌐 Click to view HTML]8;;\ 24 | 25 | [2]:   subject timepoint event  region  signal 26 | ──────────────────────────────────────────────────────── 27 | 0 s13 18 stim parietal -0.017552 28 | 1 s5 14 stim parietal -0.080883 29 | 2 s12 18 stim parietal -0.081033 30 | 3 s11 18 stim parietal -0.046134 31 | 4 s10 18 stim parietal -0.037970 32 | 33 | ╭─────────────────────────────────────────────────────────────────────────╮ 34 | [3]: │ _, (ax_line, ax_box) = plt.subplots(ncols=2, facecolor="#1C1B1F", figs… │ 35 | │ ( │ 36 | │ fmri.pipe( │ 37 | │ (sns.lineplot, "data"), │ 38 | │ x="timepoint", │ 39 | │ y="signal", │ 40 | │ hue="event", │ 41 | │ ax=ax_line, │ 42 | │ err_kws={"alpha": 1}, │ 43 | │ ).set( │ 44 | │ xlabel=None, │ 45 | │ ylabel=None, │ 46 | │ yticks=[], │ 47 | │ xticks=[], │ 48 | │ ) │ 49 | │ ) │ 50 | │ │ 51 | │ ( │ 52 | │ tips.pipe( │ 53 | │ (sns.boxplot, "data"), │ 54 | │ x="day", │ 55 | │ y="total_bill", │ 56 | │ hue="smoker", │ 57 | │ ax=ax_box, │ 58 | │ ).set( │ 59 | │ xlabel=None, │ 60 | │ ylabel=None, │ 61 | │ yticks=[], │ 62 | │ xticks=[], │ 63 | │ ) │ 64 | │ ) │ 65 | ╰─────────────────────────────────────────────────────────────────────────╯ 66 | 67 | [3]: [Text(0.5, 0, ''), Text(0, 0.5, ''), [], []] 68 | 69 | ]8;id=0;file://{{ tempfile_path }}1.png\🖼 Click to view Image]8;;\ 70 | 71 |
72 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_render_debugger_output.txt: -------------------------------------------------------------------------------- 1 | ╭─────────────────────────────────────────────────────────────────────────╮ 2 | [4]: │ %debug │ 3 | ╰─────────────────────────────────────────────────────────────────────────╯ 4 | 5 | > (1)() 6 | ----> 1 7 | _jupyterlab_variableinspector_dict_list()[1… 8 |  9 | 10 | ipdb> ll 11 | 12 | ----> 1 13 | _jupyterlab_variableinspector_dict_list()[1… 14 |  15 | 16 | ipdb> sticky 17 | 18 | *** NameError: name 'sticky' is not defined 19 | 20 | ipdb> q 21 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_render_error_traceback_no_hang.txt: -------------------------------------------------------------------------------- 1 | ╭─────────────────────────────────────────────────────────────────────────╮ 2 | [4]: │ %%bash │ 3 | │ ech │ 4 | ╰─────────────────────────────────────────────────────────────────────────╯ 5 | 6 |   7 |  bash: line 1: ech: command not found   8 |     9 | 10 | -------------------------------------------------------------------------… 11 | CalledProcessError Traceback (most recent call 12 | last) 13 |  in  14 | ----> 1 get_ipython().run_cell_magic('bash', '', 'ech\n') 15 | 16 | ~/.pyenv/versions/scratch/lib/python3.8/site-packages/IPython/core/intera… 17 | in run_cell_magic(self, magic_name, line, cell) 18 |  2389 with self.builtin_trap: 19 |  2390 args = (magic_arg_s, cell) 20 | -> 2391 result = fn(*args, **kwargs) 21 |  2392 return result 22 |  2393 23 | 24 | ~/.pyenv/versions/scratch/lib/python3.8/site-packages/IPython/core/magics… 25 | in named_script_magic(line, cell) 26 |  140 else: 27 |  141 line = script 28 | --> 142 return self.shebang(line, cell) 29 |  143 30 |  144 # write a basic docstring: 31 | 32 |  in shebang(self, line, cell) 33 | 34 | ~/.pyenv/versions/scratch/lib/python3.8/site-packages/IPython/core/magic.… 35 | in (f, *a, **k) 36 |  185 # but it's overkill for just that one bit of state. 37 |  186 def magic_deco(arg): 38 | --> 187 call = lambda f, *a, **k: f(*a, **k) 39 |  188 40 |  189 if callable(arg): 41 | 42 | ~/.pyenv/versions/scratch/lib/python3.8/site-packages/IPython/core/magics… 43 | in shebang(self, line, cell) 44 |  243 sys.stderr.flush() 45 |  244 if args.raise_error and p.returncode!=0: 46 | --> 245 raise CalledProcessError(p.returncode, cell, 47 | output=out, stderr=err) 48 |  246 49 |  247 def _run_script(self, p, cell, to_close): 50 | 51 | CalledProcessError: Command 'b'ech\n'' returned non-zero exit status 127. 52 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_render_narrow_notebook[--image-drawing-block].txt: -------------------------------------------------------------------------------- 1 | … 2 | … 3 | ─ 4 | 5 | ╭╮ 6 | … ╰╯ 7 | 8 | 9 | … 10 | … 11 | ─ 12 | 13 | … 14 | … 15 | … 16 | … 17 | … 18 | … 19 | … 20 | … 21 | … 22 | … 23 | … 24 | … 25 | … 26 | … 27 | … 28 | … 29 | … 30 | … 31 | … 32 | … 33 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 34 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 35 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 36 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 37 | … 38 | … 39 | … 40 | … 41 | … 42 | … 43 | 44 | ╭╮ 45 | … ╰╯ 46 | 47 | … ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 48 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 49 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 50 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 51 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 52 | 53 | … 54 | ─ 55 | 56 | 57 | 58 | 59 | 60 | 61 | ╭╮ 62 | … ╰╯ 63 | 64 | … … 65 | … 66 | … 67 | … 68 | … 69 | … 70 | … 71 | … 72 | 73 | ]8;id=0;file://{{ tempfile_path }}1.png\🖼]8;;\ 74 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 75 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 76 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 77 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 78 | 79 | … 80 | … 81 | … 82 | … 83 | 2 84 | … 85 | -------------------------------------------------------------------------------- /tests/unit/expected_outputs/test_render_narrow_notebook[--image-drawing-braille].txt: -------------------------------------------------------------------------------- 1 | … 2 | … 3 | ─ 4 | 5 | ╭╮ 6 | … ╰╯ 7 | 8 | 9 | … 10 | … 11 | ─ 12 | 13 | … 14 | … 15 | … 16 | … 17 | … 18 | … 19 | … 20 | … 21 | … 22 | … 23 | … 24 | … 25 | … 26 | … 27 | … 28 | … 29 | … 30 | … 31 | … 32 | … 33 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 34 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 35 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 36 | ]8;id=0;https://github.com/paw-lu/nbpreview\…]8;;\ 37 | … 38 | … 39 | … 40 | … 41 | … 42 | … 43 | 44 | ╭╮ 45 | … ╰╯ 46 | 47 | … ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 48 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 49 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 50 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 51 | ]8;id=0;file://{{ tempfile_path }}0.html\…]8;;\ 52 | 53 | … 54 | ─ 55 | 56 | 57 | 58 | 59 | 60 | 61 | ╭╮ 62 | … ╰╯ 63 | 64 | … … 65 | … 66 | … 67 | … 68 | … 69 | … 70 | … 71 | … 72 | 73 | ]8;id=0;file://{{ tempfile_path }}1.png\🖼]8;;\ 74 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 75 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 76 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 77 | ]8;id=0;file://{{ tempfile_path }}1.png\…]8;;\ 78 | 79 | … 80 | … 81 | … 82 | … 83 | 2 84 | … 85 | -------------------------------------------------------------------------------- /tests/unit/test_option_values.py: -------------------------------------------------------------------------------- 1 | """Test cases for the option_values module.""" 2 | import itertools 3 | 4 | from pygments import styles 5 | 6 | from nbpreview import option_values 7 | 8 | 9 | def test_get_all_available_themes() -> None: 10 | """It lists all available pygment themes.""" 11 | output = option_values.get_all_available_themes() 12 | expected_output = itertools.chain(styles.get_all_styles(), ("light", "dark")) 13 | assert list(output) == list(expected_output) 14 | -------------------------------------------------------------------------------- /tests/util/__ini__.py: -------------------------------------------------------------------------------- 1 | """Utilities for nbpreview test suite.""" 2 | -------------------------------------------------------------------------------- /tests/util/debug.py: -------------------------------------------------------------------------------- 1 | """Utilities for debugging failing tests.""" 2 | import difflib 3 | import pathlib 4 | from typing import Iterator, Union 5 | 6 | 7 | def diff( 8 | a: str, b: str, a_name: str = "", b_name: str = "", n: int = 3 9 | ) -> Iterator[str]: 10 | """Yield the diff between two strings.""" 11 | yield from difflib.context_diff( 12 | a=a.splitlines(), b=b.splitlines(), fromfile=a_name, tofile=b_name, n=n 13 | ) 14 | 15 | 16 | def write(content: str, filename: str, encoding: Union[str, None] = "utf8") -> None: 17 | """Write content to file.""" 18 | pathlib.Path(filename).with_suffix(".txt").write_text(content, encoding=encoding) 19 | -------------------------------------------------------------------------------- /tests/util/process_output.py: -------------------------------------------------------------------------------- 1 | """Utilities for processing test outputs.""" 2 | import pathlib 3 | import re 4 | import subprocess 5 | from pathlib import Path 6 | from typing import Iterator, Tuple 7 | 8 | 9 | def split_string( 10 | string: str, sub_length: int = 40, copy: bool = False 11 | ) -> Tuple[str, ...]: 12 | """Split a string into subsections less than or equal to new length. 13 | 14 | Args: 15 | string (str): The long string to split up. 16 | sub_length (int): The maximum length of the subsections. 17 | Defaults to 56. 18 | copy (bool): Copy output to clipboard. 19 | 20 | Returns: 21 | Tuple[str]: The string split into sections. 22 | """ 23 | string_length = len(string) 24 | split = tuple( 25 | string[begin : begin + sub_length] 26 | for begin in range(0, string_length, sub_length) 27 | ) 28 | if copy is True: 29 | string = str(split) 30 | copy_string(string) 31 | return split 32 | 33 | 34 | def copy_string(string: str) -> None: 35 | """Copies the string to clipboard. 36 | 37 | Uses pbcopy, so for now only works with macOS. 38 | """ 39 | subprocess.run("/usr/bin/pbcopy", text=True, input=string) # noqa: S603 40 | 41 | 42 | def write_output(string: str, test_name: str, replace_links: bool = True) -> None: 43 | """Write the output to the expected output's file.""" 44 | if replace_links: 45 | tempfile_link_pattern = re.compile( 46 | r"(?Pfile://)" 47 | r"(?P[\w\s/\\]*)" 48 | r"(?P\d+\.\w+)" 49 | ) 50 | string = tempfile_link_pattern.sub( 51 | lambda match: f"{match.group('prefix')}" 52 | "{{ tempfile_path }}" 53 | f"{match.group('suffix')}", 54 | string=string, 55 | ) 56 | output_directory = pathlib.Path(__file__).parent.parent / pathlib.Path( 57 | "unit", "expected_outputs" 58 | ) 59 | expected_output_file = output_directory / pathlib.Path(test_name).with_suffix( 60 | ".txt" 61 | ) 62 | expected_output_file.write_text(string) 63 | 64 | 65 | def _get_all_expected_output_paths() -> Iterator[Path]: 66 | """Get the paths of all the expected output files.""" 67 | file_dir = pathlib.Path(__file__).parent 68 | expected_outputs = ( 69 | file_dir.parent / pathlib.Path("unit", "expected_outputs") 70 | ).glob("**/*.txt") 71 | yield from expected_outputs 72 | 73 | 74 | def replace_expected_section(old: str, new: str) -> None: 75 | """Replace all occurrences of a section in the expected output.""" 76 | expected_output_paths = _get_all_expected_output_paths() 77 | for expected_output_path in expected_output_paths: 78 | old_text = expected_output_path.read_text() 79 | new_text = old_text.replace(old, new) 80 | expected_output_path.write_text(new_text) 81 | --------------------------------------------------------------------------------