├── .gitattributes
├── .github
└── workflows
│ ├── build.yaml
│ ├── docs.yaml
│ ├── nightly_lock.yaml
│ └── test.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE.txt
├── README.md
├── nbsite
├── __init__.py
├── __main__.py
├── __version.py
├── _shared_static
│ ├── alert.css
│ ├── dataframe.css
│ ├── gallery.css
│ ├── holoviz-icon-white.svg
│ ├── hv-sidebar-dropdown.css
│ ├── js
│ │ └── goatcounter.js
│ ├── mystnb.css
│ ├── nbsite.css
│ ├── notebook.css
│ └── scroller.css
├── _shared_templates
│ ├── copyright-last-updated.html
│ ├── github-stars-button.html
│ ├── hv-sidebar-dropdown.html
│ └── sidebar-nav-bs-alt.html
├── analytics
│ └── __init__.py
├── cmd.py
├── gallery
│ ├── __init__.py
│ ├── gen.py
│ └── thumbnailer.py
├── ipystartup.py
├── nb_interactivity_warning
│ └── __init__.py
├── nbbuild.py
├── paramdoc.py
├── pyodide
│ ├── ServiceHandler.js
│ ├── ServiceWorker.js
│ ├── WebWorker.js
│ ├── WorkerHandler.js
│ ├── __init__.py
│ ├── _static
│ │ ├── run_cell.js
│ │ └── runbutton.css
│ └── site.webmanifest
├── scripts
│ ├── __init__.py
│ ├── _clean_dist_html.py
│ └── _fix_links.py
├── shared_conf.py
├── templates
│ ├── basic
│ │ ├── README.md
│ │ ├── conf.py
│ │ └── index.rst
│ └── holoviz
│ │ ├── README.md
│ │ ├── about.rst
│ │ ├── conf.py
│ │ └── index.rst
├── tests
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_cmd.py
│ ├── test_nbbuild.py
│ └── test_util.py
└── util.py
├── pixi.toml
├── pyproject.toml
├── scripts
├── conda
│ ├── build.sh
│ └── recipe
│ │ └── meta.yaml
└── sync_git_tags.py
└── site
├── README.md
├── doc
├── _static
│ ├── css
│ │ └── custom.css
│ ├── favicon.ico
│ └── nbsite-logo.png
├── conf.py
├── development.md
├── index.md
├── playground
│ ├── api
│ │ └── index.rst
│ ├── example_gallery
│ │ ├── section1
│ │ │ └── thumbnails
│ │ │ │ └── thing1.png
│ │ └── section2
│ │ │ └── thumbnails
│ │ │ └── thing2.png
│ ├── gallery_backends
│ │ ├── option1
│ │ │ └── thumbnails
│ │ │ │ └── example1.png
│ │ └── option2
│ │ │ └── thumbnails
│ │ │ └── example2.png
│ ├── index.md
│ ├── markdown
│ │ ├── example.md
│ │ └── index.md
│ ├── mystmdnb
│ │ ├── holoviz.md
│ │ └── index.md
│ ├── mystnb
│ │ ├── holoviz.ipynb
│ │ └── index.ipynb
│ ├── notebook_directive
│ │ ├── example2.rst
│ │ └── index.md
│ ├── pyodide
│ │ ├── holoviz.md
│ │ └── index.md
│ └── rst
│ │ ├── example.rst
│ │ └── index.rst
└── user_guide
│ ├── extra_extensions.md
│ ├── gallery.md
│ ├── index.md
│ ├── param_doc.md
│ └── usage.md
├── dodo.py
├── dummy_package
├── build
│ └── lib
│ │ └── dummy
│ │ ├── __init__.py
│ │ └── module.py
├── dummy
│ ├── __init__.py
│ └── module.py
└── pyproject.toml
├── examples
└── playground
│ ├── example_gallery
│ ├── section1
│ │ └── thing1.ipynb
│ └── section2
│ │ └── thing2.ipynb
│ ├── gallery_backends
│ ├── option1
│ │ └── example1.ipynb
│ └── option2
│ │ └── example2.ipynb
│ └── notebook_directive
│ ├── example.ipynb
│ ├── example2.ipynb
│ ├── holoviz.ipynb
│ └── preexecuted.ipynb
├── future
└── mix
│ ├── holoviz.ipynb
│ └── index.md
└── requirements.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | nbsite/__init__.py export-subst
2 | setup.cfg export-subst
3 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: packages
2 | on:
3 | push:
4 | tags:
5 | - 'v[0-9]+.[0-9]+.[0-9]+'
6 | - 'v[0-9]+.[0-9]+.[0-9]+a[0-9]+'
7 | - 'v[0-9]+.[0-9]+.[0-9]+b[0-9]+'
8 | - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
9 | # Dry-run only
10 | workflow_dispatch:
11 | inputs:
12 | target:
13 | description: Build target
14 | type: choice
15 | options:
16 | - dryrun
17 | required: true
18 | default: dryrun
19 | schedule:
20 | - cron: '0 20 * * SUN'
21 |
22 | env:
23 | PYTHON_VERSION: "3.9"
24 | PACKAGE: "nbsite"
25 |
26 | defaults:
27 | run:
28 | shell: bash -e {0}
29 |
30 | jobs:
31 | waiting_room:
32 | name: Waiting Room
33 | runs-on: ubuntu-latest
34 | needs: [conda_build, pip_install]
35 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
36 | environment:
37 | name: publish
38 | steps:
39 | - run: echo "All builds have finished, have been approved, and ready to publish"
40 |
41 | pixi_lock:
42 | name: Pixi lock
43 | runs-on: ubuntu-latest
44 | steps:
45 | - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
46 |
47 | conda_build:
48 | name: Build Conda Packages
49 | needs: [pixi_lock]
50 | runs-on: 'ubuntu-latest'
51 | env:
52 | CONDA_UPLOAD_TOKEN: ${{ secrets.CONDA_UPLOAD_TOKEN }}
53 | steps:
54 | - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
55 | with:
56 | environments: "build"
57 | download-data: false
58 | install: false
59 | - name: conda build
60 | run: pixi run -e build build-conda
61 | - uses: actions/upload-artifact@v4
62 | if: always()
63 | with:
64 | name: conda
65 | path: dist/*.tar.bz2
66 | if-no-files-found: error
67 |
68 | conda-publish:
69 | name: Publish Conda
70 | runs-on: ubuntu-latest
71 | needs: [conda_build, waiting_room]
72 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
73 | defaults:
74 | run:
75 | shell: bash -el {0}
76 | steps:
77 | - uses: actions/download-artifact@v4
78 | with:
79 | name: conda
80 | path: dist/
81 | - name: Set environment variables
82 | run: |
83 | echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
84 | echo "CONDA_FILE=$(ls dist/*.tar.bz2)" >> $GITHUB_ENV
85 | - uses: conda-incubator/setup-miniconda@v3
86 | with:
87 | miniconda-version: "latest"
88 | channels: "conda-forge"
89 | - name: conda setup
90 | run: |
91 | conda install -y anaconda-client
92 | - name: conda dev upload
93 | if: contains(env.TAG, 'a') || contains(env.TAG, 'b') || contains(env.TAG, 'rc')
94 | run: |
95 | anaconda --token ${{ secrets.CONDA_UPLOAD_TOKEN }} upload --user pyviz --label=dev --label=tooling_dev $CONDA_FILE
96 | - name: conda main upload
97 | if: (!(contains(env.TAG, 'a') || contains(env.TAG, 'b') || contains(env.TAG, 'rc')))
98 | run: |
99 | anaconda --token ${{ secrets.CONDA_UPLOAD_TOKEN }} upload --user pyviz --label=dev --label=main --label=tooling_dev $CONDA_FILE
100 |
101 | pip_build:
102 | name: Build PyPI
103 | needs: [pixi_lock]
104 | runs-on: 'ubuntu-latest'
105 | steps:
106 | - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
107 | with:
108 | environments: "build"
109 | download-data: false
110 | install: false
111 | - name: Build package
112 | run: pixi run -e build build-pip
113 | - uses: actions/upload-artifact@v4
114 | if: always()
115 | with:
116 | name: pip
117 | path: dist/
118 | if-no-files-found: error
119 |
120 | pip_install:
121 | name: Install PyPI
122 | runs-on: "ubuntu-latest"
123 | needs: [pip_build]
124 | steps:
125 | - uses: actions/setup-python@v5
126 | with:
127 | python-version: ${{ env.PYTHON_VERSION }}
128 | - uses: actions/download-artifact@v4
129 | with:
130 | name: pip
131 | path: dist/
132 | - name: Install package
133 | run: python -m pip install dist/*.whl
134 | - name: Import package
135 | run: python -c "import $PACKAGE; print($PACKAGE._version.__version__)"
136 |
137 | pip_publish:
138 | name: Publish to PyPI
139 | runs-on: ubuntu-latest
140 | needs: [pip_build, waiting_room]
141 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
142 | permissions:
143 | id-token: write
144 | steps:
145 | - uses: actions/download-artifact@v4
146 | with:
147 | name: pip
148 | path: dist/
149 | - name: Publish to PyPi
150 | uses: pypa/gh-action-pypi-publish@release/v1
151 | with:
152 | user: ${{ secrets.PPU }}
153 | password: ${{ secrets.PPP }}
154 | packages-dir: dist/
155 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yaml:
--------------------------------------------------------------------------------
1 | name: docs
2 | on:
3 | push:
4 | tags:
5 | - 'v[0-9]+.[0-9]+.[0-9]+'
6 | - 'v[0-9]+.[0-9]+.[0-9]+a[0-9]+'
7 | - 'v[0-9]+.[0-9]+.[0-9]+b[0-9]+'
8 | - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
9 | workflow_dispatch:
10 | inputs:
11 | target:
12 | description: 'Site to build and deploy, or dry-run'
13 | type: choice
14 | options:
15 | - dev
16 | - main
17 | - dryrun
18 | required: true
19 | default: dryrun
20 | schedule:
21 | - cron: '0 20 * * SUN'
22 |
23 | defaults:
24 | run:
25 | shell: bash -e {0}
26 |
27 | env:
28 | PYTHON_VERSION: "3.11"
29 | DESC: "Documentation build"
30 |
31 | jobs:
32 | pixi_lock:
33 | name: Pixi lock
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
37 |
38 | build_docs:
39 | name: Build Documentation
40 | needs: [pixi_lock]
41 | runs-on: 'ubuntu-latest'
42 | timeout-minutes: 120
43 | outputs:
44 | tag: ${{ steps.vars.outputs.tag }}
45 | env:
46 | DISPLAY: ":99.0"
47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 | steps:
49 | - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
50 | with:
51 | environments: docs
52 | - name: Build documentation
53 | run: pixi run -e docs docs-build
54 | - uses: actions/upload-artifact@v4
55 | if: always()
56 | with:
57 | name: docs
58 | if-no-files-found: error
59 | path: site/builtdocs
60 | - name: Set output
61 | id: vars
62 | run: |
63 | echo "Deploying from ref %{GITHUB_REF#refs/*/}"
64 | echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
65 | - name: report failure
66 | if: failure()
67 | run: cat /tmp/sphinx-*.log | tail -n 100
68 |
69 | docs_publish:
70 | name: Publish Documentation
71 | runs-on: "ubuntu-latest"
72 | needs: [build_docs]
73 | env:
74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
76 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
77 | AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
78 | steps:
79 | - uses: actions/download-artifact@v4
80 | with:
81 | name: docs
82 | path: site/builtdocs/
83 | - name: Set output
84 | id: vars
85 | run: echo "tag=${{ needs.docs_build.outputs.tag }}" >> $GITHUB_OUTPUT
86 | - name: Deploy dev
87 | if: |
88 | (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'dev') ||
89 | (github.event_name == 'push' && (contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc')))
90 | uses: peaceiris/actions-gh-pages@v4
91 | with:
92 | personal_token: ${{ secrets.ACCESS_TOKEN }}
93 | external_repository: holoviz-dev/nbsite-dev
94 | publish_dir: ./site/builtdocs
95 | force_orphan: true
96 | - name: Deploy main
97 | if: |
98 | (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'main') ||
99 | (github.event_name == 'push' && !(contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc')))
100 | uses: peaceiris/actions-gh-pages@v4
101 | with:
102 | github_token: ${{ secrets.GITHUB_TOKEN }}
103 | publish_dir: ./site/builtdocs
104 | cname: nbsite.holoviz.org
105 | force_orphan: true
106 |
--------------------------------------------------------------------------------
/.github/workflows/nightly_lock.yaml:
--------------------------------------------------------------------------------
1 | name: nightly_lock
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | env:
8 | PACKAGE: "nbsite"
9 |
10 | jobs:
11 | pixi_lock:
12 | if: ${{ !github.event.repository.fork }}
13 | name: Pixi lock
14 | runs-on: ubuntu-latest
15 | timeout-minutes: 5
16 | steps:
17 | - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
18 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | # things not included
2 | # language
3 | # notifications - no email notifications set up
4 | name: tests
5 | on:
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | branches:
11 | - '*'
12 | workflow_dispatch:
13 | inputs:
14 | target:
15 | description: "How much of the test suite to run"
16 | type: choice
17 | default: default
18 | options:
19 | - default
20 | - full
21 | cache:
22 | description: "Use cache"
23 | type: boolean
24 | default: true
25 | schedule:
26 | - cron: '0 20 * * SUN'
27 |
28 | concurrency:
29 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
30 | cancel-in-progress: true
31 |
32 | defaults:
33 | run:
34 | shell: bash -e {0}
35 |
36 | env:
37 | DISPLAY: ":99.0"
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | COV: "--cov=./nbsite --cov-report=xml"
40 |
41 | jobs:
42 | pre_commit:
43 | name: Run pre-commit
44 | runs-on: "ubuntu-latest"
45 | steps:
46 | - uses: holoviz-dev/holoviz_tasks/pre-commit@v0
47 |
48 | setup:
49 | name: Setup workflow
50 | runs-on: ubuntu-latest
51 | permissions:
52 | pull-requests: read
53 | outputs:
54 | code_change: ${{ steps.filter.outputs.code }}
55 | matrix: ${{ env.MATRIX }}
56 | steps:
57 | - uses: actions/checkout@v4
58 | if: github.event_name != 'pull_request'
59 | - name: Check for code changes
60 | uses: dorny/paths-filter@v3
61 | id: filter
62 | with:
63 | filters: |
64 | code:
65 | - 'nbsite/**'
66 | - 'pixi.toml'
67 | - 'pyproject.toml'
68 | - '.github/workflows/test.yaml'
69 | - name: Set matrix option
70 | run: |
71 | if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
72 | OPTION=${{ github.event.inputs.target }}
73 | elif [[ '${{ github.event_name }}' == 'schedule' ]]; then
74 | OPTION="full"
75 | elif [[ '${{ github.event_name }}' == 'push' && '${{ github.ref_type }}' == 'tag' ]]; then
76 | OPTION="full"
77 | else
78 | OPTION="default"
79 | fi
80 | echo "MATRIX_OPTION=$OPTION" >> $GITHUB_ENV
81 | - name: Set test matrix with 'default' option
82 | if: env.MATRIX_OPTION == 'default'
83 | run: |
84 | MATRIX=$(jq -nsc '{
85 | "os": ["ubuntu-latest", "macos-latest", "windows-latest"],
86 | "environment": ["test-39", "test-312"],
87 | }')
88 | echo "MATRIX=$MATRIX" >> $GITHUB_ENV
89 | - name: Set test matrix with 'full' option
90 | if: env.MATRIX_OPTION == 'full'
91 | run: |
92 | MATRIX=$(jq -nsc '{
93 | "os": ["ubuntu-latest", "macos-latest", "windows-latest"],
94 | "environment": ["test-39", "test-310", "test-311", "test-312", "test-313"],
95 | }')
96 | echo "MATRIX=$MATRIX" >> $GITHUB_ENV
97 |
98 | pixi_lock:
99 | name: Pixi lock
100 | runs-on: ubuntu-latest
101 | steps:
102 | - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
103 | with:
104 | cache: ${{ github.event.inputs.cache == 'true' || github.event.inputs.cache == '' }}
105 |
106 | unit_test_suite:
107 | name: unit:${{ matrix.environment }}:${{ matrix.os }}
108 | needs: [pre_commit, setup, pixi_lock]
109 | runs-on: ${{ matrix.os }}
110 | if: needs.setup.outputs.code_change == 'true'
111 | strategy:
112 | fail-fast: false
113 | matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
114 | timeout-minutes: 60
115 | steps:
116 | - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
117 | with:
118 | environments: ${{ matrix.environment }}
119 | - name: Test Unit
120 | run: |
121 | pixi run -e ${{ matrix.environment }} test-unit $COV
122 | - name: Test Examples
123 | run: |
124 | pixi run -e ${{ matrix.environment }} test-example
125 |
126 | result_test_suite:
127 | name: result:test
128 | needs: [unit_test_suite]
129 | if: always()
130 | runs-on: ubuntu-latest
131 | steps:
132 | - name: check for failures
133 | if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
134 | run: echo job failed && exit 1
135 |
136 | build_docs:
137 | name: Documentation
138 | needs: [pre_commit]
139 | runs-on: 'ubuntu-latest'
140 | timeout-minutes: 120
141 | defaults:
142 | run:
143 | shell: bash -l {0}
144 | env:
145 | DESC: "Documentation build"
146 | steps:
147 | - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
148 | with:
149 | environments: docs
150 | - name: Build docs
151 | run: pixi run -e docs docs-build
152 | - name: Deploy dev
153 | if: ${{ github.event.repository.fork == false && github.event.pull_request.head.repo.fork == false }}
154 | uses: peaceiris/actions-gh-pages@v4
155 | with:
156 | personal_token: ${{ secrets.ACCESS_TOKEN }}
157 | external_repository: pyviz-dev/nbsite-dev
158 | publish_dir: ./site/builtdocs
159 | force_orphan: true
160 | - name: clean up
161 | run: pixi run -e docs docs-clean
162 | - name: check no leftover
163 | run: git diff --quiet || exit 1
164 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | #*#
3 | *~
4 | *.egg
5 | *.egg-info
6 | *.swp
7 | *.DS_Store
8 | *.so
9 | *.o
10 | *.out
11 | *.lock
12 | *.doit*
13 | pip-wheel-metadata
14 | .tox
15 | .venv/
16 |
17 | # ipython
18 | */.ipynb_checkpoints
19 |
20 | # autover
21 | */.version
22 |
23 | # nbsite
24 | # this dir contains the whole website and should not be checked in on main
25 | site/builtdocs/
26 |
27 | site/.venv/
28 |
29 | build/
30 | dist
31 |
32 | # pixi
33 | .pixi
34 | pixi.lock
35 |
36 | # mystnb
37 | jupyter_execute/
38 |
39 | # version
40 | _version.py
41 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | default_stages: [pre-commit]
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v5.0.0
5 | hooks:
6 | - id: check-builtin-literals
7 | - id: check-case-conflict
8 | - id: check-docstring-first
9 | - id: check-executables-have-shebangs
10 | - id: check-toml
11 | - id: check-json
12 | - id: detect-private-key
13 | - id: end-of-file-fixer
14 | exclude: \.min\.js$
15 | - id: trailing-whitespace
16 | - repo: https://github.com/astral-sh/ruff-pre-commit
17 | rev: v0.11.4
18 | hooks:
19 | - id: ruff
20 | files: nbsite/
21 | - repo: https://github.com/pycqa/isort
22 | rev: 6.0.1
23 | hooks:
24 | - id: isort
25 | name: isort (python)
26 | - repo: https://github.com/codespell-project/codespell
27 | rev: v2.4.1
28 | hooks:
29 | - id: codespell
30 | additional_dependencies:
31 | - tomli
32 | - repo: https://github.com/hoxbro/prettier-pre-commit
33 | rev: v3.5.3
34 | hooks:
35 | - id: prettier
36 | types_or: [css]
37 | ci:
38 | autofix_prs: false
39 | autoupdate_schedule: quarterly
40 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017-18, PyViz
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of cube-explorer nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | -----------------
4 |
5 | # NBSite: Build a tested, sphinx-based website from notebooks
6 |
7 | | | |
8 | | --- | --- |
9 | | Build Status | [](https://github.com/holoviz-dev/nbsite/actions?query=workflow%3Atests)
10 | | Coverage | [](https://codecov.io/gh/holoviz-dev/nbsite) |
11 | | Latest dev release | [](https://github.com/holoviz-dev/nbsite/tags) [](https://holoviz-dev.github.io/nbsite-dev/)|
12 | | Latest release | [](https://github.com/holoviz-dev/nbsite/releases) [](https://pypi.python.org/pypi/nbsite) [](https://anaconda.org/pyviz/nbsite) [](https://anaconda.org/conda-forge/nbsite) [](https://anaconda.org/anaconda/nbsite) |
13 | | Docs | [](https://github.com/pyviz/nbsite/tree/gh-pages) [](https://nbsite.pyviz.org) |
14 |
15 | ---
16 |
17 | **DISCLAIMER**
18 |
19 | NBSite is a tool supporting the developers of the [HoloViz](https://holoviz/org) project. As such it is tailored to their use case, workflow, and **breaking changes may occur at any time**. We suggest that before using NBSite you investigate alternatives such as [MyST-NB](https://myst-nb.readthedocs.io) or [nbsphinx](https://nbsphinx.readthedocs.io/). If you select NBSite anyway, we recommend that you pin its version.
20 |
21 | ---
22 |
23 | NBSite lets you build a website from a set of notebooks plus a minimal
24 | amount of config. The idea behind NBSite is that notebooks can simultaneously be documentation (things you want to tell people about), examples (a starting point for people to run and use themselves), and test cases (see nbsmoke).
25 |
26 | ## Sites built with NBSite
27 |
28 | Non exhaustive list of sites built with NBSite (as of November 2022):
29 |
30 | - [Panel](https://panel.holoviz.org/)
31 | - [hvPlot](https://hvplot.holoviz.org/)
32 | - [HoloViews](https://holoviews.org/)
33 | - [GeoViews](https://geoviews.org/)
34 | - [Datashader](https://datashader.org/)
35 | - [Lumen](https://lumen.holoviz.org/)
36 | - [Colorcet](https://colorcet.holoviz.org/)
37 | - [Param](https://param.holoviz.org/)
38 | - [HoloViz.org](https://holoviz.org/)
39 | - [examples.pyviz.org](https://examples.pyviz.org/)
40 | - [PyViz.org](https://pyviz.org/)
41 |
42 | ## About HoloViz
43 |
44 | NBSite is part of the HoloViz initiative for making Python-based visualization tools work well together.
45 | See [holoviz.org](https://holoviz.org/) for related packages that you can use with NBSite and
46 | [status.holoviz.org](https://status.holoviz.org/) for the current status of each HoloViz project.
47 |
--------------------------------------------------------------------------------
/nbsite/__init__.py:
--------------------------------------------------------------------------------
1 | from .__version import __version__ # noqa: F401
2 |
--------------------------------------------------------------------------------
/nbsite/__main__.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import inspect
3 |
4 | from .cmd import build, generate_rst, init
5 |
6 |
7 | def _add_common_args(parser,*names):
8 | common = {
9 | '--project-root': dict(type=str,help='defaults to current working directory',default=''),
10 | '--examples': dict(type=str,help='if relative, should be relative to project-root',default='examples'),
11 | '--doc': dict(type=str,help='if relative, should be relative to project-root',default='doc'),
12 | '--overwrite': dict(action='store_true', help='whether to overwrite any files [DANGEROUS]')
13 | }
14 | for name in names:
15 | parser.add_argument(name,**common[name])
16 |
17 | def _set_defaults(parser,fn):
18 | parser.set_defaults(func=lambda args: fn( **{k: getattr(args,k) for k in vars(args) if k!='func'} ))
19 |
20 | def main(args=None):
21 | parser = argparse.ArgumentParser(description="nbsite commands")
22 | subparsers = parser.add_subparsers(title='available commands')
23 |
24 | init_parser = subparsers.add_parser("init", help=inspect.getdoc(init))
25 | _add_common_args(init_parser,'--project-root','--doc')
26 | init_parser.add_argument('--theme', type=str, help='sphinx theme to use in template', choices=['holoviz', ''], default='')
27 | _set_defaults(init_parser,init)
28 |
29 | generaterst_parser = subparsers.add_parser("generate-rst", help=inspect.getdoc(generate_rst))
30 | _add_common_args(generaterst_parser,'--project-root','--doc','--examples', '--overwrite')
31 | generaterst_parser.add_argument('--project-name', type=str, help='name of project')
32 | generaterst_parser.add_argument('--host',type=str,help='host to use when generating notebook links',default='GitHub')
33 | generaterst_parser.add_argument('--org',type=str,help='github organization',default='')
34 | generaterst_parser.add_argument('--repo',type=str,help='name of repo',default='')
35 | generaterst_parser.add_argument('--branch',type=str,help='branch to point to in notebook links',default='main')
36 | generaterst_parser.add_argument('--offset',type=int,help='number of cells to delete from top of notebooks',default=0)
37 | generaterst_parser.add_argument('--nblink',type=str,help='where to place notebook links',choices=['bottom', 'top', 'both', 'none'], default='bottom')
38 | generaterst_parser.add_argument('--binder',type=str,help='where to place binder link',choices=['bottom', 'top', 'both', 'none'], default='none')
39 | generaterst_parser.add_argument('--skip',type=str,help='notebooks to skip running; comma separated case insensitive re to match',default='')
40 | generaterst_parser.add_argument('--keep-numbers',action='store_true',help='whether to keep the leading numbers of notebook URLs and titles')
41 | generaterst_parser.add_argument('--disable-interactivity-warning',action='store_true',help='whether to disable interactivity warnings')
42 | _set_defaults(generaterst_parser,generate_rst)
43 |
44 | build_parser = subparsers.add_parser("build", help=inspect.getdoc(build))
45 | build_parser.add_argument('--what',type=str,help='type of output to generate',default='html')
46 | build_parser.add_argument('--project-name', type=str, help='name of project', default='')
47 | build_parser.add_argument('--org',type=str,help='github organization',default='')
48 | build_parser.add_argument('--host',type=str,help='host to use when generating notebook links',default='GitHub')
49 | build_parser.add_argument('--repo',type=str,help='name of repo',default='')
50 | build_parser.add_argument('--branch',type=str,help='branch to point to in notebook links',default='main')
51 | build_parser.add_argument('--binder',type=str,help='where to place binder link',choices=['bottom', 'top', 'both', 'none'], default='none')
52 | build_parser.add_argument('--disable-parallel',action=argparse.BooleanOptionalAction,help='whether to disable building the docs in parallel')
53 |
54 | build_parser.add_argument('--output',type=str,help='where to place output',default="builtdocs")
55 | _add_common_args(build_parser,'--project-root','--doc','--examples', '--overwrite')
56 | build_parser.add_argument('--examples-assets',type=str,default="assets",
57 | help='dir in which assets for examples are located - if relative, should be relative to project-root')
58 | build_parser.add_argument('--clean-dry-run',action='store_true',help='whether to not actually delete files from output (useful for uploading)')
59 | build_parser.add_argument('--inspect-links',action='store_true',help='whether to not to print all links')
60 | _set_defaults(build_parser,build)
61 |
62 | args = parser.parse_args()
63 | return args.func(args) if hasattr(args,'func') else parser.error("must supply command to run")
64 |
65 | if __name__ == "__main__":
66 | main()
67 |
--------------------------------------------------------------------------------
/nbsite/__version.py:
--------------------------------------------------------------------------------
1 | """Define the package version.
2 |
3 | Called __version.py as setuptools_scm will create a _version.py
4 |
5 | """
6 |
7 | import os.path
8 |
9 | PACKAGE = "nbsite"
10 |
11 | try:
12 | # For performance reasons on imports, avoid importing setuptools_scm
13 | # if not in a .git folder
14 | if os.path.exists(os.path.join(os.path.dirname(__file__), "..", ".git")):
15 | # If setuptools_scm is installed (e.g. in a development environment with
16 | # an editable install), then use it to determine the version dynamically.
17 | from setuptools_scm import get_version
18 |
19 | # This will fail with LookupError if the package is not installed in
20 | # editable mode or if Git is not installed.
21 | __version__ = get_version(root="..", relative_to=__file__)
22 | else:
23 | raise FileNotFoundError
24 | except (ImportError, LookupError, FileNotFoundError):
25 | # As a fallback, use the version that is hard-coded in the file.
26 | try:
27 | # __version__ was added in _version in setuptools-scm 7.0.0, we rely on
28 | # the hopefully stable version variable.
29 | from ._version import version as __version__
30 | except (ModuleNotFoundError, ImportError):
31 | # Either _version doesn't exist (ModuleNotFoundError) or version isn't
32 | # in _version (ImportError). ModuleNotFoundError is a subclass of
33 | # ImportError, let's be explicit anyway.
34 |
35 | # Try something else:
36 | from importlib.metadata import PackageNotFoundError, version
37 |
38 | try:
39 | __version__ = version(PACKAGE)
40 | except PackageNotFoundError:
41 | # The user is probably trying to run this without having installed
42 | # the package.
43 | __version__ = "0.0.0+unknown"
44 |
45 | __all__ = ("__version__",)
46 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/alert.css:
--------------------------------------------------------------------------------
1 | .alert {
2 | padding: 0.75rem 1.25rem;
3 | margin-bottom: 1rem;
4 | border: 1px solid transparent;
5 | border-radius: 0.25rem;
6 | }
7 |
8 | .alert-heading {
9 | color: inherit;
10 | }
11 |
12 | .alert-link {
13 | font-weight: bold;
14 | }
15 |
16 | .alert-dismissible .close {
17 | position: relative;
18 | top: -0.75rem;
19 | right: -1.25rem;
20 | padding: 0.75rem 1.25rem;
21 | color: inherit;
22 | }
23 |
24 | .alert-success {
25 | background-color: #dff0d8;
26 | border-color: #d0e9c6;
27 | color: #3c763d;
28 | }
29 |
30 | .alert-success hr {
31 | border-top-color: #c1e2b3;
32 | }
33 |
34 | .alert-success .alert-link {
35 | color: #2b542c;
36 | }
37 |
38 | .alert-info {
39 | background-color: #d9edf7;
40 | border-color: #bcdff1;
41 | color: #31708f;
42 | }
43 |
44 | .alert-info hr {
45 | border-top-color: #a6d5ec;
46 | }
47 |
48 | .alert-info .alert-link {
49 | color: #245269;
50 | }
51 |
52 | .alert-warning {
53 | background-color: #fcf8e3;
54 | border-color: #faf2cc;
55 | color: #8a6d3b;
56 | }
57 |
58 | .alert-warning hr {
59 | border-top-color: #f7ecb5;
60 | }
61 |
62 | .alert-warning .alert-link {
63 | color: #66512c;
64 | }
65 |
66 | .alert-danger {
67 | background-color: #f2dede;
68 | border-color: #ebcccc;
69 | color: #a94442;
70 | }
71 |
72 | .alert-danger hr {
73 | border-top-color: #e4b9b9;
74 | }
75 |
76 | .alert-danger .alert-link {
77 | color: #843534;
78 | }
79 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/dataframe.css:
--------------------------------------------------------------------------------
1 | table.dataframe {
2 | margin-left: auto;
3 | margin-right: auto;
4 | border: none;
5 | border-collapse: collapse;
6 | border-spacing: 0;
7 | font-size: 12px;
8 | table-layout: auto;
9 | width: 100%;
10 | }
11 |
12 | .dataframe tr,
13 | .dataframe th,
14 | .dataframe td {
15 | text-align: right;
16 | vertical-align: middle;
17 | padding: 0.5em 0.5em;
18 | line-height: normal;
19 | white-space: normal;
20 | max-width: none;
21 | border: none;
22 | }
23 |
24 | .dataframe tbody {
25 | display: table-row-group;
26 | vertical-align: middle;
27 | border-color: inherit;
28 | }
29 |
30 | .dataframe tbody tr:nth-child(odd) {
31 | background-color: var(--pst-color-surface, #f5f5f5);
32 | color: var(--pst-color-text-base);
33 | }
34 |
35 | .dataframe thead {
36 | border-bottom: 1px solid var(--pst-color-border);
37 | vertical-align: bottom;
38 | }
39 |
40 | .dataframe tbody tr:hover {
41 | background-color: #e1f5fe;
42 | color: #000000;
43 | cursor: pointer;
44 | }
45 |
46 | :host {
47 | overflow: auto;
48 | padding-right: 1px;
49 | }
50 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/gallery.css:
--------------------------------------------------------------------------------
1 | ul.tab {
2 | list-style-type: none;
3 | padding: 0;
4 | overflow: hidden;
5 | }
6 |
7 | ul.tab li {
8 | float: left;
9 | padding: 0;
10 | }
11 |
12 | ul.tab li label {
13 | padding: 6px;
14 | border: 1px solid #ccc;
15 | display: inline-block;
16 | }
17 |
18 | ul.tab li input[type="radio"] {
19 | opacity: 0;
20 | width: 1px;
21 | height: 1px;
22 | }
23 |
24 | ul.tab li input[type="radio"]:checked ~ label {
25 | background: var(--pst-color-primary);
26 | color: white;
27 | }
28 |
29 | dl.dl-horizontal {
30 | padding-left: 50px;
31 | }
32 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/holoviz-icon-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
210 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/hv-sidebar-dropdown.css:
--------------------------------------------------------------------------------
1 | /* Sidebar styling for the HoloViz dropdown section */
2 | .hv-sb-dd {
3 | margin-bottom: 0.5em;
4 | }
5 |
6 | .hv-sb-dd .btn-group {
7 | width: 100%;
8 | }
9 |
10 | .hv-sb-dd .hv-sb-dd-value {
11 | text-align: start;
12 | font-size: 0.9rem;
13 | }
14 |
15 | .hv-sb-dd .btn {
16 | background-color: var(--pst-color-surface);
17 | color: var(--pst-color-text-base);
18 | }
19 |
20 | .hv-sb-dd a.btn:hover {
21 | color: var(--pst-color-link-hover);
22 | text-decoration: underline;
23 | text-decoration-thickness: max(3px, 0.1875rem, 0.12em);
24 | }
25 |
26 | .hv-sb-dd .dropdown-toggle-split {
27 | border-left: solid 1px lightgray;
28 | }
29 |
30 | .hv-sb-dd .dropdown-menu {
31 | width: 100%;
32 | background-color: var(--pst-color-surface);
33 | color: var(--pst-color-text-base);
34 | }
35 |
36 | .hv-sb-dd .dropdown-item {
37 | font-size: 0.8rem;
38 | }
39 |
40 | .hv-sb-dd .hv-icon {
41 | font-size: 0.75em;
42 | margin-left: 0.3em;
43 | }
44 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/js/goatcounter.js:
--------------------------------------------------------------------------------
1 | // GoatCounter: https://www.goatcounter.com
2 | // This file is released under the ISC license: https://opensource.org/licenses/ISC
3 | ;(function() {
4 | 'use strict';
5 |
6 | if (window.goatcounter && window.goatcounter.vars) // Compatibility with very old version; do not use.
7 | window.goatcounter = window.goatcounter.vars
8 | else
9 | window.goatcounter = window.goatcounter || {}
10 |
11 | // Load settings from data-goatcounter-settings.
12 | var s = document.querySelector('script[data-goatcounter]')
13 | if (s && s.dataset.goatcounterSettings) {
14 | try { var set = JSON.parse(s.dataset.goatcounterSettings) }
15 | catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) }
16 | for (var k in set)
17 | if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1)
18 | window.goatcounter[k] = set[k]
19 | }
20 |
21 | var enc = encodeURIComponent
22 |
23 | // Get all data we're going to send off to the counter endpoint.
24 | var get_data = function(vars) {
25 | var data = {
26 | p: (vars.path === undefined ? goatcounter.path : vars.path),
27 | r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer),
28 | t: (vars.title === undefined ? goatcounter.title : vars.title),
29 | e: !!(vars.event || goatcounter.event),
30 | s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)],
31 | b: is_bot(),
32 | q: location.search,
33 | }
34 |
35 | var rcb, pcb, tcb // Save callbacks to apply later.
36 | if (typeof(data.r) === 'function') rcb = data.r
37 | if (typeof(data.t) === 'function') tcb = data.t
38 | if (typeof(data.p) === 'function') pcb = data.p
39 |
40 | if (is_empty(data.r)) data.r = document.referrer
41 | if (is_empty(data.t)) data.t = document.title
42 | if (is_empty(data.p)) data.p = get_path()
43 |
44 | if (rcb) data.r = rcb(data.r)
45 | if (tcb) data.t = tcb(data.t)
46 | if (pcb) data.p = pcb(data.p)
47 | return data
48 | }
49 |
50 | // Check if a value is "empty" for the purpose of get_data().
51 | var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' }
52 |
53 | // See if this looks like a bot; there is some additional filtering on the
54 | // backend, but these properties can't be fetched from there.
55 | var is_bot = function() {
56 | // Headless browsers are probably a bot.
57 | var w = window, d = document
58 | if (w.callPhantom || w._phantom || w.phantom)
59 | return 150
60 | if (w.__nightmare)
61 | return 151
62 | if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate)
63 | return 152
64 | if (navigator.webdriver)
65 | return 153
66 | return 0
67 | }
68 |
69 | // Object to urlencoded string, starting with a ?.
70 | var urlencode = function(obj) {
71 | var p = []
72 | for (var k in obj)
73 | if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false)
74 | p.push(enc(k) + '=' + enc(obj[k]))
75 | return '?' + p.join('&')
76 | }
77 |
78 | // Show a warning in the console.
79 | var warn = function(msg) {
80 | if (console && 'warn' in console)
81 | console.warn('goatcounter: ' + msg)
82 | }
83 |
84 | // Get the endpoint to send requests to.
85 | var get_endpoint = function() {
86 | var s = document.querySelector('script[data-goatcounter]')
87 | if (s && s.dataset.goatcounter)
88 | return s.dataset.goatcounter
89 | return (goatcounter.endpoint || window.counter) // counter is for compat; don't use.
90 | }
91 |
92 | // Get current path.
93 | var get_path = function() {
94 | var loc = location,
95 | c = document.querySelector('link[rel="canonical"][href]')
96 | if (c) { // May be relative or point to different domain.
97 | var a = document.createElement('a')
98 | a.href = c.href
99 | if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, ''))
100 | loc = a
101 | }
102 | return (loc.pathname + loc.search) || '/'
103 | }
104 |
105 | // Run function after DOM is loaded.
106 | var on_load = function(f) {
107 | if (document.body === null)
108 | document.addEventListener('DOMContentLoaded', function() { f() }, false)
109 | else
110 | f()
111 | }
112 |
113 | // Filter some requests that we (probably) don't want to count.
114 | goatcounter.filter = function() {
115 | if ('visibilityState' in document && document.visibilityState === 'prerender')
116 | return 'visibilityState'
117 | if (!goatcounter.allow_frame && location !== parent.location)
118 | return 'frame'
119 | if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/))
120 | return 'localhost'
121 | if (!goatcounter.allow_local && location.protocol === 'file:')
122 | return 'localfile'
123 | if (localStorage && localStorage.getItem('skipgc') === 't')
124 | return 'disabled with #toggle-goatcounter'
125 | return false
126 | }
127 |
128 | // Get URL to send to GoatCounter.
129 | window.goatcounter.url = function(vars) {
130 | var data = get_data(vars || {})
131 | if (data.p === null) // null from user callback.
132 | return
133 | data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control.
134 |
135 | var endpoint = get_endpoint()
136 | if (!endpoint)
137 | return warn('no endpoint found')
138 |
139 | return endpoint + urlencode(data)
140 | }
141 |
142 | // Count a hit.
143 | window.goatcounter.count = function(vars) {
144 | var f = goatcounter.filter()
145 | if (f)
146 | return warn('not counting because of: ' + f)
147 |
148 | var url = goatcounter.url(vars)
149 | if (!url)
150 | return warn('not counting because path callback returned null')
151 |
152 | if (navigator.sendBeacon)
153 | navigator.sendBeacon(url)
154 | else { // Fallback for (very) old browsers.
155 | var img = document.createElement('img')
156 | img.src = url
157 | img.style.position = 'absolute' // Affect layout less.
158 | img.style.bottom = '0px'
159 | img.style.width = '1px'
160 | img.style.height = '1px'
161 | img.loading = 'eager'
162 | img.setAttribute('alt', '')
163 | img.setAttribute('aria-hidden', 'true')
164 |
165 | var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) }
166 | img.addEventListener('load', rm, false)
167 | document.body.appendChild(img)
168 | }
169 | }
170 |
171 | // Get a query parameter.
172 | window.goatcounter.get_query = function(name) {
173 | var s = location.search.substr(1).split('&')
174 | for (var i = 0; i < s.length; i++)
175 | if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0)
176 | return s[i].substr(name.length + 1)
177 | }
178 |
179 | // Track click events.
180 | window.goatcounter.bind_events = function() {
181 | if (!document.querySelectorAll) // Just in case someone uses an ancient browser.
182 | return
183 |
184 | var send = function(elem) {
185 | return function() {
186 | goatcounter.count({
187 | event: true,
188 | path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''),
189 | title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''),
190 | referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''),
191 | })
192 | }
193 | }
194 |
195 | Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) {
196 | if (elem.dataset.goatcounterBound)
197 | return
198 | var f = send(elem)
199 | elem.addEventListener('click', f, false)
200 | elem.addEventListener('auxclick', f, false) // Middle click.
201 | elem.dataset.goatcounterBound = 'true'
202 | })
203 | }
204 |
205 | // Add a "visitor counter" frame or image.
206 | window.goatcounter.visit_count = function(opt) {
207 | on_load(function() {
208 | opt = opt || {}
209 | opt.type = opt.type || 'html'
210 | opt.append = opt.append || 'body'
211 | opt.path = opt.path || get_path()
212 | opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')}
213 |
214 | opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?'
215 | if (opt.no_branding) opt.attr['src'] += '&no_branding=1'
216 | if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style)
217 | if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start)
218 | if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end)
219 |
220 | var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type]
221 | if (!tag)
222 | return warn('visit_count: unknown type: ' + opt.type)
223 |
224 | if (opt.type === 'html') {
225 | opt.attr['frameborder'] = '0'
226 | opt.attr['scrolling'] = 'no'
227 | }
228 |
229 | var d = document.createElement(tag)
230 | for (var k in opt.attr)
231 | d.setAttribute(k, opt.attr[k])
232 |
233 | var p = document.querySelector(opt.append)
234 | if (!p)
235 | return warn('visit_count: append not found: ' + opt.append)
236 | p.appendChild(d)
237 | })
238 | }
239 |
240 | // Make it easy to skip your own views.
241 | if (location.hash === '#toggle-goatcounter') {
242 | if (localStorage.getItem('skipgc') === 't') {
243 | localStorage.removeItem('skipgc', 't')
244 | alert('GoatCounter tracking is now ENABLED in this browser.')
245 | }
246 | else {
247 | localStorage.setItem('skipgc', 't')
248 | alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.')
249 | }
250 | }
251 |
252 | if (!goatcounter.no_onload)
253 | on_load(function() {
254 | // 1. Page is visible, count request.
255 | // 2. Page is not yet visible; wait until it switches to 'visible' and count.
256 | // See #487
257 | if (!('visibilityState' in document) || document.visibilityState === 'visible')
258 | goatcounter.count()
259 | else {
260 | var f = function(e) {
261 | if (document.visibilityState !== 'visible')
262 | return
263 | document.removeEventListener('visibilitychange', f)
264 | goatcounter.count()
265 | }
266 | document.addEventListener('visibilitychange', f)
267 | }
268 |
269 | if (!goatcounter.no_events)
270 | goatcounter.bind_events()
271 | })
272 | })();
273 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/mystnb.css:
--------------------------------------------------------------------------------
1 | /* Whole cell */
2 | div.container.cell {
3 | padding-left: 0;
4 | margin-bottom: 1em;
5 | }
6 |
7 | .cell_output .output pre,
8 | .cell_input pre {
9 | margin: 0px;
10 | }
11 |
12 | /* Input cells */
13 | div.cell div.cell_input {
14 | padding-left: 0em;
15 | padding-right: 0em;
16 | border: 1px #ccc solid;
17 | background-color: #f7f7f7;
18 | border-left-color: green;
19 | border-left-width: medium;
20 | }
21 |
22 | div.cell_input > div,
23 | div.cell_output div.output > div.highlight {
24 | margin: 0em !important;
25 | border: none !important;
26 | }
27 |
28 | /* All cell outputs */
29 | .cell_output {
30 | padding-left: 1em;
31 | padding-right: 0em;
32 | margin-top: 1em;
33 | }
34 |
35 | /* Outputs from jupyter_sphinx overrides to remove extra CSS */
36 | div.section div.jupyter_container {
37 | padding: 0.4em;
38 | margin: 0 0 0.4em 0;
39 | background-color: none;
40 | border: none;
41 | -moz-box-shadow: none;
42 | -webkit-box-shadow: none;
43 | box-shadow: none;
44 | }
45 |
46 | /* Text outputs from cells */
47 | .cell_output .output.text_plain,
48 | .cell_output .output.traceback,
49 | .cell_output .output.stream,
50 | .cell_output .output.stderr {
51 | background: #fcfcfc;
52 | margin-top: 1em;
53 | margin-bottom: 0em;
54 | box-shadow: none;
55 | }
56 |
57 | .cell_output .output.text_plain,
58 | .cell_output .output.stream,
59 | .cell_output .output.stderr {
60 | border: 1px solid #f7f7f7;
61 | }
62 |
63 | .cell_output .output.stderr {
64 | background: #fdd;
65 | }
66 |
67 | .cell_output .output.traceback {
68 | border: 1px solid #ffd6d6;
69 | }
70 |
71 | /* Math align to the left */
72 | .cell_output .MathJax_Display {
73 | text-align: left !important;
74 | }
75 |
76 | /* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */
77 | div.cell_output table {
78 | border: none;
79 | border-collapse: collapse;
80 | border-spacing: 0;
81 | color: black;
82 | font-size: 1em;
83 | table-layout: fixed;
84 | }
85 | div.cell_output thead {
86 | border-bottom: 1px solid black;
87 | vertical-align: bottom;
88 | }
89 | div.cell_output tr,
90 | div.cell_output th,
91 | div.cell_output td {
92 | text-align: right;
93 | vertical-align: middle;
94 | padding: 0.5em 0.5em;
95 | line-height: normal;
96 | white-space: normal;
97 | max-width: none;
98 | border: none;
99 | }
100 | div.cell_output th {
101 | font-weight: bold;
102 | }
103 | div.cell_output tbody tr:nth-child(odd) {
104 | background: #f5f5f5;
105 | }
106 | div.cell_output tbody tr:hover {
107 | background: rgba(66, 165, 245, 0.2);
108 | }
109 |
110 | /* Inline text from `paste` operation */
111 |
112 | span.pasted-text {
113 | font-weight: bold;
114 | }
115 |
116 | span.pasted-inline img {
117 | max-height: 2em;
118 | }
119 |
120 | tbody span.pasted-inline img {
121 | max-height: none;
122 | }
123 |
124 | /* Font colors for translated ANSI escape sequences
125 | Color values are adapted from share/jupyter/nbconvert/templates/classic/static/style.css
126 | */
127 | div.highlight .-Color-Bold {
128 | font-weight: bold;
129 | }
130 | div.highlight .-Color[class*="-Black"] {
131 | color: #3e424d;
132 | }
133 | div.highlight .-Color[class*="-Red"] {
134 | color: #e75c58;
135 | }
136 | div.highlight .-Color[class*="-Green"] {
137 | color: #00a250;
138 | }
139 | div.highlight .-Color[class*="-Yellow"] {
140 | color: yellow;
141 | }
142 | div.highlight .-Color[class*="-Blue"] {
143 | color: #208ffb;
144 | }
145 | div.highlight .-Color[class*="-Magenta"] {
146 | color: #d160c4;
147 | }
148 | div.highlight .-Color[class*="-Cyan"] {
149 | color: #60c6c8;
150 | }
151 | div.highlight .-Color[class*="-White"] {
152 | color: #c5c1b4;
153 | }
154 | div.highlight .-Color[class*="-BGBlack"] {
155 | background-color: #3e424d;
156 | }
157 | div.highlight .-Color[class*="-BGRed"] {
158 | background-color: #e75c58;
159 | }
160 | div.highlight .-Color[class*="-BGGreen"] {
161 | background-color: #00a250;
162 | }
163 | div.highlight .-Color[class*="-BGYellow"] {
164 | background-color: yellow;
165 | }
166 | div.highlight .-Color[class*="-BGBlue"] {
167 | background-color: #208ffb;
168 | }
169 | div.highlight .-Color[class*="-BGMagenta"] {
170 | background-color: #d160c4;
171 | }
172 | div.highlight .-Color[class*="-BGCyan"] {
173 | background-color: #60c6c8;
174 | }
175 | div.highlight .-Color[class*="-BGWhite"] {
176 | background-color: #c5c1b4;
177 | }
178 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/nbsite.css:
--------------------------------------------------------------------------------
1 | /* Align navbar item in center */
2 | .navbar-header-items__center {
3 | margin-left: auto;
4 | margin-right: auto;
5 | }
6 |
7 | /* Ensure content fills full space */
8 | .bd-main .bd-content .bd-article-container {
9 | max-width: unset;
10 | }
11 |
12 | /* Hide primary sidenav bottom section to avoid extra scroll */
13 | .sidebar-primary-items__end.sidebar-primary__section {
14 | display: none;
15 | }
16 |
17 | .nav-link {
18 | white-space: nowrap;
19 | }
20 |
21 | ul.current.nav.bd-sidenav {
22 | padding: 0;
23 | }
24 |
25 | @media (max-width: 960px) {
26 | .homepage-logo {
27 | display: none;
28 | }
29 | }
30 |
31 | @media (min-width: 960px) {
32 | .bd-sidebar {
33 | max-width: 250px;
34 | }
35 |
36 | .bd-page-width {
37 | max-width: min(100%, 1600px);
38 | }
39 | }
40 |
41 | @media (min-width: 1200px) {
42 | .bd-main .bd-content .bd-article-container .bd-article {
43 | padding-left: 0;
44 | }
45 |
46 | .bd-sidebar-secondary {
47 | max-width: 250px;
48 | }
49 | }
50 |
51 | @media (min-width: 1400px) {
52 | .bd-sidebar {
53 | max-width: 300px;
54 | }
55 |
56 | .bd-main .bd-content .bd-article-container .bd-article {
57 | padding-left: 0.5rem;
58 | padding-top: 0.5rem;
59 | }
60 | }
61 |
62 | .theme-switch-button {
63 | font-size: unset;
64 | }
65 |
66 | .related a {
67 | margin-left: 0.5em;
68 | margin-right: 0.5em;
69 | }
70 |
71 | p.caption:not([role="heading"]) {
72 | text-align: center !important;
73 | font-weight: bold;
74 | }
75 |
76 | ul.parents {
77 | display: none;
78 | }
79 |
80 | .related a:hover {
81 | background: #7cb92f;
82 | }
83 | .related a[href="#"]:hover,
84 | .related a[href="index.html"]:hover {
85 | background: #9edb4f;
86 | }
87 | .related a[href="#"],
88 | .related a[href="index.html"] {
89 | background: #8dca3f;
90 | }
91 | .related a {
92 | padding: 0.5em;
93 | margin: 0;
94 | }
95 |
96 | .global-toc li a[href="#"]:hover,
97 | .global-toc li a[href="index.html"]:hover {
98 | background: #fff;
99 | }
100 |
101 | .global-toc li a[href="#"],
102 | .global-toc li a[href="index.html"] {
103 | background: #fafafa;
104 | }
105 |
106 | .global-toc li a {
107 | display: block;
108 | }
109 |
110 | .global-toc li a:hover {
111 | background: #f5f5f5;
112 | }
113 |
114 | div.body h1 {
115 | background: #beebbe;
116 | }
117 |
118 | div.body h2 {
119 | background: #c8e3c8;
120 | }
121 |
122 | div.body h3,
123 | div.body h4,
124 | div.body h5,
125 | div.body h6 {
126 | background: #d9e3d8;
127 | }
128 |
129 | div.inheritance_box {
130 | overflow: auto;
131 | }
132 |
133 | tt.docutils.literal {
134 | background: transparent;
135 | font-size: 0.9em;
136 | }
137 |
138 | dl.class {
139 | border-top: 1px solid #5a970d;
140 | padding-top: 15px;
141 | }
142 |
143 | dl.class.rm_expanded,
144 | dl.class.rm_collapsed {
145 | border-top: none;
146 | padding: 0;
147 | margin-top: 0;
148 | margin-bottom: 0;
149 | /*padding-left: 1.5em;*/
150 | }
151 |
152 | dl.class.rm_expanded > dt:before {
153 | content: "[-]";
154 | cursor: pointer;
155 | color: #005b81;
156 | }
157 |
158 | dl.class.rm_collapsed dt:before {
159 | content: "[+]";
160 | cursor: pointer;
161 | color: #005b81;
162 | }
163 |
164 | dl.class.rm_expanded dd {
165 | display: block;
166 | }
167 |
168 | dl.class.rm_collapsed dd {
169 | display: none;
170 | }
171 |
172 | a.anchor-link {
173 | visibility: hidden;
174 | }
175 |
176 | /* Avoid wrapping in HoloViews extension logo block */
177 | div.logo-block {
178 | display: inline-block;
179 | }
180 |
181 | div.bk-root {
182 | min-height: 50px;
183 | }
184 |
185 | .output {
186 | overflow-x: auto;
187 | }
188 |
189 | /* Adapted from the sphinx book theme */
190 | main.bd-content a.headerlink {
191 | opacity: 0;
192 | margin-left: 0.2em;
193 | }
194 |
195 | main.bd-content a.headerlink:hover {
196 | background-color: transparent;
197 | color: rgba(var(--pst-color-headerlink-hover), 1);
198 | opacity: 1 !important;
199 | }
200 |
201 | main.bd-content h1:hover a.headerlink,
202 | main.bd-content h2:hover a.headerlink,
203 | main.bd-content h3:hover a.headerlink,
204 | main.bd-content h4:hover a.headerlink,
205 | main.bd-content h5:hover a.headerlink {
206 | opacity: 0.5;
207 | }
208 | /* End of copy */
209 |
210 | /* Color of the paragraph symbol of the header link */
211 | :root {
212 | --pst-color-headerlink: 170, 170, 170;
213 | --pst-color-headerlink-hover: 170, 170, 170;
214 | }
215 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/notebook.css:
--------------------------------------------------------------------------------
1 | .cell_output {
2 | padding-left: 0;
3 | }
4 |
5 | html[data-theme="dark"] .bd-content div.cell_output .text_html,
6 | html[data-theme="light"] .bd-content div.cell_output .text_html {
7 | padding: 0;
8 | }
9 |
10 | html[data-theme="dark"] .bd-content div.cell_output .text_html {
11 | background-color: var(--pst-color-background);
12 | color: var(--pst-color-text-base);
13 | }
14 |
15 | div.input_prompt {
16 | visibility: hidden;
17 | height: 0;
18 | }
19 |
20 | div.output_prompt {
21 | visibility: hidden;
22 | height: 0;
23 | }
24 |
25 | .output_subarea pre {
26 | background-color: transparent;
27 | border-style: none;
28 | margin-left: 1em;
29 | }
30 |
31 | .rendered_html code {
32 | font-size: 0.9em !important;
33 | }
34 |
35 | /* Hide cell toggle */
36 |
37 | details.toggle-details {
38 | display: none;
39 | }
40 |
41 | button.toggle-button {
42 | display: none;
43 | }
44 |
45 | .toggle-hidden:not(.admonition) {
46 | height: 0;
47 | }
48 |
49 | .tag_hide-input {
50 | margin-bottom: 0 !important;
51 | }
52 |
53 | details.hide.above-input {
54 | display: none;
55 | }
56 |
57 | .toggle-hidden + .cell_output {
58 | margin-top: 0 !important;
59 | }
60 |
--------------------------------------------------------------------------------
/nbsite/_shared_static/scroller.css:
--------------------------------------------------------------------------------
1 | /* CSS for side-scrolling admonition */
2 | #scroller-right {
3 | position: fixed;
4 | top: 80%;
5 | right: 1%;
6 | max-width: 10%;
7 | padding: 0.8%;
8 | transform: translate(0%, -50%);
9 | border: 1px solid gray;
10 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
11 | font-size: smaller;
12 | z-index: 1;
13 | }
14 |
15 | #scroller-right {
16 | max-width: 14%;
17 | }
18 |
19 | @media (max-width: 1200px) {
20 | #scroller-right {
21 | position: relative !important;
22 | right: unset !important;
23 | top: unset !important;
24 | max-width: 100% !important;
25 | transform: unset !important;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/nbsite/_shared_templates/copyright-last-updated.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {%- if hasdoc('copyright') %}
4 | {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
5 | {%- else %}
6 | {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
7 | {%- endif %}
8 |
9 | {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
10 |
${msg.content}`; 94 | } else if (msg.mime === 'text/html') { 95 | output.innerHTML = `
Short
39 | */ 40 | .o-tooltip--left { 41 | position: relative; 42 | } 43 | 44 | .o-tooltip--left:after { 45 | opacity: 0; 46 | visibility: hidden; 47 | position: absolute; 48 | content: attr(data-tooltip); 49 | padding: 0.2em; 50 | font-size: 0.8em; 51 | left: -0.2em; 52 | background: grey; 53 | color: white; 54 | white-space: nowrap; 55 | z-index: 2; 56 | border-radius: 2px; 57 | transform: translateX(-102%) translateY(0); 58 | transition: 59 | opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), 60 | transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 61 | } 62 | 63 | .o-tooltip--left:has(.pyodide-alert-icon):after { 64 | opacity: 1; 65 | visibility: visible; 66 | } 67 | 68 | .o-tooltip--left:hover:after { 69 | display: block; 70 | opacity: 1; 71 | visibility: visible; 72 | transform: translateX(-100%) translateY(0); 73 | transition: 74 | opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), 75 | transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 76 | transition-delay: 0.5s; 77 | } 78 | 79 | /* By default the copy button shouldn't show up when printing a page */ 80 | @media print { 81 | button.runbtn { 82 | display: none; 83 | } 84 | } 85 | 86 | button.runbtn svg.pyodide-loading-icon { 87 | transform-box: fill-box; 88 | transform-origin: 50% 50%; 89 | animation-duration: 3s; 90 | animation-name: rotate; 91 | animation-iteration-count: infinite; 92 | } 93 | 94 | @keyframes rotate { 95 | from { 96 | transform: rotate(0deg); 97 | } 98 | to { 99 | transform: rotate(360deg); 100 | } 101 | } 102 | 103 | .pyodide-output { 104 | margin: 1em 0; 105 | overflow-x: auto; 106 | } 107 | 108 | .pyodide-output-wrapper { 109 | margin-left: 0.5em; 110 | } 111 | 112 | .pyodide-stderr { 113 | background-color: rgba(194, 103, 103, 0.35); 114 | border: indianred solid 1px; 115 | display: none; 116 | } 117 | 118 | .pyodide-stdout { 119 | display: none; 120 | } 121 | 122 | .pyodide-output.embedded { 123 | border-left: 0.5em solid darkgoldenrod; 124 | border-radius: 0.4em; 125 | } 126 | 127 | .pyodide-output.live { 128 | border-left: 0.5em solid green; 129 | border-radius: 0.5em; 130 | } 131 | -------------------------------------------------------------------------------- /nbsite/pyodide/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ name }}", 3 | "short_name": "{{ name }}", 4 | "start_url": "index.html", 5 | "icons": [ 6 | { 7 | "src": "_static/icons/icon-32x32.png", 8 | "type": "image/png", 9 | "sizes": "32x32" 10 | }, 11 | { 12 | "src": "_static/icons/icon-192x192.png", 13 | "type": "image/png", 14 | "sizes": "192x192" 15 | }, 16 | { 17 | "src": "_static/icons/icon-512x512.png", 18 | "type": "image/png", 19 | "sizes": "512x512" 20 | } 21 | ], 22 | "display": "{{ display|default('standalone', true) }}", 23 | "scope": "/", 24 | "background_color": "{{ background_color|default('#fdfdfd', true) }}", 25 | "theme_color": "{{ background_color|default('#0072B5', true) }}", 26 | "orientation": "{{ orientation|default('any', true) }}" 27 | } 28 | -------------------------------------------------------------------------------- /nbsite/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | from ._clean_dist_html import clean_dist_html 2 | from ._fix_links import fix_links 3 | 4 | __all__ = ("clean_dist_html", "fix_links") 5 | -------------------------------------------------------------------------------- /nbsite/scripts/_clean_dist_html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """_Title.ipynb -> The Title 4 | 5 | 6 | 7 | """ 8 | 9 | import os 10 | import shutil 11 | 12 | 13 | def IGetFiles(d): 14 | for thing in os.scandir(d): 15 | if thing.is_dir(): 16 | yield from IGetFiles(thing.path) 17 | else: 18 | yield thing.path 19 | 20 | # I think it's ok to assume these exist for a sphinx site... 21 | 22 | def clean_dist_html(output, dry_run): 23 | htmldir = os.path.abspath(output) 24 | 25 | if dry_run: 26 | print("This is just a dry-run of removing files from:", htmldir) 27 | else: 28 | print("Removing files from:", htmldir) 29 | 30 | # (.doctrees in build folder by default only for sphinx<1.8) 31 | for folder in (".doctrees", "_sources"): 32 | d = os.path.join(htmldir,folder) 33 | try: 34 | if dry_run: 35 | print("would remove", folder) 36 | else: 37 | print("removing", folder) 38 | shutil.rmtree(d) 39 | except Exception: 40 | pass 41 | 42 | for path in IGetFiles(htmldir): 43 | if os.path.splitext(path)[1].lower() == '.ipynb': 44 | name = path.split(output)[-1] 45 | if dry_run: 46 | print("would remove", name) 47 | else: 48 | print("removing", name) 49 | os.remove(path) 50 | -------------------------------------------------------------------------------- /nbsite/scripts/_fix_links.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Cleans up relative cross-notebook links by replacing them with .html 4 | extension. 5 | """ 6 | import os 7 | import re 8 | import warnings 9 | 10 | from concurrent.futures import ThreadPoolExecutor 11 | from functools import partial 12 | from pathlib import Path 13 | 14 | from bs4 import BeautifulSoup 15 | 16 | # TODO: holoviews specific links e.g. to reference manual...doc & generalize 17 | 18 | #BOKEH_REPLACEMENTS = {'cell.output_area.append_execute_result': '//cell.output_area.append_execute_result', 19 | # '}(window));\n': '}(window));\n', 20 | # '\n(function(root) {': '