pax_global_header00006660000000000000000000000064151262733000014511gustar00rootroot0000000000000052 comment=ce5c07b1f747948e5cb138a03834872a6074b57b sphinxcontrib-mermaid-2.0.0/000077500000000000000000000000001512627330000160165ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/.github/000077500000000000000000000000001512627330000173565ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/.github/dependabot.yml000066400000000000000000000011031512627330000222010ustar00rootroot00000000000000# Keep GitHub Actions up to date with GitHub's Dependabot... # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: - package-ecosystem: github-actions directory: / groups: github-actions: patterns: - "*" # Group all Actions updates into a single larger pull request schedule: interval: weekly sphinxcontrib-mermaid-2.0.0/.github/workflows/000077500000000000000000000000001512627330000214135ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/.github/workflows/release.yml000066400000000000000000000011141512627330000235530ustar00rootroot00000000000000name: Publish Python 🐍 distributions 📦 to PyPI on: push: tags: - "*" jobs: pypi-publish: name: upload release to PyPI runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') permissions: id-token: write steps: - name: Checkout uses: actions/checkout@v6 - name: install build run: python -m pip install --upgrade build - name: build run: python -m build - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1sphinxcontrib-mermaid-2.0.0/.github/workflows/test.yml000066400000000000000000000015501512627330000231160ustar00rootroot00000000000000name: Test on: [push, pull_request, workflow_dispatch] env: FORCE_COLOR: 1 jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] os: [windows-latest, macos-latest, ubuntu-latest] steps: - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true cache: pip cache-dependency-path: pyproject.toml - name: Install dependencies run: | python -m pip install -e .[test] - name: Lint run: | ruff format --check sphinxcontrib ruff check sphinxcontrib - name: Test run: | pytest sphinxcontrib-mermaid-2.0.0/.gitignore000066400000000000000000000022701512627330000200070ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ _build/ _static/ _templates/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/index.md docs/_build/ # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # IDEA project settings .idea # Test directories (for manual theme testing) test-fullscreen/ theme-tests/ sphinxcontrib-mermaid-2.0.0/.readthedocs.yaml000066400000000000000000000012561512627330000212510ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-24.04 apt_packages: - libasound2t64 tools: python: "3" nodejs: "20" jobs: post_install: - npm install -g @mermaid-js/mermaid-cli # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF formats: - epub - pdf python: install: - requirements: docs/requirements.txt - method: pip path: .sphinxcontrib-mermaid-2.0.0/CHANGELOG.md000066400000000000000000000101301512627330000176220ustar00rootroot00000000000000# Changelog ## 2.0.0 (TBD) - Drop support for Python 3.8 and 3.9 - Add support for dynamic theme changes ## 1.2.3 (November 25, 2025) - Fix issue with ELK charts by upgrading to ELK plugin [0.2.0]{.title-ref} ## 1.2.2 (November 23, 2025) - Hotfix for CSS/JS assets included via old MANIFEST mechanism ## 1.2.1 (November 23, 2025) - Adjust chart size defaults to closer resember previous - Fix issue with fullscreen classes after rebase ## 1.2.0 (November 23, 2025) - Normalize javascript across various configuration options - Make graph full width and avoid unused margins in graph image - Rename fullscreen container class from [mermaid-fullscreen-content]{.title-ref} to [mermaid-container-fullscreen]{.title-ref} ## 1.1.0 (November 19, 2025) - Upgrade Mermaid to 11.12.1 - Add fullscreen graph view capabilities ## 1.0.0 (October 12, 2024) - Upgrade Mermaid to 11.2.0 - Add support for ELK diagrams - Add support for name parameter - Add passthrough of mermaid frontmatter - Convert to native namespace package - Drop support for Python 3.7 - Convert default placeholder from div to pre - Fix for tempfile encoding when containing non ascii characters - Fix for mermaid sequence config arguments - Default to jsdelivr (previously unpkg) for JS asset CDN See full [set of changes](https://github.com/mgaitan/sphinxcontrib-mermaid/compare/1.0.0...0.9.2). ## 0.9.2 (May 28, 2023) - Implemented zoom on diagrams functionality. Contributed by [Daniel Althviz Moré](https://github.com/dalthviz) - Fix a bug on empty diagram generations. Contributed by [Kevin Deldycke](https://github.com/kdeldycke). - Upgrade default to Mermaid 10.2.0. - Implement automatic releases from Github Actions when a tag is pushed See full [set of changes](https://github.com/mgaitan/sphinxcontrib-mermaid/compare/0.9.2...0.8.1). ## 0.8.1 (Feb 25, 2023) - Default to Mermaid 9.4.0 as 10.0 introduced incompatible changes. See [the discussion](https://github.com/mermaid-js/mermaid/discussions/4148). ## 0.8 (Feb 9, 2023) - Moved CI to Github Actions - Make the class diagram reproducible - Allow the user to change the JS priority - Drop support for Python 3.6 - Black formatting See [full set of changes](https://github.com/mgaitan/sphinxcontrib-mermaid/compare/0.7.1...0.8). ## 0.7.1 (July 17, 2021) - Update docs and tests for markdown support ## 0.7 (May 31, 2021) - Add compatibility with Sphinx 4.0 - [mermaid_init_js]{.title-ref} is now included in an standard way. - Documented how to use in Markdown documents ## 0.6.3 (February 21, 2021) - Make it compatible with recent Sphinx versions - Add basic (real) tests (So I stop breaking it!) ## 0.6.2 (February 18, 2021) - fix regression - setup travis ## 0.6.1 (February 8, 2021) - Fix a problem when called mermaid-cli - Fix typos on documentation - Improve internal code formatting (via black) ## 0.6.0 (January 31, 2021) - Drop support for Python version older than 3.6. - Allow to include javascript lib locally - Initialization code is now customizable - The default version included from the CDN is always the latest available. ## 0.5.0 (September 24, 2020) - Added mermaid_cmd_shell. Useful for Windows user. - Reimplement inheritance diagrams. - Fix UnicodeEncodeError on Python 2 ## 0.4.0 (April 9, 2020) - Added [mermaid_params]{.title-ref} - Added config file option - Improved latex integration - Added the [pdfcrop]{.title-ref} functionality - Mermaid version is configurable - Several cleanups in the code ## 0.3.1 (Nov 22, 2017) - Support the new Mermaid CLI by [Bastian Luettig](https://github.com/bastiedotorg) ## 0.3 (Oct 4, 2017) - several improves and bugfixes contributed by [Alberto Berti](https://github.com/azazel75) ## 0.2.1 (Jun 4, 2017) - Workaround for opacity issue with rtd's theme (thanks to [Anton Koldaev](http://github.com/iroller)) ## 0.2 (Jun 4, 2017) - Python 3 support fix (thanks to [Shakeeb Alireza](http://github.com/shakfu)) - In-browser diagram generation - Autoclasstree directive. (Thanks to [Zulko](http://github.com/zulko)) ## 0.1.1 (Jun 4, 2017) - Better usage instructions - Bugfix ## 0.1 (Jul 18, 2016) - first public version sphinxcontrib-mermaid-2.0.0/LICENSE000066400000000000000000000025261512627330000170300ustar00rootroot00000000000000sphinxcontrib-mermaid is a Sphinx extension for Mermaid Diagrams Copyright (c) 2016 by Martín Gaitán All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sphinxcontrib-mermaid-2.0.0/MANIFEST.in000066400000000000000000000003311512627330000175510ustar00rootroot00000000000000include README.rst include CHANGELOG.rst include LICENSE.rst # Include static assets (CSS and JavaScript) recursive-include sphinxcontrib/mermaid *.css.j2 *.js.j2 prune tests exclude .gitignore global-exclude *.pyc sphinxcontrib-mermaid-2.0.0/README.md000066400000000000000000000275021512627330000173030ustar00rootroot00000000000000> [!NOTE] > `sphinxcontrib-mermaid` is actively seeking new maintainers. > As the original creator, I'm no longer able to dedicate the time and > attention needed. If you're interested in contributing and helping to > drive this project forward, please see [this > issue](https://github.com/mgaitan/sphinxcontrib-mermaid/issues/148). # sphinxcontrib-mermaid [![test status](https://github.com/mgaitan/sphinxcontrib-mermaid/actions/workflows/test.yml/badge.svg)](https://github.com/mgaitan/sphinxcontrib-mermaid/actions/workflows/test.yml) [![version](https://img.shields.io/pypi/v/sphinxcontrib-mermaid)](https://pypi.org/project/sphinxcontrib-mermaid/) [![downloads](https://img.shields.io/pypi/dm/shbin)](https://libraries.io/pypi/sphinxcontrib-mermaid/) This extension allows you to embed [Mermaid](https://mermaid.js.org/) graphs in your documents, including general flowcharts, sequence diagrams, gantt diagrams and more. It adds a directive to embed mermaid markup. For example: ```rst .. mermaid:: sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? loop Healthcheck John->John: Fight against hypochondria end Note right of John: Rational thoughts
prevail... John-->Alice: Great! John->Bob: How about you? Bob-->John: Jolly good! ``` By default, the HTML builder will simply render this as a `div` tag with `class="mermaid"`, injecting the external javascript, css and initialization code to make mermaid works. For other builders (or if `mermaid_output_format` config variable is set differently), the extension will use [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) to render as to a PNG or SVG image, and then used in the proper code. ```mermaid sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? loop Healthcheck John->John: Fight against hypochondria end Note right of John: Rational thoughts
prevail... John-->Alice: Great! John->Bob: How about you? Bob-->John: Jolly good! ``` You can also embed external mermaid files, by giving the file name as an argument to the directive and no additional content: ```rst .. mermaid:: path/to/mermaid-gantt-code.mmd ``` As for all file references in Sphinx, if the filename is not absolute, it is taken as relative to the source directory. In addition, you can use mermaid to automatically generate a diagram to show the class inheritance using the directive `autoclasstree`. It accepts one or more fully qualified names to a class or a module. In the case of a module, all the class found will be included. Of course, these objects need to be importable to make its diagram. If an optional attribute `:full:` is given, it will show the complete hierarchy of each class. The option `:namespace: ` limits to the base classes that belongs to this namespace. Meanwhile, the flag `:strict:` only process the classes that are strictly defined in the given module (ignoring classes imported from other modules). For example: ```rst .. autoclasstree:: sphinx.util.DownloadFiles sphinx.util.ExtensionError :full: ``` ```{eval-rst} .. autoclasstree:: sphinx.util.DownloadFiles sphinx.util.ExtensionError :full: ``` Or directly the module: ```rst .. autoclasstree:: sphinx.util ``` ```{eval-rst} .. autoclasstree:: sphinx.util ``` ## Installation You can install it using pip ```bash pip install sphinxcontrib-mermaid ``` Then add `sphinxcontrib.mermaid` in `extensions` list of your project's `conf.py`: ```python extensions = [ ..., 'sphinxcontrib.mermaid' ] ``` ## Directive options `sphinxcontrib-mermaid` mermaid diagrams can be configured by `rst`/`md` frontmatter: ```rst .. mermaid:: :name: test ``` ````markdown ```mermaid --- name: test --- ```` - `name`: determines the image's name for HTML output. **NOTE**: mermaid will use this as the `id` of the generated `svg` element, which can be useful for styling with custom css: `#mydiagram > svg { height: 1000px }` - `alt`: determines the image's alternate text for HTML output. If not given, the alternate text defaults to the mermaid code. - `align`: determines the image's position. Valid options are `'left'`, `'center'`, `'right'` - `caption`: can be used to give a caption to the diagram. - `zoom`: can be used to enable zooming the diagram. For a global config see `mermaid_d3_zoom` below.
A preview after adding :zoom: option only to the first diagram example above:
- `fullscreen`: can be used to enable fullscreen modal viewing of the diagram. For a global config see `mermaid_fullscreen` below. - `config`: JSON to pass through to the [mermaid configuration](https://mermaid.js.org/config/configuration.html). **NOTE**: The mermaid documentation uses YAML, but we must use JSON because Markdown processing of frontmatter will interfere. - `title`: Title to pass through to the [mermaid configuration](https://mermaid.js.org/config/configuration.html) ## Config values ### `mermaid_output_format` The output format for Mermaid when building HTML files. This must be either `'raw'` `'png'` or `'svg'`; the default is `'raw'`. `mermaid-cli` is required if it's not `raw` ### `mermaid_cmd` The command name with which to invoke `mermaid-cli` program. The default is `'mmdc'`; you may need to set this to a full path if it's not in the executable search path. If a string is specified, it is split using [shlex.split]{.title-ref} to support multi-word commands. To avoid splitting, a list of strings can be specified. Examples: ```python mermaid_cmd = 'npx mmdc' mermeid_cmd = ['npx', '--no-install', 'mmdc'] ``` ### `mermaid_cmd_shell` When set to true, the `shell=True` argument will be passed the process execution command. This allows commands other than binary executables to be executed on Windows. The default is false. ### `mermaid_params` For individual parameters, a list of parameters can be added. Refer to [Examples](https://github.com/mermaid-js/mermaid-cli#usage): ```python mermaid_params = ['--theme', 'forest', '--width', '600', '--backgroundColor', 'transparent'] ``` This will render the mermaid diagram with theme forest, 600px width and transparent background. ### `mermaid_sequence_config` Allows overriding the sequence diagram configuration. It could be useful to increase the width between actors. It **needs to be a json file** Check options in the [documentation](https://mermaid-js.github.io/mermaid/#/mermaidAPI?id=configuration) ### `mermaid_verbose` Use the verbose mode when call mermaid-cli, and show its output in the building process. ### `mermaid_pdfcrop` If using latex output, it might be useful to crop the pdf just to the needed space. For this, `pdfcrop` can be used. State binary name to use this extra function. ### `mermaid_init_config` Optional override of arguments to `mermaid.initialize()`, passed in as a JSON. Defaults to `{ "startOnLoad": True}`. ### `mermaid_version` The version of mermaid that will be used to parse `raw` output in HTML files. This should match a version available on [https://www.jsdelivr.com/package/npm/mermaid](https://www.jsdelivr.com/package/npm/mermaid). The default is `"11.12.1"`. ### `mermaid_use_local` Optional path to a local installation of `mermaid.esm.min.mjs`. By default, we will pull from jsdelivr. ### `mermaid_include_elk` Whether to download and load the ELK JavaScript extensions. Defaults to False. ### `mermaid_include_zenuml` Whether to download and load the ZenuML JavaScript extensions. Defaults to False. ### `mermaid_elk_version` The version of mermaid ELK renderer that will be used. The default is `"0.2.0"`. ### `mermaid_zenuml_version` The version of mermaid ZenuML renderer that will be used. The default is `"0.2.2"`. ### `mermaid_elk_use_local` Optional path to a local installation of `mermaid-layout-elk.esm.min.mjs`. By default, we will pull from jsdelivr. ### `mermaid_zenuml_use_local` Optional path to a local installation of `mermaid-zenuml.esm.min.mjs`. By default, we will pull from jsdelivr. ### `d3_use_local` Optional path to a local installation of `d3.min.js`. By default, we will pull from jsdelivr. ### `d3_version` The version of d3 that will be used to provide zoom functionality on mermaid graphs. The default is `"7.9.0"`. ### `mermaid_d3_zoom` Enables zooming in all the generated Mermaid diagrams. ### `mermaid_width` Sets the default diagram width within its container. Default to 100%. ### `mermaid_height` Sets the default diagram height within its container. Default to 500px. ### `mermaid_fullscreen` Enables fullscreen modal viewing for all Mermaid diagrams. When enabled, a fullscreen button appears in the top-right corner of each diagram. Clicking it opens the diagram in a fullscreen modal overlay. The modal can be closed by pressing ESC, clicking outside the diagram, or clicking the close button. This feature is theme-agnostic and works with any Sphinx theme. ### `mermaid_fullscreen_button` Customizes the fullscreen button icon/text. Default is `"⛶"`. You can use any Unicode character or emoji, for example `"🔍"` or `"⛶"`. ### `mermaid_fullscreen_button_opacity` Customizes the fullscreen button opacity, to avoid fully obscuring important chart content. Default is `50` (percent). You can use any value from 0 to 100. Button becomes fully opaque on hover. ## Markdown support You can include Mermaid diagrams in your Markdown documents in Sphinx. You just need to setup the [markdown support in Sphinx](https://www.sphinx-doc.org/en/master/usage/markdown.html) via [myst-parser](https://myst-parser.readthedocs.io/) . See a [minimal configuration from the tests](https://github.com/mgaitan/sphinxcontrib-mermaid/blob/master/tests/roots/test-markdown/conf.py). Then in your `.md` documents include a code block as in reStructuredTexts: ````markdown ```{mermaid} sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? ``` ```` For GitHub cross-support, you can omit the curly braces and configure myst to use the [mermaid]{.title-ref} code block as a myst directive. For example, in \`conf.py\`: ```python myst_fence_as_directive = ["mermaid"] ``` ## Building PDFs on readthedocs.io In order to have Mermaid diagrams build properly in PDFs generated on readthedocs.io, you will need a few extra configurations. 1. In your `.readthedocs.yaml` file (which should be in the root of your repository) include a `post-install` command to the Mermaid CLI: ```yaml build: os: ubuntu-20.04 tools: python: "3.8" nodejs: "16" jobs: post_install: - npm install -g @mermaid-js/mermaid-cli ``` Note that if you previously did not have a `.readthedocs.yaml` file, you will also need to specify all targets you wish to build and other basic configuration options. A minimal example of a complete file is: ```yaml # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-24.04 apt_packages: - libasound2t64 tools: python: "3.11" nodejs: "20" jobs: post_install: - npm install -g @mermaid-js/mermaid-cli # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF formats: - epub - pdf python: install: - requirements: docs/requirements.txt ``` 2. In your documentation directory add file `puppeteer-config.json` with contents: : ```json { "args": ["--no-sandbox"] } ``` 3. In your documentation `conf.py` file, add: : ```python mermaid_params = ['-p', 'puppeteer-config.json'] ``` sphinxcontrib-mermaid-2.0.0/docs/000077500000000000000000000000001512627330000167465ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/docs/Makefile000066400000000000000000000012661512627330000204130ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = Sphinxcontrib-mermaiddemo SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile open: python -m webbrowser -t "file://$(PWD)/_build/html/index.html" # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) sphinxcontrib-mermaid-2.0.0/docs/conf.py000066400000000000000000000060551512627330000202530ustar00rootroot00000000000000#!/usr/bin/env python3 from pathlib import Path extensions = [ "myst_parser", "sphinxcontrib.mermaid", "sphinx.ext.imgconverter", ] templates_path = ["_templates"] source_suffix = [".md"] master_doc = "index" project = "sphinxcontrib-mermaid" copyright = "2017-2025, Martín Gaitán" author = "Martín Gaitán" version = "2.0" release = "2.0.0rc1" exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] pygments_style = "sphinx" todo_include_todos = False html_theme = "furo" html_static_path = ["_static"] htmlhelp_basename = "sphinxcontrib-mermaiddoc" latex_documents = [ (master_doc, "sphinxcontrib-mermaid.tex", "sphinxcontrib-mermaid documentation", "Martín Gaitán", "manual"), ] man_pages = [(master_doc, "sphinxcontrib-mermaid", "sphinxcontrib-mermaid documentation", [author], 1)] texinfo_documents = [ ( master_doc, "sphinxcontrib-mermaid", "sphinxcontrib-mermaid documentation", author, "sphinxcontrib-mermaid", "One line description of project.", "Miscellaneous", ), ] # Myst myst_enable_extensions = ["amsmath", "colon_fence", "dollarmath", "html_image"] myst_fence_as_directive = ["mermaid"] mermaid_params = ["-ppuppeteer-config.json"] mermaid_d3_zoom = True mermaid_fullscreen = True mermaid_include_elk = True mermaid_include_mindmap = True toctree_base = """{toctree} --- caption: "" maxdepth: 2 hidden: true ---""" toctree_root = f"""```{toctree_base} ``` """ def run_copyreadme(_): out = Path("index.md") readme = Path("../README.md") out.write_text(toctree_root + "\n" + readme.read_text()) _GITHUB_ADMONITIONS = { "> [!NOTE]": "note", "> [!TIP]": "tip", "> [!IMPORTANT]": "important", "> [!WARNING]": "warning", "> [!CAUTION]": "caution", } def run_convert_github_admonitions_to_rst(app, filename, lines): # loop through lines, replace github admonitions for i, orig_line in enumerate(lines): orig_line_splits = orig_line.split("\n") replacing = False for j, line in enumerate(orig_line_splits): # look for admonition key for admonition_key in _GITHUB_ADMONITIONS: if admonition_key in line: line = line.replace(admonition_key, ":::{" + _GITHUB_ADMONITIONS[admonition_key] + "}\n") # start replacing quotes in subsequent lines replacing = True break else: # replace indent to match directive if replacing and "> " in line: line = line.replace("> ", " ") elif replacing: # missing "> ", so stop replacing and terminate directive line = f"\n:::\n{line}" replacing = False # swap line back in splits orig_line_splits[j] = line # swap line back in original lines[i] = "\n".join(orig_line_splits) def setup(app): app.connect("builder-inited", run_copyreadme) app.connect("source-read", run_convert_github_admonitions_to_rst) sphinxcontrib-mermaid-2.0.0/docs/puppeteer-config.json000066400000000000000000000000371512627330000231150ustar00rootroot00000000000000{ "args": ["--no-sandbox"] } sphinxcontrib-mermaid-2.0.0/docs/readme_pypa.md000066400000000000000000000013631512627330000215610ustar00rootroot00000000000000# sphinxcontrib-mermaid [![](https://github.com/mgaitan/sphinxcontrib-mermaid/actions/workflows/test.yml/badge.svg)](https://github.com/mgaitan/sphinxcontrib-mermaid/actions/workflows/test.yml) [![](https://img.shields.io/pypi/v/sphinxcontrib-mermaid)](https://pypi.org/project/sphinxcontrib-mermaid/) [![](https://img.shields.io/pypi/dm/shbin)](https://libraries.io/pypi/sphinxcontrib-mermaid/) This extension allows you to embed [Mermaid](https://mermaid.js.org/) graphs in your documents, including general flowcharts, sequence diagrams, gantt diagrams and more. See the [official Documentation](http://sphinxcontrib-mermaid-demo.readthedocs.io/en/latest/) or [project repository](https://github.com/mgaitan/sphinxcontrib-mermaid) for more details! sphinxcontrib-mermaid-2.0.0/docs/requirements.txt000066400000000000000000000000371512627330000222320ustar00rootroot00000000000000furo myst-parser>=1.0.0 Sphinx sphinxcontrib-mermaid-2.0.0/pyproject.toml000066400000000000000000000042421512627330000207340ustar00rootroot00000000000000[build-system] requires = ["setuptools"] build-backend="setuptools.build_meta" [project] name = "sphinxcontrib-mermaid" authors = [{name = "Martín Gaitán", email = "gaitan@gmail.com"}] description="Mermaid diagrams in your Sphinx-powered docs" readme = "docs/readme_pypa.md" license = { text = "BSD" } version = "2.0.0" requires-python = ">=3.10" keywords = ["sphinx", "mermaid", "diagrams", "documentation"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Web Environment", "Framework :: Sphinx :: Extension", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Documentation", "Topic :: Utilities", ] dependencies = [ "jinja2", "sphinx", "pyyaml", ] [project.urls] Repository = "https://github.com/mgaitan/sphinxcontrib-mermaid" Homepage = "https://github.com/mgaitan/sphinxcontrib-mermaid" Changelog = "https://github.com/mgaitan/sphinxcontrib-mermaid/blob/master/CHANGELOG.rst" [project.optional-dependencies] test = [ "defusedxml", "myst-parser", "pytest", "ruff", "sphinx" ] [project.scripts] [tool.pytest.ini_options] asyncio_mode = "strict" testpaths = "tests" [tool.ruff] line-length = 150 [tool.ruff.lint] extend-select = ["I"] [tool.ruff.lint.isort] combine-as-imports = true default-section = "third-party" known-first-party = ["sphinxcontrib.mermaid"] section-order = [ "future", "standard-library", "third-party", "first-party", "local-folder", ] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401", "F403"] [tool.setuptools.packages.find] where = ["./"] include = ["sphinxcontrib.mermaid"] [tool.setuptools.package-data] "sphinxcontrib.mermaid" = ["*.css", "*.js"] sphinxcontrib-mermaid-2.0.0/sphinxcontrib/000077500000000000000000000000001512627330000207105ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/000077500000000000000000000000001512627330000223265ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/__init__.py000066400000000000000000000550431512627330000244460ustar00rootroot00000000000000""" sphinx-mermaid ~~~~~~~~~~~~~~~ Allow mermaid diagrams to be included in Sphinx-generated documents inline. :copyright: Copyright 2016-2025 by Martín Gaitán and others :license: BSD, see LICENSE for details. """ from __future__ import annotations import codecs import errno import os import posixpath import re import shlex import uuid from hashlib import sha1 from json import dumps, loads from pathlib import Path from subprocess import PIPE, Popen from tempfile import TemporaryDirectory import sphinx from docutils import nodes from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList from jinja2 import Template from packaging.version import Version from sphinx.application import Sphinx from sphinx.locale import _ from sphinx.util import logging from sphinx.util.i18n import search_image_for_language from sphinx.util.osutil import ensuredir from yaml import dump from .autoclassdiag import class_diagram from .exceptions import MermaidError logger = logging.getLogger(__name__) # Load fullscreen CSS and JavaScript from external files _MODULE_DIR = Path(__file__).parent _FULLSCREEN_CSS = (_MODULE_DIR / "fullscreen.css.j2").read_text(encoding="utf-8") _MERMAID_CSS = (_MODULE_DIR / "default.css.j2").read_text(encoding="utf-8") _MERMAID_JS = (_MODULE_DIR / "default.js.j2").read_text(encoding="utf-8") mapname_re = re.compile(r' {code} """ attr_defs = ['{}="{}"'.format(k, v) for k, v in attrs.items()] self.body.append(tag_template.format(attr_defs=" ".join(attr_defs), classes=" ".join(classes), code=self.encode(code))) raise nodes.SkipNode def render_mm_html(self, node, code, options, prefix="mermaid", imgcls=None, alt=None): _fmt = self.builder.config.mermaid_output_format if _fmt == "raw": return _render_mm_html_raw(self, node, code, options, prefix="mermaid", imgcls=None, alt=None) try: if _fmt not in ("png", "svg"): raise MermaidError("mermaid_output_format must be one of 'raw', 'png', 'svg', but is %r" % _fmt) fname, outfn = render_mm(self, code, options, _fmt, prefix) except MermaidError as exc: logger.warning(f"mermaid code {code!r}: " + str(exc)) raise nodes.SkipNode if fname is None: self.body.append(self.encode(code)) else: if alt is None: alt = node.get("alt", self.encode(code).strip()) imgcss = imgcls and f'class="{imgcls}"' or "" if _fmt == "svg": svgtag = f"""

{alt}

""" self.body.append(svgtag) else: if "align" in node: self.body.append('
' % (node["align"], node["align"]))

            self.body.append(f'{alt}\n')
            if "align" in node:
                self.body.append("
\n") raise nodes.SkipNode def html_visit_mermaid(self, node): render_mm_html(self, node, node["code"], node["options"]) def render_mm_latex(self, node, code, options, prefix="mermaid"): try: fname, outfn = render_mm(self, code, options, "pdf", prefix) except MermaidError as exc: logger.warning(f"mm code {code!r}: " + str(exc)) raise nodes.SkipNode if self.builder.config.mermaid_pdfcrop != "": mm_args = [self.builder.config.mermaid_pdfcrop, outfn] try: p = Popen(mm_args, stdout=PIPE, stdin=PIPE, stderr=PIPE) except OSError as err: if err.errno != errno.ENOENT: # No such file or directory raise logger.warning(f"command {self.builder.config.mermaid_pdfcrop!r} cannot be run (needed to crop pdf), check the mermaid_cmd setting") return None, None stdout, stderr = p.communicate() if self.builder.config.mermaid_verbose: logger.info(stdout) if p.returncode != 0: raise MermaidError("PdfCrop exited with error:\n[stderr]\n%s\n[stdout]\n%s" % (stderr, stdout)) if not os.path.isfile(outfn): raise MermaidError("PdfCrop did not produce an output file:\n[stderr]\n%s\n[stdout]\n%s" % (stderr, stdout)) fname = "{filename[0]}-crop{filename[1]}".format(filename=os.path.splitext(fname)) is_inline = self.is_inline(node) if is_inline: para_separator = "" else: para_separator = "\n" if fname is not None: post = None if not is_inline and "align" in node: if node["align"] == "left": self.body.append("{") post = "\\hspace*{\\fill}}" elif node["align"] == "right": self.body.append("{\\hspace*{\\fill}") post = "}" self.body.append("%s\\sphinxincludegraphics{%s}%s" % (para_separator, fname, para_separator)) if post: self.body.append(post) raise nodes.SkipNode def latex_visit_mermaid(self, node): render_mm_latex(self, node, node["code"], node["options"]) def render_mm_texinfo(self, node, code, options, prefix="mermaid"): try: fname, outfn = render_mm(self, code, options, "png", prefix) except MermaidError as exc: logger.warning(f"mm code {code!r}: " + str(exc)) raise nodes.SkipNode if fname is not None: self.body.append("@image{%s,,,[mermaid],png}\n" % fname[:-4]) raise nodes.SkipNode def texinfo_visit_mermaid(self, node): render_mm_texinfo(self, node, node["code"], node["options"]) def text_visit_mermaid(self, node): if "alt" in node.attributes: self.add_text(_("[graph: %s]") % node["alt"]) else: self.add_text(_("[graph]")) raise nodes.SkipNode def man_visit_mermaid(self, node): if "alt" in node.attributes: self.body.append(_("[graph: %s]") % node["alt"]) else: self.body.append(_("[graph]")) raise nodes.SkipNode def install_js( app: Sphinx, pagename, templatename: str, context: dict, doctree: nodes.document | None, ) -> None: # Skip for pages without Mermaid diagrams if doctree and not doctree.next_node(mermaid): return # Add required JavaScript if app.config.mermaid_use_local: _mermaid_js_url = app.config.mermaid_use_local elif app.config.mermaid_version == "latest": _mermaid_js_url = "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs" elif Version(app.config.mermaid_version) > Version("10.2.0"): _mermaid_js_url = f"https://cdn.jsdelivr.net/npm/mermaid@{app.config.mermaid_version}/dist/mermaid.esm.min.mjs" elif app.config.mermaid_version: raise MermaidError("Requires mermaid js version 10.3.0 or later") _mermaid_elk_js_url = None if app.config.mermaid_include_elk: if app.config.mermaid_elk_use_local: _mermaid_elk_js_url = app.config.mermaid_elk_use_local elif app.config.mermaid_elk_version == "latest": _mermaid_elk_js_url = "https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs" elif app.config.mermaid_elk_version: _mermaid_elk_js_url = ( f"https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@{app.config.mermaid_elk_version}/dist/mermaid-layout-elk.esm.min.mjs" ) _mermaid_zenuml_js_url = None if app.config.mermaid_include_zenuml: if app.config.mermaid_zenuml_use_local: _mermaid_zenuml_js_url = app.config.mermaid_zenuml_use_local elif app.config.mermaid_zenuml_version == "latest": _mermaid_zenuml_js_url = "https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml/dist/mermaid-zenuml.esm.min.mjs" elif app.config.mermaid_zenuml_version: _mermaid_zenuml_js_url = ( f"https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@{app.config.mermaid_zenuml_version}/dist/mermaid-zenuml.esm.min.mjs" ) _wrote_mermaid_run = False _has_zoom = app.config.mermaid_d3_zoom _has_fullscreen = app.config.mermaid_fullscreen _button_text = app.config.mermaid_fullscreen_button _button_opacity = app.config.mermaid_fullscreen_button_opacity _mermaid_width = app.config.mermaid_width _mermaid_height = app.config.mermaid_height template_js = Template(_MERMAID_JS) template_css = Template(_MERMAID_CSS) template_fullscreen_css = Template(_FULLSCREEN_CSS) common_render_args = dict( mermaid_js_url=_mermaid_js_url, mermaid_init_config=dumps(app.config.mermaid_init_config), mermaid_include_elk=_mermaid_elk_js_url is not None, mermaid_include_zenuml=_mermaid_zenuml_js_url is not None, mermaid_elk_js_url=_mermaid_elk_js_url, mermaid_zenuml_js_url=_mermaid_zenuml_js_url, common_css=template_css.render( mermaid_width=_mermaid_width, mermaid_height=_mermaid_height, ), button_text=_button_text, # ignored button_opacity=_button_opacity, # ignored add_fullscreen=_has_fullscreen, add_zoom=_has_zoom, ) if app.config.mermaid_output_format == "raw": if app.config.d3_use_local: _d3_js_url = app.config.d3_use_local elif app.config.d3_version == "latest": _d3_js_url = "https://cdn.jsdelivr.net/npm/d3/dist/d3.min.js" elif app.config.d3_version: _d3_js_url = f"https://cdn.jsdelivr.net/npm/d3@{app.config.d3_version}/dist/d3.min.js" app.add_js_file(_d3_js_url, priority=app.config.mermaid_js_priority) if app.config.mermaid_d3_zoom: if not _has_fullscreen: _d3_js_script = template_js.render( fullscreen_css="", # ignored d3_selector=".mermaid svg", d3_node_count=-1, **common_render_args, ) app.add_js_file(None, body=_d3_js_script, priority=app.config.mermaid_js_priority, type="module") _wrote_mermaid_run = True elif doctree: mermaid_nodes = doctree.findall(mermaid) _d3_selector = "" count = 0 for mermaid_node in mermaid_nodes: if "zoom_id" in mermaid_node: _zoom_id = mermaid_node["zoom_id"] if _d3_selector == "": _d3_selector += f".mermaid[data-zoom-id={_zoom_id}] svg" else: _d3_selector += f", .mermaid[data-zoom-id={_zoom_id}] svg" count += 1 if _d3_selector != "": if not _has_fullscreen: _d3_js_script = template_js.render( fullscreen_css="", # ignored d3_selector=_d3_selector, d3_node_count=count, **common_render_args, ) app.add_js_file(None, body=_d3_js_script, priority=app.config.mermaid_js_priority, type="module") _wrote_mermaid_run = True # Handle fullscreen feature if _has_fullscreen and not _wrote_mermaid_run: if _has_zoom: # Fullscreen with zoom _d3_selector = ".mermaid svg" if not _d3_selector and doctree: # Build selector for per-diagram zoom mermaid_nodes = doctree.findall(mermaid) count = 0 for mermaid_node in mermaid_nodes: if "zoom_id" in mermaid_node: _zoom_id = mermaid_node["zoom_id"] if _d3_selector == "": _d3_selector += f".mermaid[data-zoom-id={_zoom_id}] svg" else: _d3_selector += f", .mermaid[data-zoom-id={_zoom_id}] svg" count += 1 if _d3_selector == "": _d3_selector = ".mermaid svg" count = -1 else: count = -1 _d3_js_script = template_js.render( fullscreen_css=template_fullscreen_css.render( mermaid_width=_mermaid_width, mermaid_height=_mermaid_height, ), d3_selector=_d3_selector if _d3_selector else ".mermaid svg", d3_node_count=count if _d3_selector else -1, **common_render_args, ) app.add_js_file(None, body=_d3_js_script, priority=app.config.mermaid_js_priority, type="module") _wrote_mermaid_run = True else: # Fullscreen without zoom _fullscreen_js_script = template_js.render( fullscreen_css=template_fullscreen_css.render( mermaid_width=_mermaid_width, mermaid_height=_mermaid_height, ), d3_selector="", # ignored d3_node_count=-1, # ignored **common_render_args, ) app.add_js_file(None, body=_fullscreen_js_script, priority=app.config.mermaid_js_priority, type="module") _wrote_mermaid_run = True if not _wrote_mermaid_run and _mermaid_js_url: app.add_js_file( None, body=template_js.render( fullscreen_css="", d3_selector="", # ignored d3_node_count=-1, # ignored **common_render_args, ), priority=app.config.mermaid_js_priority, type="module", ) def setup(app): app.add_node( mermaid, html=(html_visit_mermaid, None), latex=(latex_visit_mermaid, None), texinfo=(texinfo_visit_mermaid, None), text=(text_visit_mermaid, None), man=(man_visit_mermaid, None), ) app.add_directive("mermaid", Mermaid) app.add_directive("autoclasstree", MermaidClassDiagram) app.add_config_value("mermaid_cmd", "mmdc", "html") app.add_config_value("mermaid_cmd_shell", "False", "html") app.add_config_value("mermaid_pdfcrop", "", "html") app.add_config_value("mermaid_output_format", "raw", "html") app.add_config_value("mermaid_params", list(), "html") app.add_config_value("mermaid_verbose", False, "html") app.add_config_value("mermaid_sequence_config", False, "html") app.add_config_value("mermaid_init_config", {"startOnLoad": False}, "html") app.add_config_value("mermaid_version", "11.12.1", "html") app.add_config_value("mermaid_use_local", "", "html") # Plugins app.add_config_value("mermaid_include_elk", False, "html") app.add_config_value("mermaid_include_zenuml", False, "html") app.add_config_value("mermaid_elk_version", "0.2.0", "html") app.add_config_value("mermaid_zenuml_version", "0.2.2", "html") app.add_config_value("mermaid_elk_use_local", "", "html") app.add_config_value("mermaid_zenuml_use_local", "", "html") app.add_config_value("d3_use_local", "", "html") app.add_config_value("d3_version", "7.9.0", "html") app.add_config_value("mermaid_d3_zoom", False, "html") app.add_config_value("mermaid_js_priority", 500, "html") app.add_config_value("mermaid_width", "100%", "html") app.add_config_value("mermaid_height", "500px", "html") app.add_config_value("mermaid_fullscreen", True, "html") app.add_config_value("mermaid_fullscreen_button", "⛶", "html") app.add_config_value("mermaid_fullscreen_button_opacity", "50", "html") app.connect("html-page-context", install_js) return {"version": sphinx.__display_version__, "parallel_read_safe": True} sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/autoclassdiag.py000066400000000000000000000035351512627330000255310ustar00rootroot00000000000000import inspect from sphinx.errors import ExtensionError from sphinx.util import import_object from .exceptions import MermaidError def get_classes(*cls_or_modules, strict=False): """ given one or several fully qualified names, yield class instances found. If ``strict`` is only consider classes that are strictly defined in that module and not imported from somewhere else. """ for cls_or_module in cls_or_modules: try: obj = import_object(cls_or_module) except ExtensionError as e: raise MermaidError(str(e)) if inspect.isclass(obj): yield obj elif inspect.ismodule(obj): for obj_ in obj.__dict__.values(): if inspect.isclass(obj_) and (not strict or obj_.__module__.startswith(obj.__name__)): yield obj_ else: raise MermaidError(f"{cls_or_module} is not a class nor a module") def class_diagram(*cls_or_modules, full=False, strict=False, namespace=None): inheritances = set() def get_tree(cls): for base in cls.__bases__: if base.__name__ == "object": continue if namespace and not base.__module__.startswith(namespace): continue inheritances.add((base.__name__, cls.__name__)) if full: get_tree(base) for cls in get_classes(*cls_or_modules, strict=strict): get_tree(cls) if not inheritances: return "" return "classDiagram\n" + "\n".join(f" {a} <|-- {b}" for a, b in sorted(inheritances)) if __name__ == "__main__": class A: pass class B(A): pass class C1(B): pass class C2(B): pass class D(C1, C2): pass class E(C1): pass print(class_diagram("__main__.D", "__main__.E", full=True)) sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/default.css.j2000066400000000000000000000004121512627330000247730ustar00rootroot00000000000000pre.mermaid { /* Same as .mermaid-container > pre */ display: block; width: {{ mermaid_width }}; } pre.mermaid > svg { /* Same as .mermaid-container > pre > svg */ height: {{ mermaid_height }}; width: 100%; max-width: 100% !important; } sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/default.js.j2000066400000000000000000000255651512627330000246370ustar00rootroot00000000000000import mermaid from "{{ mermaid_js_url }}"; {% if mermaid_include_elk %} import elkLayouts from "{{ mermaid_elk_js_url }}" {% endif %} {% if mermaid_include_zenuml %} import zenumlLayouts from "{{ mermaid_zenuml_js_url }}" {% endif %} const initStyles = () => { const defaultStyle = document.createElement('style'); defaultStyle.textContent = `{{ common_css }}`; document.head.appendChild(defaultStyle); const fullscreenStyle = document.createElement('style'); fullscreenStyle.textContent = `{{ fullscreen_css }}`; document.head.appendChild(fullscreenStyle); } // Detect if page has dark background const isDarkTheme = () => { // We use a set of heuristics: // 1. Check for common dark mode classes or attributes // 2. Check computed background color brightness if (document.documentElement.classList.contains('dark') || document.documentElement.getAttribute('data-theme') === 'dark' || document.body.classList.contains('dark') || document.body.getAttribute('data-theme') === 'dark') { // console.log("Dark theme detected via class/attribute"); return true; } if (document.documentElement.classList.contains('light') || document.documentElement.getAttribute('data-theme') === 'light' || document.body.classList.contains('light') || document.body.getAttribute('data-theme') === 'light') { // console.log("Light theme detected via class/attribute"); return false; } if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { // console.log("Dark theme detected via prefers-color-scheme"); return true; } const bgColor = window.getComputedStyle(document.body).backgroundColor; const match = bgColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)/); if (match) { const r = parseInt(match[1]); const g = parseInt(match[2]); const b = parseInt(match[3]); const brightness = (r * 299 + g * 587 + b * 114) / 1000; // console.log("Background color brightness:", brightness); return brightness < 128; } // console.log("No dark or light theme detected, defaulting to light theme"); return false; }; let darkTheme = isDarkTheme(); let modal = null; let modalContent = null; let previousScrollOffset = [window.scrollX, window.scrollY]; const runMermaid = async (rerun) => { console.log("Running mermaid diagrams, rerun =", rerun); // clear all existing mermaid charts let all_mermaids = document.querySelectorAll(".mermaid"); if (rerun) { all_mermaids.forEach((el) => { if(!el.hasAttribute("data-original-code")) { // store original code // console.log(`Storing original code for first run: `, el.innerHTML); el.setAttribute('data-original-code', el.innerHTML); } if(el.getAttribute("data-processed") === "true") { // remove and restore original el.removeAttribute("data-processed"); // console.log(`Restoring original code for re-run: `, el.getAttribute('data-original-code')); el.innerHTML = el.getAttribute('data-original-code'); } else { // store original code // console.log(`Storing original code for re-run: `, el.innerHTML); el.setAttribute('data-original-code', el.innerHTML); } }); await mermaid.run(); } all_mermaids = document.querySelectorAll(".mermaid"); const mermaids_processed = document.querySelectorAll(".mermaid[data-processed='true']"); if ("{{ add_zoom }}" === "True") { const mermaids_to_add_zoom = {{ d3_node_count }} === -1 ? all_mermaids.length : {{ d3_node_count }}; if(mermaids_to_add_zoom > 0) { var svgs = d3.selectAll("{{ d3_selector }}"); if(all_mermaids.length !== mermaids_processed.length) { setTimeout(() => runMermaid(false), 200); return; } else if(svgs.size() !== mermaids_to_add_zoom) { setTimeout(() => runMermaid(false), 200); return; } else { svgs.each(function() { var svg = d3.select(this); svg.html("" + svg.html() + ""); var inner = svg.select("g"); var zoom = d3.zoom().on("zoom", function(event) { inner.attr("transform", event.transform); }); svg.call(zoom); }); } } } else if(all_mermaids.length !== mermaids_processed.length) { // Wait for mermaid to process all diagrams setTimeout(() => runMermaid(false), 200); return; } // Stop here if not adding fullscreen capability if ("{{ add_fullscreen }}" !== "True") return; if (modal !== null ) { // Destroy existing modal modal.remove(); modal = null; modalContent = null; } modal = document.createElement('div'); modal.className = 'mermaid-fullscreen-modal' + (darkTheme ? ' dark-theme' : ''); modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); modal.setAttribute('aria-label', 'Fullscreen diagram viewer'); modal.innerHTML = `
`; document.body.appendChild(modal); modalContent = modal.querySelector('.mermaid-container-fullscreen'); const closeBtn = modal.querySelector('.mermaid-fullscreen-close'); const closeModal = () => { modal.classList.remove('active'); modalContent.innerHTML = ''; document.body.style.overflow = '' window.scrollTo({left: previousScrollOffset[0], top: previousScrollOffset[1], behavior: 'instant'}); }; closeBtn.addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.classList.contains('active')) { closeModal(); } }); document.querySelectorAll('.mermaid').forEach((mermaidDiv) => { if (mermaidDiv.parentNode.classList.contains('mermaid-container') || mermaidDiv.closest('.mermaid-fullscreen-modal')) { // Already processed, adjust button class if needed const existingBtn = mermaidDiv.parentNode.querySelector('.mermaid-fullscreen-btn'); if (existingBtn) { existingBtn.className = 'mermaid-fullscreen-btn' + (darkTheme ? ' dark-theme' : ''); } return; } const container = document.createElement('div'); container.className = 'mermaid-container'; mermaidDiv.parentNode.insertBefore(container, mermaidDiv); container.appendChild(mermaidDiv); const fullscreenBtn = document.createElement('button'); fullscreenBtn.className = 'mermaid-fullscreen-btn' + (darkTheme ? ' dark-theme' : ''); fullscreenBtn.setAttribute('aria-label', 'View diagram in fullscreen'); fullscreenBtn.textContent = '{{ button_text }}'; fullscreenBtn.style.opacity = '{{ button_opacity }}%'; // Calculate dynamic position based on diagram's margin and padding const diagramStyle = window.getComputedStyle(mermaidDiv); const marginTop = parseFloat(diagramStyle.marginTop) || 0; const marginRight = parseFloat(diagramStyle.marginRight) || 0; const paddingTop = parseFloat(diagramStyle.paddingTop) || 0; const paddingRight = parseFloat(diagramStyle.paddingRight) || 0; fullscreenBtn.style.top = `${marginTop + paddingTop + 4}px`; fullscreenBtn.style.right = `${marginRight + paddingRight + 4}px`; fullscreenBtn.addEventListener('click', () => { previousScrollOffset = [window.scroll, window.scrollY]; const clone = mermaidDiv.cloneNode(true); modalContent.innerHTML = ''; modalContent.appendChild(clone); const svg = clone.querySelector('svg'); if (svg) { svg.removeAttribute('width'); svg.removeAttribute('height'); svg.style.width = '100%'; svg.style.height = 'auto'; svg.style.maxWidth = '100%'; svg.style.sdisplay = 'block'; if ("{{ add_zoom }}" === "True") { setTimeout(() => { const g = svg.querySelector('g'); if (g) { var svgD3 = d3.select(svg); svgD3.html("" + svgD3.html() + ""); var inner = svgD3.select("g"); var zoom = d3.zoom().on("zoom", function(event) { inner.attr("transform", event.transform); }); svgD3.call(zoom); } }, 100); } } modal.classList.add('active'); document.body.style.overflow = 'hidden'; }); container.appendChild(fullscreenBtn); }); }; const load = async () => { initStyles(); await runMermaid(true); const reRunIfThemeChanges = async () => { const newDarkTheme = isDarkTheme(); if (newDarkTheme !== darkTheme) { darkTheme = newDarkTheme; console.log("Theme change detected, re-running mermaid with", darkTheme ? "dark" : "default", "theme"); await mermaid.initialize( {...JSON.parse( `{{ mermaid_init_config }}` ), ...{ darkMode: darkTheme, theme: darkTheme ? 'dark' : 'default' }, } ); await runMermaid(true); } }; // Update theme classes when theme changes const themeObserver = new MutationObserver(reRunIfThemeChanges); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style', 'data-theme'] }); themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'style', 'data-theme'] }); }; {% if mermaid_include_elk %} mermaid.registerLayoutLoaders(elkLayouts); {% endif %} {% if mermaid_include_zenuml %} mermaid.registerExternalDiagrams([zenumlLayouts]); {% endif %} console.log("Initializing mermaid with", darkTheme ? "dark" : "default", "theme"); mermaid.initialize( {...JSON.parse( `{{ mermaid_init_config }}` ), ...{ darkMode: darkTheme, theme: darkTheme ? 'dark' : 'default' }, } ); window.addEventListener("load", load); sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/exceptions.py000066400000000000000000000001501512627330000250550ustar00rootroot00000000000000from sphinx.errors import SphinxError class MermaidError(SphinxError): category = "Mermaid error" sphinxcontrib-mermaid-2.0.0/sphinxcontrib/mermaid/fullscreen.css.j2000066400000000000000000000064521512627330000255230ustar00rootroot00000000000000.mermaid-container { display: flex; flex-direction: row; width: 100%; } .mermaid-container > pre { display: block; width: {{ mermaid_width }}; } .mermaid-container > pre > svg { height: {{ mermaid_height }}; width: 100%; max-width: 100% !important; } .mermaid-fullscreen-btn { width: 28px; height: 28px; background: rgba(255, 255, 255, 0.95); border: 1px solid rgba(0, 0, 0, 0.3); border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); font-size: 14px; line-height: 1; padding: 0; color: #333; } .mermaid-fullscreen-btn:hover { opacity: 100% !important; background: rgba(255, 255, 255, 1); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3); transform: scale(1.1); } .mermaid-fullscreen-btn.dark-theme { background: rgba(50, 50, 50, 0.95); border: 1px solid rgba(255, 255, 255, 0.3); color: #e0e0e0; } .mermaid-fullscreen-btn.dark-theme:hover { background: rgba(60, 60, 60, 1); box-shadow: 0 3px 10px rgba(255, 255, 255, 0.2); } .mermaid-fullscreen-modal { display: none; position: fixed !important; top: 0 !important; left: 0 !important; width: 95vw; height: 100vh; background: rgba(255, 255, 255, 0.98); z-index: 9999; padding: 20px; overflow: auto; } .mermaid-fullscreen-modal.dark-theme { background: rgba(0, 0, 0, 0.98); } .mermaid-fullscreen-modal.active { display: flex; align-items: center; justify-content: center; } .mermaid-container-fullscreen { position: relative; width: 95vw; height: 90vh; max-width: 95vw; max-height: 90vh; background: white; border-radius: 8px; padding: 20px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); overflow: auto; display: flex; align-items: center; justify-content: center; } .mermaid-container-fullscreen.dark-theme { background: #1a1a1a; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8); } .mermaid-container-fullscreen pre.mermaid { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .mermaid-container-fullscreen .mermaid svg { height: 100% !important; width: 100% !important; cursor: grab; } .mermaid-fullscreen-close { position: fixed !important; top: 20px !important; right: 20px !important; width: 40px; height: 40px; background: rgba(255, 255, 255, 0.95); border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 50%; cursor: pointer; z-index: 10000; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: all 0.2s; font-size: 24px; line-height: 1; color: #333; } .mermaid-fullscreen-close:hover { background: white; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); transform: scale(1.1); } .mermaid-fullscreen-close.dark-theme { background: rgba(50, 50, 50, 0.95); border: 1px solid rgba(255, 255, 255, 0.2); color: #e0e0e0; } .mermaid-fullscreen-close.dark-theme:hover { background: rgba(60, 60, 60, 1); box-shadow: 0 6px 16px rgba(255, 255, 255, 0.2); } .mermaid-fullscreen-modal .mermaid-fullscreen-btn { display: none !important; }sphinxcontrib-mermaid-2.0.0/tests/000077500000000000000000000000001512627330000171605ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/tests/conftest.py000066400000000000000000000006161512627330000213620ustar00rootroot00000000000000from pathlib import Path import pytest import sphinx from packaging.version import Version pytest_plugins = "sphinx.testing.fixtures" @pytest.fixture(scope="session") def rootdir(): if Version(sphinx.__version__) < Version("7.0.0"): from sphinx.testing.path import path return path(__file__).parent.abspath() / "roots" return Path(__file__).parent.absolute() / "roots" sphinxcontrib-mermaid-2.0.0/tests/roots/000077500000000000000000000000001512627330000203265ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/tests/roots/test-basic/000077500000000000000000000000001512627330000223645ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/tests/roots/test-basic/conf.py000066400000000000000000000001051512627330000236570ustar00rootroot00000000000000extensions = ["sphinxcontrib.mermaid"] exclude_patterns = ["_build"] sphinxcontrib-mermaid-2.0.0/tests/roots/test-basic/index.rst000066400000000000000000000004641512627330000242310ustar00rootroot00000000000000Hi, basic test -------------- .. mermaid:: :name: participants sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? Empty class diagram should not fail ----------------------------------- .. mermaid:: classDiagram .. toctree:: zoom.rstsphinxcontrib-mermaid-2.0.0/tests/roots/test-basic/zoom.rst000066400000000000000000000004301512627330000240770ustar00rootroot00000000000000 Zooming ------- .. mermaid:: :name: participants sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? .. mermaid:: :zoom: flowchart TD A[Christmas] -->|Get money| B(Go shopping) B --> A{Let me think}sphinxcontrib-mermaid-2.0.0/tests/roots/test-fullscreen/000077500000000000000000000000001512627330000234455ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/tests/roots/test-fullscreen/conf.py000066400000000000000000000001371512627330000247450ustar00rootroot00000000000000extensions = ["sphinxcontrib.mermaid"] mermaid_fullscreen = True mermaid_output_format = "raw" sphinxcontrib-mermaid-2.0.0/tests/roots/test-fullscreen/index.rst000066400000000000000000000001061512627330000253030ustar00rootroot00000000000000test-fullscreen =============== .. mermaid:: graph LR A --> B sphinxcontrib-mermaid-2.0.0/tests/roots/test-markdown/000077500000000000000000000000001512627330000231255ustar00rootroot00000000000000sphinxcontrib-mermaid-2.0.0/tests/roots/test-markdown/conf.py000066400000000000000000000001711512627330000244230ustar00rootroot00000000000000extensions = ["sphinxcontrib.mermaid", "myst_parser"] exclude_patterns = ["_build"] source_suffix = {".md": "markdown"} sphinxcontrib-mermaid-2.0.0/tests/roots/test-markdown/index.md000066400000000000000000000004111512627330000245520ustar00rootroot00000000000000# Hi from Markdown! ```{mermaid} --- align: center name: participants --- sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? ``` # Empty class diagram should not fail ```{mermaid} classDiagram ``` sphinxcontrib-mermaid-2.0.0/tests/test_html.py000066400000000000000000000141611512627330000215400ustar00rootroot00000000000000import re import pytest @pytest.fixture def build_all(app): app.builder.build_all() @pytest.fixture def index(app, build_all): # normalize script tag for compat to Sphinx<4 return (app.outdir / "index.html").read_text().replace("' in index @pytest.mark.sphinx("html", testroot="basic", confoverrides={"d3_use_local": "test"}) def test_conf_d3_local(app, index): assert "cdn.jsdelivr.net/npm/d3" not in index @pytest.mark.sphinx("html", testroot="basic", confoverrides={"mermaid_init_config": {"startOnLoad": True}}) def test_mermaid_init_js(index): assert "mermaid.run()" in index assert ( '{"startOnLoad": false}' not in index ) assert ( '{"startOnLoad": true}' in index ) @pytest.mark.sphinx("html", testroot="basic", confoverrides={"mermaid_include_elk": True, "mermaid_elk_version": "latest"}) def test_mermaid_with_elk(app, index): assert "mermaid.run()" in index assert ( 'import elkLayouts from "https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs"' in index ) @pytest.mark.sphinx("html", testroot="markdown", confoverrides={"mermaid_include_elk": True}) def test_html_raw_from_markdown(index): assert "mermaid.run()" in index assert "mermaid.run()" in index assert ( 'import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/mermaid.esm.min.mjs"' in index ) assert ( 'import elkLayouts from "https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@0.2.0/dist/mermaid-layout-elk.esm.min.mjs"' in index ) assert ( 'mermaid.registerLayoutLoaders(elkLayouts);' in index ) assert ( '{"startOnLoad": false}' in index ) assert ( '
\n            sequenceDiagram\n      participant Alice\n      participant Bob\n      Alice->John: Hello John, how are you?\n    
' in index ) @pytest.mark.sphinx("html", testroot="fullscreen") def test_fullscreen_enabled(index): """Test that fullscreen JavaScript is added when enabled.""" assert "mermaid.run()" in index assert ".mermaid-fullscreen-btn:hover" in index assert ".mermaid-fullscreen-modal" in index assert "mermaid-fullscreen-close" in index @pytest.mark.sphinx("html", testroot="basic", confoverrides={"mermaid_fullscreen": False}) def test_fullscreen_disabled(index): """Test that fullscreen is not added when disabled.""" assert "mermaid.run()" in index assert ".mermaid-fullscreen-btn:hover" not in index @pytest.mark.sphinx("html", testroot="fullscreen", confoverrides={"mermaid_d3_zoom": True}) def test_fullscreen_with_zoom(index): """Test that fullscreen works with D3 zoom.""" assert "mermaid.run()" in index assert ".mermaid-fullscreen-btn" in index assert "d3.zoom" in index @pytest.mark.sphinx("html", testroot="fullscreen", confoverrides={"mermaid_fullscreen_button": "[+]"}) def test_custom_fullscreen_button(index): """Test custom fullscreen button icon.""" assert "mermaid.run()" in index assert "[+]" in index