pax_global_header00006660000000000000000000000064147663641730014533gustar00rootroot0000000000000052 comment=a125657b1a2f7be73ce67e5555c56707fa61e1e3 markdown-exec-1.10.2/000077500000000000000000000000001476636417300143605ustar00rootroot00000000000000markdown-exec-1.10.2/.copier-answers.yml000066400000000000000000000013531476636417300201240ustar00rootroot00000000000000# Changes here will be overwritten by Copier. _commit: 1.8.1 _src_path: gh:pawamoy/copier-uv author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli author_username: pawamoy copyright_date: '2022' copyright_holder: Timothée Mazzucotelli copyright_holder_email: dev@pawamoy.fr copyright_license: ISC insiders: true insiders_email: insiders@pawamoy.fr insiders_repository_name: markdown-exec project_description: Utilities to execute code blocks in Markdown files. project_name: Markdown Exec public_release: true python_package_command_line_name: '' python_package_distribution_name: markdown-exec python_package_import_name: markdown_exec repository_name: markdown-exec repository_namespace: pawamoy repository_provider: github.com markdown-exec-1.10.2/.envrc000066400000000000000000000000211476636417300154670ustar00rootroot00000000000000PATH_add scripts markdown-exec-1.10.2/.github/000077500000000000000000000000001476636417300157205ustar00rootroot00000000000000markdown-exec-1.10.2/.github/FUNDING.yml000066400000000000000000000000371476636417300175350ustar00rootroot00000000000000github: pawamoy polar: pawamoy markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476636417300201035ustar00rootroot00000000000000markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/1-bug.md000066400000000000000000000027231476636417300213440ustar00rootroot00000000000000--- name: Bug report about: Create a bug report to help us improve. title: "bug: " labels: unconfirmed assignees: [pawamoy] --- ### Description of the bug ### To Reproduce ``` WRITE MRE / INSTRUCTIONS HERE ``` ### Full traceback
Full traceback ```python PASTE TRACEBACK HERE ```
### Expected behavior ### Environment information ```bash python -m markdown_exec._internal.debug # | xclip -selection clipboard ``` PASTE MARKDOWN OUTPUT HERE ### Additional context markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/2-feature.md000066400000000000000000000012131476636417300222140ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project. title: "feature: " labels: feature assignees: pawamoy --- ### Is your feature request related to a problem? Please describe. ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/3-docs.md000066400000000000000000000011311476636417300215110ustar00rootroot00000000000000--- name: Documentation update about: Point at unclear, missing or outdated documentation. title: "docs: " labels: docs assignees: pawamoy --- ### Is something unclear, missing or outdated in our documentation? ### Relevant code snippets ### Link to the relevant documentation section markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/4-change.md000066400000000000000000000011261476636417300220130ustar00rootroot00000000000000--- name: Change request about: Suggest any other kind of change for this project. title: "change: " assignees: pawamoy --- ### Is your change request related to a problem? Please describe. ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context markdown-exec-1.10.2/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003321476636417300220710ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: I have a question / I need help url: https://github.com/pawamoy/markdown-exec/discussions/new?category=q-a about: Ask and answer questions in the Discussions tab. markdown-exec-1.10.2/.github/workflows/000077500000000000000000000000001476636417300177555ustar00rootroot00000000000000markdown-exec-1.10.2/.github/workflows/ci.yml000066400000000000000000000063551476636417300211040ustar00rootroot00000000000000name: ci on: push: pull_request: branches: - main defaults: run: shell: bash env: LANG: en_US.utf-8 LC_ALL: en_US.utf-8 PYTHONIOENCODING: UTF-8 PYTHON_VERSIONS: "" jobs: quality: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Set up Graphviz uses: ts-graphviz/setup-graphviz@v1 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Setup uv uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: pyproject.toml - name: Install dependencies run: make setup - name: Check if the documentation builds correctly run: make check-docs - name: Check the code quality run: make check-quality - name: Check if the code is correctly typed run: make check-types - name: Check for breaking changes in the API run: make check-api - name: Store objects inventory for tests uses: actions/upload-artifact@v4 with: name: objects.inv path: site/objects.inv exclude-test-jobs: runs-on: ubuntu-latest outputs: jobs: ${{ steps.exclude-jobs.outputs.jobs }} steps: - id: exclude-jobs run: | if ${{ github.repository_owner == 'pawamoy-insiders' }}; then echo 'jobs=[ {"os": "macos-latest"}, {"os": "windows-latest"}, {"python-version": "3.10"}, {"python-version": "3.11"}, {"python-version": "3.12"}, {"python-version": "3.13"}, {"python-version": "3.14"} ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT else echo 'jobs=[ {"os": "macos-latest", "resolution": "lowest-direct"}, {"os": "windows-latest", "resolution": "lowest-direct"} ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT fi tests: needs: - quality - exclude-test-jobs strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest python-version: - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" resolution: - highest - lowest-direct exclude: ${{ fromJSON(needs.exclude-test-jobs.outputs.jobs) }} runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.python-version == '3.14' }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - name: Setup uv uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: pyproject.toml cache-suffix: ${{ matrix.resolution }} - name: Install dependencies env: UV_RESOLUTION: ${{ matrix.resolution }} run: make setup - name: Download objects inventory uses: actions/download-artifact@v4 with: name: objects.inv path: site/ - name: Run the test suite run: make test markdown-exec-1.10.2/.github/workflows/release.yml000066400000000000000000000023671476636417300221300ustar00rootroot00000000000000name: release on: push permissions: contents: write jobs: release: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Setup uv uses: astral-sh/setup-uv@v5 - name: Build dists if: github.repository_owner == 'pawamoy-insiders' run: uv tool run --from build pyproject-build - name: Upload dists artifact uses: actions/upload-artifact@v4 if: github.repository_owner == 'pawamoy-insiders' with: name: markdown-exec-insiders path: ./dist/* - name: Prepare release notes if: github.repository_owner != 'pawamoy-insiders' run: uv tool run git-changelog --release-notes > release-notes.md - name: Create release with assets uses: softprops/action-gh-release@v2 if: github.repository_owner == 'pawamoy-insiders' with: files: ./dist/* - name: Create release uses: softprops/action-gh-release@v2 if: github.repository_owner != 'pawamoy-insiders' with: body_path: release-notes.md markdown-exec-1.10.2/.gitignore000066400000000000000000000003311476636417300163450ustar00rootroot00000000000000# editors .idea/ .vscode/ # python *.egg-info/ *.py[cod] .venv/ .venvs/ /build/ /dist/ # tools .coverage* /.pdm-build/ /htmlcov/ /site/ uv.lock # cache .cache/ .pytest_cache/ .mypy_cache/ .ruff_cache/ __pycache__/ markdown-exec-1.10.2/CHANGELOG.md000066400000000000000000000534451476636417300162040ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [1.10.2](https://github.com/pawamoy/markdown-exec/releases/tag/1.10.2) - 2025-03-18 [Compare with 1.10.1](https://github.com/pawamoy/markdown-exec/compare/1.10.1...1.10.2) ### Bug Fixes - Escape Pyodide output, improve error handling ([ec83f48](https://github.com/pawamoy/markdown-exec/commit/ec83f48db29708b03e8d2487ccb0d5ee0889a464) by Timothée Mazzucotelli). [Issue-87](https://github.com/pawamoy/markdown-exec/issues/87) ### Code Refactoring - Sync API and docs ([1245ed9](https://github.com/pawamoy/markdown-exec/commit/1245ed96b8c5d430cc949fcfa2b9f2ef5be78f65) by Timothée Mazzucotelli). - Move code under internal folder ([b132e78](https://github.com/pawamoy/markdown-exec/commit/b132e78d2a9ebc48edd7b388e00b161eb0a048b6) by Timothée Mazzucotelli). ## [1.10.1](https://github.com/pawamoy/markdown-exec/releases/tag/1.10.1) - 2025-03-11 [Compare with 1.10.0](https://github.com/pawamoy/markdown-exec/compare/1.10.0...1.10.1) ### Build - Add lower bound to pygments-ansi-color extra ([a8e17c9](https://github.com/pawamoy/markdown-exec/commit/a8e17c9cd1bf9ba8ab5008a83c77a0b1208a1b25) by Timothée Mazzucotelli). ### Bug Fixes - Fix emoji display in pyodide fence for themes other than Material ([e09e9b2](https://github.com/pawamoy/markdown-exec/commit/e09e9b2005f9fe0599db7bc9f053e7227b824a6a) by Timothée Mazzucotelli). [Issue-83](https://github.com/pawamoy/markdown-exec/issues/83), [PR-84](https://github.com/pawamoy/markdown-exec/pull/84) ## [1.10.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.10.0) - 2024-12-06 [Compare with 1.9.3](https://github.com/pawamoy/markdown-exec/compare/1.9.3...1.10.0) ### Build - Drop support for Python 3.8 ([103bc1d](https://github.com/pawamoy/markdown-exec/commit/103bc1dc5f07f330b9e7ca4f052714350c52389d) by Timothée Mazzucotelli). ### Features - Allow setting Pyodide version ([912c8c7](https://github.com/pawamoy/markdown-exec/commit/912c8c75a5f579a949644f33bcead0b71e9637fd) by Andrew). [Issue-66](https://github.com/pawamoy/markdown-exec/issues/66), [PR-67](https://github.com/pawamoy/markdown-exec/pull/67), Co-authored-by: Timothée Mazzucotelli ## [1.9.3](https://github.com/pawamoy/markdown-exec/releases/tag/1.9.3) - 2024-06-24 [Compare with 1.9.2](https://github.com/pawamoy/markdown-exec/compare/1.9.2...1.9.3) ### Bug Fixes - Fix patching lines in tracebacks on Python 3.13 ([917af4c](https://github.com/pawamoy/markdown-exec/commit/917af4c90138a861fb488b91b754231a04bf9f96) by Timothée Mazzucotelli). [Issue-58](https://github.com/pawamoy/markdown-exec/issues/58) ## [1.9.2](https://github.com/pawamoy/markdown-exec/releases/tag/1.9.2) - 2024-06-20 [Compare with 1.9.1](https://github.com/pawamoy/markdown-exec/compare/1.9.1...1.9.2) ### Bug Fixes - Render source even if output is empty ([d3f1e6b](https://github.com/pawamoy/markdown-exec/commit/d3f1e6bcf245c656b0014e859e66137ad1e89549) by Timothée Mazzucotelli). [Issue-57](https://github.com/pawamoy/markdown-exec/issues/57) ## [1.9.1](https://github.com/pawamoy/markdown-exec/releases/tag/1.9.1) - 2024-06-14 [Compare with 1.9.0](https://github.com/pawamoy/markdown-exec/compare/1.9.0...1.9.1) ### Build - Re-include tests folder in source distributions ([ae549db](https://github.com/pawamoy/markdown-exec/commit/ae549dbfb7382cf4fa8c35bdcfa4619231f37f4b) by Timothée Mazzucotelli). [Issue-55](https://github.com/pawamoy/markdown-exec/issues/55) ## [1.9.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.9.0) - 2024-06-13 [Compare with 1.8.3](https://github.com/pawamoy/markdown-exec/compare/1.8.3...1.9.0) ### Features - Abort with error when the plugin is configured to require the ANSI extra but it is not installed ([25bcbbe](https://github.com/pawamoy/markdown-exec/commit/25bcbbe6cbc0e0df764456a508f03de2abfcd938) by Timothée Mazzucotelli). - Allow excluding assets when rendering Pyodide fence ([5412353](https://github.com/pawamoy/markdown-exec/commit/541235354210522f67af8ff2dc03dfa5216bca20) by Timothée Mazzucotelli). - Detect when SuperFences is not enabled and error out early ([5d771d2](https://github.com/pawamoy/markdown-exec/commit/5d771d285cecfcb631438f04b94f5b20275d03df) by Timothée Mazzucotelli). [Issue-39](https://github.com/pawamoy/markdown-exec/issues/39) - Allow changing the console width for the execution of code blocks ([76d603c](https://github.com/pawamoy/markdown-exec/commit/76d603ce57232f2ee98f5abec265d2f67174fbdd) by Timothée Mazzucotelli). [Issue-34](https://github.com/pawamoy/markdown-exec/issues/34) - Allow automatic execution of code blocks thanks to the `MARKDOWN_EXEC_AUTO` environment variable ([0db27b2](https://github.com/pawamoy/markdown-exec/commit/0db27b23dd7697afb19755b4ec32db43c4add75a) by Timothée Mazzucotelli). [Issue-24](https://github.com/pawamoy/markdown-exec/issues/24) - Allow changing working directory for code blocks execution ([ee3fae9](https://github.com/pawamoy/markdown-exec/commit/ee3fae957193e2beb6ac9a0bad3b261d1b67584e) by Timothée Mazzucotelli). [Issue-20](https://github.com/pawamoy/markdown-exec/issues/20) ### Bug Fixes - Reuse existing loggers tp prevent wrong dispatch ([8967270](https://github.com/pawamoy/markdown-exec/commit/8967270d821e5e021a2298ff8e458bc6ce0f1762) by Timothée Mazzucotelli). - Don't render anything when code block output is empty ([4337d13](https://github.com/pawamoy/markdown-exec/commit/4337d1337b5aedd190627287f7e0a48000396902) by Timothée Mazzucotelli). [Issue-17](https://github.com/pawamoy/markdown-exec/issues/17) - Increase minimum height of Pyodide output code blocks ([93598b2](https://github.com/pawamoy/markdown-exec/commit/93598b285babfca59b2b297adf804719f012f812) by Timothée Mazzucotelli). [Issue-40](https://github.com/pawamoy/markdown-exec/issues/40) - Fix removal of temporary div for headings forwarding ([c012c1d](https://github.com/pawamoy/markdown-exec/commit/c012c1d9f194f492dcb055638c790580aa91c51e) by Timothée Mazzucotelli). [Issue-50](https://github.com/pawamoy/markdown-exec/issues/50) ### Code Refactoring - Wrap placeholders in `
` to avoid them being wrapped in `

` ([500ed1b](https://github.com/pawamoy/markdown-exec/commit/500ed1b3a6bb94edd3d5d7152cd818bc3db27bbd) by Timothée Mazzucotelli). ## [1.8.3](https://github.com/pawamoy/markdown-exec/releases/tag/1.8.3) - 2024-05-22 [Compare with 1.8.2](https://github.com/pawamoy/markdown-exec/compare/1.8.2...1.8.3) ### Bug Fixes - Don't leak future annotations in user code ([ba0c35e](https://github.com/pawamoy/markdown-exec/commit/ba0c35e89e3056325b3dcbc7e61b6f108ec55885) by Timothée Mazzucotelli). [Issue-47](https://github.com/pawamoy/markdown-exec/issues/47) ## [1.8.2](https://github.com/pawamoy/markdown-exec/releases/tag/1.8.2) - 2024-05-20 [Compare with 1.8.1](https://github.com/pawamoy/markdown-exec/compare/1.8.1...1.8.2) ### Bug Fixes - Give `__name__` to executed Python "modules", and populate `sys.modules` too ([db25ee7](https://github.com/pawamoy/markdown-exec/commit/db25ee703da9b70cb4a13b2b4b61634d697119c4) by Timothée Mazzucotelli). [Issue-47](https://github.com/pawamoy/markdown-exec/issues/47) ## [1.8.1](https://github.com/pawamoy/markdown-exec/releases/tag/1.8.1) - 2024-04-15 [Compare with 1.8.0](https://github.com/pawamoy/markdown-exec/compare/1.8.0...1.8.1) ### Bug Fixes - Add missing CSS classes to the ANSI stylesheet ([51493f2](https://github.com/pawamoy/markdown-exec/commit/51493f255dd91f28ce6c8d7e7176ec5687e28b4a) by Timothée Mazzucotelli). [Issue-43](https://github.com/pawamoy/markdown-exec/issues/43) ## [1.8.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.8.0) - 2024-01-05 [Compare with 1.7.0](https://github.com/pawamoy/markdown-exec/compare/1.7.0...1.8.0) ### Features - Add `pyodide` fence ([3a2fab0](https://github.com/pawamoy/markdown-exec/commit/3a2fab0b23196a4122bcee6d9b81d3f421f11bbb) by Timothée Mazzucotelli). - Add `ansi` option to mark ANSI extra as required or not ([27743c2](https://github.com/pawamoy/markdown-exec/commit/27743c20f56dd00ce730e1d028d362a4f95e48c7) by Timothée Mazzucotelli). [Issue #28](https://github.com/pawamoy/markdown-exec/issues/28), [Issue #29](https://github.com/pawamoy/markdown-exec/issues/29) ### Code Refactoring - Modernize MkDocs plugin ([4864608](https://github.com/pawamoy/markdown-exec/commit/48646081746c6c5ece0c6566a4b9733ace518791) by Timothée Mazzucotelli). ## [1.7.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.7.0) - 2023-10-17 [Compare with 1.6.0](https://github.com/pawamoy/markdown-exec/compare/1.6.0...1.7.0) ### Features - Set `MKDOCS_CONFIG_DIR` environment variable to build file path relative to it ([a2cbea5](https://github.com/pawamoy/markdown-exec/commit/a2cbea52d39ef43960c910830eae14dc846624d0) by Timothée Mazzucotelli). ## [1.6.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.6.0) - 2023-04-18 [Compare with 1.5.3](https://github.com/pawamoy/markdown-exec/compare/1.5.3...1.6.0) ### Features - Add `idprefix` option allowing to change/remove HTML id/href prefixes ([4d91463](https://github.com/pawamoy/markdown-exec/commit/4d914630e5642feb87103644800d3c9f7b59c6ad) by Timothée Mazzucotelli). ## [1.5.3](https://github.com/pawamoy/markdown-exec/releases/tag/1.5.3) - 2023-04-18 [Compare with 1.5.2](https://github.com/pawamoy/markdown-exec/compare/1.5.2...1.5.3) ### Code Refactoring - Reuse Markdown configuration as declared in mkdocs.yml ([afe091c](https://github.com/pawamoy/markdown-exec/commit/afe091caa33ed54fd65e25e4f90b8b60786ba3f9) by Timothée Mazzucotelli). ## [1.5.2](https://github.com/pawamoy/markdown-exec/releases/tag/1.5.2) - 2023-04-18 [Compare with 1.5.1](https://github.com/pawamoy/markdown-exec/compare/1.5.1...1.5.2) ### Code Refactoring - Reset counter in post build event ([3bf80de](https://github.com/pawamoy/markdown-exec/commit/3bf80deabe9a7438b459c73e962c9693bce71135) by Timothée Mazzucotelli). ## [1.5.1](https://github.com/pawamoy/markdown-exec/releases/tag/1.5.1) - 2023-04-17 [Compare with 1.5.0](https://github.com/pawamoy/markdown-exec/compare/1.5.0...1.5.1) ### Bug Fixes - Remove pycon output lines when rendering source as console ([fb5a23d](https://github.com/pawamoy/markdown-exec/commit/fb5a23d8d1d50aa2a1ede97150c269a07fa200ec) by Timothée Mazzucotelli). - Fix nested rendering ([a110d44](https://github.com/pawamoy/markdown-exec/commit/a110d446209b390ec8a4ad8868818352f72a9808) by Timothée Mazzucotelli). ## [1.5.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.5.0) - 2023-04-17 [Compare with 1.4.1](https://github.com/pawamoy/markdown-exec/compare/1.4.1...1.5.0) ### Features - Update ToC with generated headings ([5ea2263](https://github.com/pawamoy/markdown-exec/commit/5ea2263d53729b6d3e79da69c29b171bb6c3e22d) by Timothée Mazzucotelli). ## [1.4.1](https://github.com/pawamoy/markdown-exec/releases/tag/1.4.1) - 2023-04-16 [Compare with 1.4.0](https://github.com/pawamoy/markdown-exec/compare/1.4.0...1.4.1) ### Bug Fixes - Improve handling of errors within sessions ([87ac5f3](https://github.com/pawamoy/markdown-exec/commit/87ac5f352ce44370f52a7fb56d846c04b76447f9) by Timothée Mazzucotelli). - Swallow non-extra parameters in run functions ([f5d4fef](https://github.com/pawamoy/markdown-exec/commit/f5d4fef1f78d94c3f8850f873076e3cd68c0a981) by Timothée Mazzucotelli). ### Code Refactoring - Simplify tree formatter signature ([09d5427](https://github.com/pawamoy/markdown-exec/commit/09d542772ccb0d1250366b39fa3a9c9362e1ed42) by Timothée Mazzucotelli). ## [1.4.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.4.0) - 2023-03-15 [Compare with 1.3.0](https://github.com/pawamoy/markdown-exec/compare/1.3.0...1.4.0) ### Features - Sessions: persist and reuse state for Python and Pycon code blocks ([a8fef5e](https://github.com/pawamoy/markdown-exec/commit/a8fef5e90b1d7165e16ff5afe4b84e8441503098) by Timothée Mazzucotelli). [Issue #16](https://github.com/pawamoy/markdown-exec/issues/16) ## [1.3.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.3.0) - 2023-02-18 [Compare with 1.2.0](https://github.com/pawamoy/markdown-exec/compare/1.2.0...1.3.0) ### Features - Support wrapping result with console source ([268c82e](https://github.com/pawamoy/markdown-exec/commit/268c82e6f005dcaa1ddc75608d2f28927f069761) by Timothée Mazzucotelli). [Issue #13](https://github.com/pawamoy/markdown-exec/issues/13) ### Code Refactoring - Remove margin hack from Material source ([beec237](https://github.com/pawamoy/markdown-exec/commit/beec2374b27075e66ddb4a7cdc2f2c81b7455b95) by Timothée Mazzucotelli). - Better support pycon syntax ([22b51c6](https://github.com/pawamoy/markdown-exec/commit/22b51c64155060922e46ea10e6c0d1c1c1b00a2f) by Timothée Mazzucotelli). ## [1.2.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.2.0) - 2023-02-01 [Compare with 1.1.0](https://github.com/pawamoy/markdown-exec/compare/1.1.0...1.2.0) ### Features - Support ANSI code blocks ([39719c5](https://github.com/pawamoy/markdown-exec/commit/39719c5d7ac1bbde6d60002082a0ad3b48730545) by Timothée Mazzucotelli). [Issue #11](https://github.com/pawamoy/markdown-exec/issues/11) ## [1.1.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.1.0) - 2023-01-27 [Compare with 1.0.0](https://github.com/pawamoy/markdown-exec/compare/1.0.0...1.1.0) ### Features - Log details to help debugging errors ([4c0228d](https://github.com/pawamoy/markdown-exec/commit/4c0228da41f5970e719b20a40c0fab47a9d12244) by Timothée Mazzucotelli). [Issue #12](https://github.com/pawamoy/markdown-exec/issues/12) - Allow expecting specific exit codes ([620ec66](https://github.com/pawamoy/markdown-exec/commit/620ec66182dd0f84600258408720779822615085) by Timothée Mazzucotelli). [Issue #10](https://github.com/pawamoy/markdown-exec/issues/10) ### Code Refactoring - Formatters now only accept keyword arguments ([0940ca9](https://github.com/pawamoy/markdown-exec/commit/0940ca98e81548474351e234715df2fc290fdc1e) by Timothée Mazzucotelli). ## [1.0.0](https://github.com/pawamoy/markdown-exec/releases/tag/1.0.0) - 2022-11-24 [Compare with 0.7.4](https://github.com/pawamoy/markdown-exec/compare/0.7.4...1.0.0) ### Features - Allow defining IDs on code blocks (for warnings) ([0091167](https://github.com/pawamoy/markdown-exec/commit/009116719e81dd91190b391c82709fb179a62364) by Timothée Mazzucotelli). ### Code Refactoring - Use base format everywhere (more flexible) ([cefba70](https://github.com/pawamoy/markdown-exec/commit/cefba704ae45df1b115b969e3d4d5105ebd052dd) by Timothée Mazzucotelli). ## [0.7.4](https://github.com/pawamoy/markdown-exec/releases/tag/0.7.4) - 2022-11-13 [Compare with 0.7.3](https://github.com/pawamoy/markdown-exec/compare/0.7.3...0.7.4) ### Bug Fixes - Render source for non-HTML output (regression) ([3028dcd](https://github.com/pawamoy/markdown-exec/commit/3028dcd4f20f94b578995c326fd68d53a6dc3638) by Timothée Mazzucotelli). ## [0.7.3](https://github.com/pawamoy/markdown-exec/releases/tag/0.7.3) - 2022-11-13 [Compare with 0.7.2](https://github.com/pawamoy/markdown-exec/compare/0.7.2...0.7.3) ### Bug Fixes - Don't wrap HTML in `p` tag ([420d79d](https://github.com/pawamoy/markdown-exec/commit/420d79d67c2a6bdc925b3bc3d89790258f922317) by Timothée Mazzucotelli). ## [0.7.2](https://github.com/pawamoy/markdown-exec/releases/tag/0.7.2) - 2022-09-01 [Compare with 0.7.1](https://github.com/pawamoy/markdown-exec/compare/0.7.1...0.7.2) ### Bug Fixes - Make `tree` formatter forward extra options ([54996a9](https://github.com/pawamoy/markdown-exec/commit/54996a9bc2c803bb8c9de0861af69723ddb000fa) by Timothée Mazzucotelli). - Fix race condition issue ([37d7f86](https://github.com/pawamoy/markdown-exec/commit/37d7f86eeaa73029ae89c1c5d07146d2387b10d3) by Timothée Mazzucotelli). ## [0.7.1](https://github.com/pawamoy/markdown-exec/releases/tag/0.7.1) - 2022-08-28 [Compare with 0.7.0](https://github.com/pawamoy/markdown-exec/compare/0.7.0...0.7.1) ### Bug Fixes - Allow printing non-string objects ([ceaa482](https://github.com/pawamoy/markdown-exec/commit/ceaa482d16adfbd1609595a2ed6a241bad71f9de) by Timothée Mazzucotelli). [Issue #7](https://github.com/pawamoy/markdown-exec/issues/7) ## [0.7.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.7.0) - 2022-05-28 [Compare with 0.6.0](https://github.com/pawamoy/markdown-exec/compare/0.6.0...0.7.0) ### Features - Add ability to hide source lines ([3cb1934](https://github.com/pawamoy/markdown-exec/commit/3cb19345fa2b65478ac439b5f486d04bf5ff5337) by Timothée Mazzucotelli). ## [0.6.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.6.0) - 2022-05-21 [Compare with 0.5.0](https://github.com/pawamoy/markdown-exec/compare/0.5.0...0.6.0) ### Features - Add tree formatter ([8096990](https://github.com/pawamoy/markdown-exec/commit/8096990dcbf6795572e5e5afee12195d5a56c6f6) by Timothée Mazzucotelli). - Handle code blocks execution errors and log warnings ([34e16db](https://github.com/pawamoy/markdown-exec/commit/34e16db679721db7d1df375912d512b5aed80b1a) by Timothée Mazzucotelli). ### Bug Fixes - Fix Python execution to support nested scopes ([74b9a95](https://github.com/pawamoy/markdown-exec/commit/74b9a95ade3862752fb78d6c64be8b9b1d4d3886) by Timothée Mazzucotelli). ## [0.5.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.5.0) - 2022-05-09 [Compare with 0.4.0](https://github.com/pawamoy/markdown-exec/compare/0.4.0...0.5.0) ### Features - Allow wrapping result in code block ([37201e4](https://github.com/pawamoy/markdown-exec/commit/37201e4409badec903f311bcc0a6ab7acddff37c) by Timothée Mazzucotelli). - Add support for shell code blocks ([f2b4b67](https://github.com/pawamoy/markdown-exec/commit/f2b4b671f4399637d0dac235a0af7739033f9526) by Timothée Mazzucotelli). ### Code Refactoring - Fetch plugin languages from dict ([de8309e](https://github.com/pawamoy/markdown-exec/commit/de8309e6895a079031461bfea317215bcff9bc21) by Timothée Mazzucotelli). - Add reusable base formatter ([c265bee](https://github.com/pawamoy/markdown-exec/commit/c265bee9abf0ad545b7fdc6ccf2e320071295a18) by Timothée Mazzucotelli). ## [0.4.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.4.0) - 2022-05-09 [Compare with 0.3.1](https://github.com/pawamoy/markdown-exec/compare/0.3.1...0.4.0) ### Features - Add literate Markdown support ([8d3ed7e](https://github.com/pawamoy/markdown-exec/commit/8d3ed7ef5c9a88849be0a5da44e7b478eb44c180) by Timothée Mazzucotelli). - Add `material-block` style to show source ([ff10ee1](https://github.com/pawamoy/markdown-exec/commit/ff10ee1f0b55b2e77b97f272b49b24024f9de2ac) by Timothée Mazzucotelli). - Support up to 8 levels of exec code block nesting ([bfde808](https://github.com/pawamoy/markdown-exec/commit/bfde8087ca6f4eb91aba8eb01b37755dfacb4cdb) by Timothée Mazzucotelli). ## [0.3.1](https://github.com/pawamoy/markdown-exec/releases/tag/0.3.1) - 2022-05-07 [Compare with 0.3.0](https://github.com/pawamoy/markdown-exec/compare/0.3.0...0.3.1) ### Bug Fixes - Actually prevent HTML re-rendering ([4374852](https://github.com/pawamoy/markdown-exec/commit/4374852706207beac3b8dbd8dc9d75be51b1df0d) by Timothée Mazzucotelli). ## [0.3.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.3.0) - 2022-05-01 [Compare with 0.2.0](https://github.com/pawamoy/markdown-exec/compare/0.2.0...0.3.0) ### Features - Support `pycon` code blocks ([2c86394](https://github.com/pawamoy/markdown-exec/commit/2c86394417858654af21316c3555aff0e9fd2d26) by Timothée Mazzucotelli). - Add `console` source integration option ([62dfd74](https://github.com/pawamoy/markdown-exec/commit/62dfd74185f7f33cdf6f4726b3aa898a1ac5d22f) by Timothée Mazzucotelli). - Provide a MkDocs plugin for easier setup ([5fce814](https://github.com/pawamoy/markdown-exec/commit/5fce81462063b7c61d9833939e44958a466d4b24) by Timothée Mazzucotelli). - Support changing tabs titles ([d150596](https://github.com/pawamoy/markdown-exec/commit/d150596beda1e5a5304bc06e27668294a75ff220) by Timothée Mazzucotelli). - Allow using `print` in code blocks ([7c124fd](https://github.com/pawamoy/markdown-exec/commit/7c124fd416d6923bea2834479d972b14c3e22112) by Timothée Mazzucotelli). - Allow passing extra opts like title to source code blocks ([bb3252a](https://github.com/pawamoy/markdown-exec/commit/bb3252a3e959cea198966ba59a70f6f5aa57a963) by Timothée Mazzucotelli). ### Code Refactoring - Split Python formatter to allow reuse ([fc56702](https://github.com/pawamoy/markdown-exec/commit/fc56702b9c393323adc30abba42c823f601ef738) by Timothée Mazzucotelli). - Setup a more robust Markdown converter ([395f4c4](https://github.com/pawamoy/markdown-exec/commit/395f4c4c21ab7f4afcc88250c1fd882269a06d02) by Timothée Mazzucotelli). ## [0.2.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.2.0) - 2022-04-18 [Compare with 0.1.0](https://github.com/pawamoy/markdown-exec/compare/0.1.0...0.2.0) ### Features - Add ability to render using tabs ([91a95ae](https://github.com/pawamoy/markdown-exec/commit/91a95ae4c6ad82e85dac24a110d09ca71eff688a) by Timothée Mazzucotelli). ## [0.1.0](https://github.com/pawamoy/markdown-exec/releases/tag/0.1.0) - 2022-02-19 [Compare with first commit](https://github.com/pawamoy/markdown-exec/compare/41c8d81992d2443cd5c3418df0f461b0af1a6ec8...0.1.0) ### Features - Implement execution of code blocks ([41c8d81](https://github.com/pawamoy/markdown-exec/commit/41c8d81992d2443cd5c3418df0f461b0af1a6ec8) by Timothée Mazzucotelli). markdown-exec-1.10.2/CODE_OF_CONDUCT.md000066400000000000000000000125361476636417300171660ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at dev@pawamoy.fr. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations markdown-exec-1.10.2/CONTRIBUTING.md000066400000000000000000000100751476636417300166140ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ## Environment setup Nothing easier! Fork and clone the repository, then: ```bash cd markdown-exec make setup ``` > NOTE: If it fails for some reason, you'll need to install [uv](https://github.com/astral-sh/uv) manually. > > You can install it with: > > ```bash > curl -LsSf https://astral.sh/uv/install.sh | sh > ``` > > Now you can try running `make setup` again, or simply `uv sync`. You now have the dependencies installed. Run `make help` to see all the available actions! ## Tasks The entry-point to run commands and tasks is the `make` Python script, located in the `scripts` directory. Try running `make` to show the available commands and tasks. The *commands* do not need the Python dependencies to be installed, while the *tasks* do. The cross-platform tasks are written in Python, thanks to [duty](https://github.com/pawamoy/duty). If you work in VSCode, we provide [an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup) for the project. ## Development As usual: 1. create a new branch: `git switch -c feature-or-bugfix-name` 1. edit the code and/or the documentation **Before committing:** 1. run `make format` to auto-format the code 1. run `make check` to check everything (fix any warning) 1. run `make test` to run the tests (fix any issue) 1. if you updated the documentation or the project dependencies: 1. run `make docs` 1. go to http://localhost:8000 and check that everything looks good 1. follow our [commit message convention](#commit-message-convention) If you are unsure about how to fix or ignore a warning, just let the continuous integration fail, and we will help you during review. Don't bother updating the changelog, we will take care of this. ## Commit message convention Commit messages must follow our convention based on the [Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html): ``` [(scope)]: Subject [Body] ``` **Subject and body must be valid Markdown.** Subject must have proper casing (uppercase for first letter if it makes sense), but no dot at the end, and no punctuation in general. Scope and body are optional. Type can be: - `build`: About packaging, building wheels, etc. - `chore`: About packaging or repo/files management. - `ci`: About Continuous Integration. - `deps`: Dependencies update. - `docs`: About documentation. - `feat`: New feature. - `fix`: Bug fix. - `perf`: About performance. - `refactor`: Changes that are not features or bug fixes. - `style`: A change in code style/format. - `tests`: About tests. If you write a body, please add trailers at the end (for example issues and PR references, or co-authors), without relying on GitHub's flavored Markdown: ``` Body. Issue #10: https://github.com/namespace/project/issues/10 Related to PR namespace/other-project#15: https://github.com/namespace/other-project/pull/15 ``` These "trailers" must appear at the end of the body, without any blank lines between them. The trailer title can contain any character except colons `:`. We expect a full URI for each trailer, not just GitHub autolinks (for example, full GitHub URLs for commits and issues, not the hash or the #issue-number). We do not enforce a line length on commit messages summary and body, but please avoid very long summaries, and very long lines in the body, unless they are part of code blocks that must not be wrapped. ## Pull requests guidelines Link to any related issue in the Pull Request message. During the review, we recommend using fixups: ```bash # SHA is the SHA of the commit you want to fix git commit --fixup=SHA ``` Once all the changes are approved, you can squash your commits: ```bash git rebase -i --autosquash main ``` And force-push: ```bash git push -f ``` If this seems all too complicated, you can push or force-push each new commit, and we will squash them ourselves if needed, before merging. markdown-exec-1.10.2/FUNDING.json000066400000000000000000000001531476636417300163440ustar00rootroot00000000000000{ "drips": { "ethereum": { "ownedBy": "0xaaac25D56367b6c9e0496c092679072720382A0e" } } } markdown-exec-1.10.2/LICENSE000066400000000000000000000013621476636417300153670ustar00rootroot00000000000000ISC License Copyright (c) 2022, Timothée Mazzucotelli Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. markdown-exec-1.10.2/Makefile000066400000000000000000000007601476636417300160230ustar00rootroot00000000000000# If you have `direnv` loaded in your shell, and allow it in the repository, # the `make` command will point at the `scripts/make` shell script. # This Makefile is just here to allow auto-completion in the terminal. actions = \ allrun \ changelog \ check \ check-api \ check-docs \ check-quality \ check-types \ clean \ coverage \ docs \ docs-deploy \ format \ help \ multirun \ release \ run \ setup \ test \ vscode .PHONY: $(actions) $(actions): @python scripts/make "$@" markdown-exec-1.10.2/README.md000066400000000000000000000063311476636417300156420ustar00rootroot00000000000000# Markdown Exec [![ci](https://github.com/pawamoy/markdown-exec/workflows/ci/badge.svg)](https://github.com/pawamoy/markdown-exec/actions?query=workflow%3Aci) [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/markdown-exec/) [![pypi version](https://img.shields.io/pypi/v/markdown-exec.svg)](https://pypi.org/project/markdown-exec/) [![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#markdown-exec:gitter.im) Utilities to execute code blocks in Markdown files. For example, you write a Python code block that computes some HTML, and this HTML is injected in place of the code block. ## Installation ```bash pip install "markdown-exec[ansi]" ``` The `ansi` extra provides the necessary bits (`pygments-ansi-color` and a CSS file) to render ANSI colors in HTML code blocks. The CSS file is automatically added to MkDocs' `extra_css` when Markdown Exec is activated via `plugins` (see below). ## Configuration This extension relies on the [SuperFences](https://facelessuser.github.io/pymdown-extensions/extensions/superfences/) extension of [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/). To allow execution of code blocks, configure a custom fence from Python: ```python from markdown import Markdown from markdown_exec import formatter, validator Markdown( extensions=["pymdownx.superfences"], extension_configs={ "pymdownx.superfences": { "custom_fences": [ { "name": "python", "class": "python", "validator": validator, "format": formatter, } # ...one fence for each language we support: # bash, console, md, markdown, py, python, pycon, sh, tree ] } } ) ``` ...or in MkDocs configuration file, as a Markdown extension: ```yaml # mkdocs.yml markdown_extensions: - pymdownx.superfences: custom_fences: - name: python class: python validator: !!python/name:markdown_exec.validator format: !!python/name:markdown_exec.formatter # ...one fence for each language we support: # bash, console, md, markdown, py, python, pycon, sh, tree ``` ...or in MkDocs configuration file, as a plugin: ```yaml # mkdocs.yml plugins: - search - markdown-exec # SuperFences must still be enabled! markdown_extensions: - pymdownx.superfences ``` We do recommend enabling Markdown Exec with the MkDocs plugin if you are using MkDocs: it will take care of adding relevant assets (CSS/JS) to the final site when needed. ## Usage You are now able to execute code blocks instead of displaying them: ````md ```python exec="on" print("Hello Markdown!") ``` ```` The `exec` option will be true for every possible value except `0`, `no`, `off` and `false` (case insensitive). Below you can see an example of running a bash script that is expected to return a non-zero exit code: ````md ```bash exec="1" source="tabbed-left" returncode="2" grep extra_css README.md && exit 2 ``` ```` See [usage](https://pawamoy.github.io/markdown-exec/usage/) for more details, and the [gallery](https://pawamoy.github.io/markdown-exec/gallery/) for more examples! markdown-exec-1.10.2/config/000077500000000000000000000000001476636417300156255ustar00rootroot00000000000000markdown-exec-1.10.2/config/coverage.ini000066400000000000000000000005631476636417300201250ustar00rootroot00000000000000[coverage:run] branch = true parallel = true source = src/ tests/ [coverage:paths] equivalent = src/ .venv/lib/*/site-packages/ .venvs/*/lib/*/site-packages/ [coverage:report] precision = 2 omit = src/*/__init__.py src/*/__main__.py tests/__init__.py exclude_lines = pragma: no cover if TYPE_CHECKING [coverage:json] output = htmlcov/coverage.json markdown-exec-1.10.2/config/git-changelog.toml000066400000000000000000000003401476636417300212270ustar00rootroot00000000000000bump = "auto" convention = "angular" in-place = true output = "CHANGELOG.md" parse-refs = false parse-trailers = true sections = ["build", "deps", "feat", "fix", "refactor"] template = "keepachangelog" versioning = "pep440" markdown-exec-1.10.2/config/mypy.ini000066400000000000000000000002701476636417300173230ustar00rootroot00000000000000[mypy] ignore_missing_imports = true exclude = (?x)( tests/fixtures/ | docs/snippets/gallery/ | docs/snippets/usage/ ) warn_unused_ignores = true show_error_codes = true markdown-exec-1.10.2/config/pytest.ini000066400000000000000000000004341476636417300176570ustar00rootroot00000000000000[pytest] python_files = test_*.py addopts = --cov --cov-config config/coverage.ini testpaths = tests # action:message_regex:warning_class:module_regex:line filterwarnings = error # TODO: remove once pytest-xdist 4 is released ignore:.*rsyncdir:DeprecationWarning:xdist markdown-exec-1.10.2/config/ruff.toml000066400000000000000000000044221476636417300174660ustar00rootroot00000000000000target-version = "py39" line-length = 120 [lint] exclude = [ "tests/fixtures/*.py", ] select = [ "A", "ANN", "ARG", "B", "BLE", "C", "C4", "COM", "D", "DTZ", "E", "ERA", "EXE", "F", "FBT", "G", "I", "ICN", "INP", "ISC", "N", "PGH", "PIE", "PL", "PLC", "PLE", "PLR", "PLW", "PT", "PYI", "Q", "RUF", "RSE", "RET", "S", "SIM", "SLF", "T", "T10", "T20", "TCH", "TID", "TRY", "UP", "W", "YTT", ] ignore = [ "A001", # Variable is shadowing a Python builtin "ANN101", # Missing type annotation for self "ANN102", # Missing type annotation for cls "ANN204", # Missing return type annotation for special method __str__ "ANN401", # Dynamically typed expressions (typing.Any) are disallowed "ARG005", # Unused lambda argument "C901", # Too complex "D105", # Missing docstring in magic method "D417", # Missing argument description in the docstring "E501", # Line too long "ERA001", # Commented out code "G004", # Logging statement uses f-string "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments to function call "PLR0915", # Too many statements "SLF001", # Private member accessed "TRY003", # Avoid specifying long messages outside the exception class ] [lint.per-file-ignores] "src/**/cli.py" = [ "T201", # Print statement ] "src/*/debug.py" = [ "T201", # Print statement ] "!src/*/*.py" = [ "D100", # Missing docstring in public module ] "!src/**.py" = [ "D101", # Missing docstring in public class "D103", # Missing docstring in public function ] "scripts/*.py" = [ "INP001", # File is part of an implicit namespace package "T201", # Print statement ] "tests/**.py" = [ "ARG005", # Unused lambda argument "FBT001", # Boolean positional arg in function definition "PLR2004", # Magic value used in comparison "S101", # Use of assert detected ] [lint.flake8-quotes] docstring-quotes = "double" [lint.flake8-tidy-imports] ban-relative-imports = "all" [lint.isort] known-first-party = ["markdown_exec"] [lint.pydocstyle] convention = "google" [format] exclude = [ "tests/fixtures/*.py", ] docstring-code-format = true docstring-code-line-length = 80 markdown-exec-1.10.2/config/vscode/000077500000000000000000000000001476636417300171105ustar00rootroot00000000000000markdown-exec-1.10.2/config/vscode/launch.json000066400000000000000000000026711476636417300212630ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "python (current file)", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", "justMyCode": false, "args": "${command:pickArgs}" }, { "name": "run", "type": "debugpy", "request": "launch", "module": "markdown_exec", "console": "integratedTerminal", "justMyCode": false, "args": "${command:pickArgs}" }, { "name": "docs", "type": "debugpy", "request": "launch", "module": "mkdocs", "justMyCode": false, "args": [ "serve", "-v" ] }, { "name": "test", "type": "debugpy", "request": "launch", "module": "pytest", "justMyCode": false, "args": [ "-c=config/pytest.ini", "-vvv", "--no-cov", "--dist=no", "tests", "-k=${input:tests_selection}" ] } ], "inputs": [ { "id": "tests_selection", "type": "promptString", "description": "Tests selection", "default": "" } ] }markdown-exec-1.10.2/config/vscode/settings.json000066400000000000000000000017041476636417300216450ustar00rootroot00000000000000{ "files.watcherExclude": { "**/.venv*/**": true, "**/.venvs*/**": true, "**/venv*/**": true }, "mypy-type-checker.args": [ "--config-file=config/mypy.ini" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ "--config-file=config/pytest.ini" ], "ruff.enable": true, "ruff.format.args": [ "--config=config/ruff.toml" ], "ruff.lint.args": [ "--config=config/ruff.toml" ], "yaml.schemas": { "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" }, "yaml.customTags": [ "!ENV scalar", "!ENV sequence", "!relative scalar", "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" ] }markdown-exec-1.10.2/config/vscode/tasks.json000066400000000000000000000046051476636417300211350ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "changelog", "type": "process", "command": "scripts/make", "args": ["changelog"] }, { "label": "check", "type": "process", "command": "scripts/make", "args": ["check"] }, { "label": "check-quality", "type": "process", "command": "scripts/make", "args": ["check-quality"] }, { "label": "check-types", "type": "process", "command": "scripts/make", "args": ["check-types"] }, { "label": "check-docs", "type": "process", "command": "scripts/make", "args": ["check-docs"] }, { "label": "check-api", "type": "process", "command": "scripts/make", "args": ["check-api"] }, { "label": "clean", "type": "process", "command": "scripts/make", "args": ["clean"] }, { "label": "docs", "type": "process", "command": "scripts/make", "args": ["docs"] }, { "label": "docs-deploy", "type": "process", "command": "scripts/make", "args": ["docs-deploy"] }, { "label": "format", "type": "process", "command": "scripts/make", "args": ["format"] }, { "label": "release", "type": "process", "command": "scripts/make", "args": ["release", "${input:version}"] }, { "label": "setup", "type": "process", "command": "scripts/make", "args": ["setup"] }, { "label": "test", "type": "process", "command": "scripts/make", "args": ["test", "coverage"], "group": "test" }, { "label": "vscode", "type": "process", "command": "scripts/make", "args": ["vscode"] } ], "inputs": [ { "id": "version", "type": "promptString", "description": "Version" } ] }markdown-exec-1.10.2/docs/000077500000000000000000000000001476636417300153105ustar00rootroot00000000000000markdown-exec-1.10.2/docs/.overrides/000077500000000000000000000000001476636417300173705ustar00rootroot00000000000000markdown-exec-1.10.2/docs/.overrides/main.html000066400000000000000000000011041476636417300211760ustar00rootroot00000000000000{% extends "base.html" %} {% block announce %} Fund this project through sponsorship {% include ".icons/octicons/heart-fill-16.svg" %} — Follow @pawamoy on {% include ".icons/fontawesome/brands/mastodon.svg" %} Fosstodon for updates {% endblock %} markdown-exec-1.10.2/docs/.overrides/partials/000077500000000000000000000000001476636417300212075ustar00rootroot00000000000000markdown-exec-1.10.2/docs/.overrides/partials/comments.html000066400000000000000000000041141476636417300237220ustar00rootroot00000000000000

markdown-exec-1.10.2/docs/.overrides/partials/path-item.html000066400000000000000000000012111476636417300237600ustar00rootroot00000000000000{# Fix breadcrumbs for when mkdocs-section-index is used. #} {# See https://github.com/squidfunk/mkdocs-material/issues/7614. #} {% macro render_content(nav_item) %} {{ nav_item.title }} {% endmacro %} {% macro render(nav_item, ref=nav_item) %} {% if nav_item.is_page %}
  • {{ render_content(ref) }}
  • {% elif nav_item.children %} {{ render(nav_item.children | first, ref) }} {% endif %} {% endmacro %} markdown-exec-1.10.2/docs/changelog.md000066400000000000000000000000601476636417300175550ustar00rootroot00000000000000--- title: Changelog --- --8<-- "CHANGELOG.md" markdown-exec-1.10.2/docs/code_of_conduct.md000066400000000000000000000000741476636417300207500ustar00rootroot00000000000000--- title: Code of Conduct --- --8<-- "CODE_OF_CONDUCT.md" markdown-exec-1.10.2/docs/contributing.md000066400000000000000000000000661476636417300203430ustar00rootroot00000000000000--- title: Contributing --- --8<-- "CONTRIBUTING.md" markdown-exec-1.10.2/docs/credits.md000066400000000000000000000001351476636417300172660ustar00rootroot00000000000000--- title: Credits hide: - toc --- ```python exec="yes" --8<-- "scripts/gen_credits.py" ``` markdown-exec-1.10.2/docs/css/000077500000000000000000000000001476636417300161005ustar00rootroot00000000000000markdown-exec-1.10.2/docs/css/insiders.css000066400000000000000000000037311476636417300204360ustar00rootroot00000000000000@keyframes heart { 0%, 40%, 80%, 100% { transform: scale(1); } 20%, 60% { transform: scale(1.15); } } @keyframes vibrate { 0%, 2%, 4%, 6%, 8%, 10%, 12%, 14%, 16%, 18% { -webkit-transform: translate3d(-2px, 0, 0); transform: translate3d(-2px, 0, 0); } 1%, 3%, 5%, 7%, 9%, 11%, 13%, 15%, 17%, 19% { -webkit-transform: translate3d(2px, 0, 0); transform: translate3d(2px, 0, 0); } 20%, 100% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .heart { color: #e91e63; } .pulse { animation: heart 1000ms infinite; } .vibrate { animation: vibrate 2000ms infinite; } .new-feature svg { fill: var(--md-accent-fg-color) !important; } a.insiders { color: #e91e63; } .sponsorship-list { width: 100%; } .sponsorship-item { border-radius: 100%; display: inline-block; height: 1.6rem; margin: 0.1rem; overflow: hidden; width: 1.6rem; } .sponsorship-item:focus, .sponsorship-item:hover { transform: scale(1.1); } .sponsorship-item img { filter: grayscale(100%) opacity(75%); height: auto; width: 100%; } .sponsorship-item:focus img, .sponsorship-item:hover img { filter: grayscale(0); } .sponsorship-item.private { background: var(--md-default-fg-color--lightest); color: var(--md-default-fg-color); font-size: .6rem; font-weight: 700; line-height: 1.6rem; text-align: center; } .mastodon { color: #897ff8; border-radius: 100%; box-shadow: inset 0 0 0 .05rem currentcolor; display: inline-block; height: 1.2rem !important; padding: .25rem; transition: all .25s; vertical-align: bottom !important; width: 1.2rem; } .premium-sponsors { text-align: center; } #silver-sponsors img { height: 140px; } #bronze-sponsors img { height: 140px; } #bronze-sponsors p { display: flex; flex-wrap: wrap; justify-content: center; } #bronze-sponsors a { display: block; flex-shrink: 0; } .sponsors-total { font-weight: bold; }markdown-exec-1.10.2/docs/css/material.css000066400000000000000000000001311476636417300204030ustar00rootroot00000000000000/* More space at the bottom of the page. */ .md-main__inner { margin-bottom: 1.5rem; } markdown-exec-1.10.2/docs/css/mkdocstrings.css000066400000000000000000000046151476636417300213270ustar00rootroot00000000000000/* Indentation. */ div.doc-contents:not(.first) { padding-left: 25px; border-left: .05rem solid var(--md-typeset-table-color); } /* Mark external links as such. */ a.external::after, a.autorefs-external::after { /* https://primer.style/octicons/arrow-up-right-24 */ mask-image: url('data:image/svg+xml,'); -webkit-mask-image: url('data:image/svg+xml,'); content: ' '; display: inline-block; vertical-align: middle; position: relative; height: 1em; width: 1em; background-color: currentColor; } a.external:hover::after, a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); } /* Tree-like output for backlinks. */ .doc-backlink-list { --tree-clr: var(--md-default-fg-color); --tree-font-size: 1rem; --tree-item-height: 1; --tree-offset: 1rem; --tree-thickness: 1px; --tree-style: solid; display: grid; list-style: none !important; } .doc-backlink-list li > span:first-child { text-indent: .3rem; } .doc-backlink-list li { padding-inline-start: var(--tree-offset); border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr); position: relative; margin-left: 0 !important; &:last-child { border-color: transparent; } &::before{ content: ''; position: absolute; top: calc(var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness)); left: calc(var(--tree-thickness) * -1); width: calc(var(--tree-offset) + var(--tree-thickness) * 2); height: calc(var(--tree-item-height) * var(--tree-font-size)); border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr); border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr); } &::after{ content: ''; position: absolute; border-radius: 50%; background-color: var(--tree-clr); top: calc(var(--tree-item-height) / 2 * 1rem); left: var(--tree-offset) ; translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1); } } markdown-exec-1.10.2/docs/gallery.md000066400000000000000000000137771476636417300173100ustar00rootroot00000000000000--- hide: - navigation --- # Gallery Welcome to our gallery of examples! ## Diagrams, charts, graphs, plots ### with [Diagrams](https://github.com/mingrammer/diagrams) > Diagram as Code for prototyping cloud system architectures. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/diagrams.py" ``` ```` ### with [D2](https://d2lang.com/) > A modern diagram scripting language that turns text to diagrams. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/d2.py" ``` ```` ### with [Matplotlib](https://matplotlib.org/) > Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. ````md exec="1" source="tabbed-right" ```python exec="1" html="1" --8<-- "gallery/matplotlib.py" ``` ```` ### with [pipdeptree](https://github.com/tox-dev/pipdeptree) > A command line utility to display dependency tree of the installed Python packages. We call `pipdeptree` with its `--mermaid` option to generate a [Mermaid](https://mermaid.js.org/) diagram. ````md exec="1" source="tabbed-right" ```bash exec="1" result="mermaid" # Change the direction of the graph from top-down to left-right, # and remove local version identifiers from our own package. pipdeptree -p markdown-exec --mermaid 2>/dev/null | sed -E 's/\.dev.+"\]$/"]/;s/\+d.*"\]$/"]/' ``` ```` Another example with more dependencies: ````md exec="1" source="tabbed-right" ```bash exec="1" result="mermaid" pipdeptree -p mkdocstrings-python --mermaid 2>/dev/null | sed 's/flowchart TD/flowchart LR/' ``` ```` ### with [Plotly](https://plotly.com/python/) > The interactive graphing library for Python ✨ ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/plotly.py" ``` ```` ### with [pydeps](https://github.com/thebjorn/pydeps) > Python Module Dependency graphs. pydeps uses [Graphviz](https://graphviz.org/) under the hood to generate graphs. In this example we add links to the code reference in related nodes. Try clicking on the `markdown_exec` nodes! ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/pydeps.py" ``` ```` ## Code snippets ### with [Rich](https://github.com/Textualize/rich) > Rich is a Python library for rich text and beautiful formatting in the terminal. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/rich.py" ``` ```` ## Console output If you installed Markdown Exec with the `ansi` extra (`pip install markdown-exec[ansi]`), the ANSI colors in the output of shell commands will be translated to HTML/CSS, allowing to render them naturally in your documentation pages. For this to happen, use the [`result="ansi"` option](http://localhost:8000/markdown-exec/usage/#wrap-result-in-a-code-block). ````md exec="1" source="tabbed-right" ```bash exec="true" result="ansi" --8<-- "gallery/ansi.sh" ``` ```` ### with [Rich](https://github.com/Textualize/rich) > Rich is a Python library for rich text and beautiful formatting in the terminal. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/rich_terminal.py" ``` ```` ## SVG drawings ### with [Chalk](https://github.com/chalk-diagrams/chalk) > A declarative drawing API in Python. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/chalk.py" ``` ```` ### with [Drawsvg 2](https://github.com/cduck/drawsvg) > Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/drawsvg.py" ``` ```` ### with [Hyperbolic](https://github.com/cduck/hyperbolic) > A Python 3 library for constructing and drawing hyperbolic geometry. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/hyperbolic.py" ``` ```` ## QRCodes ### with [qrcode](https://pypi.org/project/qrcode/) > Python QR Code image generator. ````md exec="1" source="tabbed-right" ```python exec="true" html="true" --8<-- "gallery/qrcode.py" ``` ```` ## TUI screenshots ### with [Textual](https://github.com/Textualize/textual) > Textual is a *Rapid Application Development* framework for Python, built by [Textualize.io](https://www.textualize.io/). ````md exec="1" source="tabbed-right" ```python exec="1" html="true" --8<-- "gallery/textual.py" ``` ```` ## File-trees This example displays a file-tree of the current project, in which you can descend thanks to Material for MkDocs' [code annotations](https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#code-annotations). It uses a recursive Python function which accept a code block session name as parameter 🤯: ````md exec="1" source="tabbed-right" ```python exec="1" session="filetree" --8<-- "gallery/expandable_filetree.py" ``` ```python exec="1" session="filetree" exptree(".", "filetree") ``` ```` ## Python CLI documentation ### with [`argparse`](https://docs.python.org/3/library/argparse.html#module-argparse) (code block) If you know a project is using `argparse` to build its command line interface, and if it exposes its parser, then you can get the help message directly from the parser. ````md exec="1" source="tabbed-right" ```python exec="true" --8<-- "gallery/argparse_format.py" ``` ```` ### with [`argparse`](https://docs.python.org/3/library/argparse.html#module-argparse) (Markdown) In this example, we inspect the `argparse` parser to build better-looking Markdown/HTML contents. We simply use the description and iterate on options, but more complex stuff is possible of course. ````md exec="1" source="tabbed-right" ```python exec="true" updatetoc="no" --8<-- "gallery/argparse.py" ``` ```` ### with [`runpy`](https://docs.python.org/3/library/runpy.html#module-runpy) This example uses Python's `runpy` module to run another Python module. This other module's output is captured by temporarily patching `sys.stdout` with a text buffer. ````md exec="1" source="tabbed-right" ```python exec="true" --8<-- "gallery/runpy.py" ``` ```` markdown-exec-1.10.2/docs/index.md000066400000000000000000000000751476636417300167430ustar00rootroot00000000000000--- title: Overview hide: - feedback --- --8<-- "README.md" markdown-exec-1.10.2/docs/insiders/000077500000000000000000000000001476636417300171305ustar00rootroot00000000000000markdown-exec-1.10.2/docs/insiders/changelog.md000066400000000000000000000004611476636417300214020ustar00rootroot00000000000000# Changelog ## Markdown Exec Insiders ### 1.0.1 June 15, 2023 { id="1.0.1" } - Support HTML minification by wrapping code in pre tags - Catch JS error on pages without Pyodide fences ### 1.0.0 April 26, 2023 { id="1.0.0" } - Add a [`pyodide` fence](../usage/pyodide.md) markdown-exec-1.10.2/docs/insiders/goals.yml000066400000000000000000000005101476636417300207540ustar00rootroot00000000000000goals: 500: name: PlasmaVac User Guide features: - name: Pyodide fence ref: /usage/pyodide/ since: 2023/04/26 1000: name: GraviFridge Fluid Renewal features: [] 1500: name: HyperLamp Navigation Tips features: [] 2000: name: FusionDrive Ejection Configuration features: [] markdown-exec-1.10.2/docs/insiders/index.md000066400000000000000000000242271476636417300205700ustar00rootroot00000000000000--- title: Insiders --- # Insiders *Markdown Exec* follows the **sponsorware** release strategy, which means that new features are first exclusively released to sponsors as part of [Insiders][]. Read on to learn [what sponsorships achieve][sponsorship], [how to become a sponsor][sponsors] to get access to Insiders, and [what's in it for you][features]! ## What is Insiders? *Markdown Exec Insiders* is a private fork of *Markdown Exec*, hosted as a private GitHub repository. Almost[^1] [all new features][features] are developed as part of this fork, which means that they are immediately available to all eligible sponsors, as they are granted access to this private repository. [^1]: In general, every new feature is first exclusively released to sponsors, but sometimes upstream dependencies enhance existing features that must be supported by *Markdown Exec*. Every feature is tied to a [funding goal][funding] in monthly subscriptions. When a funding goal is hit, the features that are tied to it are merged back into *Markdown Exec* and released for general availability, making them available to all users. Bugfixes are always released in tandem. Sponsorships start as low as [**$10 a month**][sponsors].[^2] [^2]: Note that $10 a month is the minimum amount to become eligible for Insiders. While GitHub Sponsors also allows to sponsor lower amounts or one-time amounts, those can't be granted access to Insiders due to technical reasons. Such contributions are still very much welcome as they help ensuring the project's sustainability. ## What sponsorships achieve Sponsorships make this project sustainable, as they buy the maintainers of this project time – a very scarce resource – which is spent on the development of new features, bug fixing, stability improvement, issue triage and general support. The biggest bottleneck in Open Source is time.[^3] [^3]: Making an Open Source project sustainable is exceptionally hard: maintainers burn out, projects are abandoned. That's not great and very unpredictable. The sponsorware model ensures that if you decide to use *Markdown Exec*, you can be sure that bugs are fixed quickly and new features are added regularly. If you're unsure if you should sponsor this project, check out the list of [completed funding goals][goals completed] to learn whether you're already using features that were developed with the help of sponsorships. You're most likely using at least a handful of them, [thanks to our awesome sponsors][sponsors]! ## What's in it for me? ```python exec="1" session="insiders" data_source = "docs/insiders/goals.yml" ``` ```python exec="1" session="insiders" idprefix="" --8<-- "scripts/insiders.py" if unreleased_features: print( "The moment you [become a sponsor](#how-to-become-a-sponsor), you'll get **immediate " f"access to {len(unreleased_features)} additional features** that you can start using right away, and " "which are currently exclusively available to sponsors:\n" ) for feature in unreleased_features: feature.render(badge=True) print( "\n\nThese are just the features related to this project. " "[See the complete feature list on the author's main Insiders page](https://pawamoy.github.io/insiders/#whats-in-it-for-me)." ) else: print( "The moment you [become a sponsor](#how-to-become-a-sponsor), you'll get immediate " "access to all released features that you can start using right away, and " "which are exclusively available to sponsors. At this moment, there are no " "Insiders features for this project, but checkout the [next funding goals](#goals) " "to see what's coming, as well as **[the feature list for all Insiders projects](https://pawamoy.github.io/insiders/#whats-in-it-for-me).**" ) ``` Additionally, your sponsorship will give more weight to your upvotes on issues, helping us prioritize work items in our backlog. For more information on how we prioritize work, see this page: [Backlog management][backlog]. ## How to become a sponsor Thanks for your interest in sponsoring! In order to become an eligible sponsor with your GitHub account, visit [pawamoy's sponsor profile][github sponsor profile], and complete a sponsorship of **$10 a month or more**. You can use your individual or organization GitHub account for sponsoring. Sponsorships lower than $10 a month are also very much appreciated, and useful. They won't grant you access to Insiders, but they will be counted towards reaching sponsorship goals. Every sponsorship helps us implementing new features and releasing them to the public. **Important:** By default, when you're sponsoring **[@pawamoy][github sponsor profile]** through a GitHub organization, all the publicly visible members of the organization will be invited to join our private repositories. If you wish to only grant access to a subset of users, please send a short email to insiders@pawamoy.fr with the name of your organization and the GitHub accounts of the users that should be granted access. **Tip:** to ensure that access is not tied to a particular individual GitHub account, you can create a bot account (i.e. a GitHub account that is not tied to a specific individual), and use this account for the sponsoring. After being granted access to our private repositories, the bot account can create private forks of our private repositories into your own organization, which all members of your organization will have access to. You can cancel your sponsorship anytime.[^5] [^5]: If you cancel your sponsorship, GitHub schedules a cancellation request which will become effective at the end of the billing cycle. This means that even though you cancel your sponsorship, you will keep your access to Insiders as long as your cancellation isn't effective. All charges are processed by GitHub through Stripe. As we don't receive any information regarding your payment, and GitHub doesn't offer refunds, sponsorships are non-refundable. [:octicons-heart-fill-24:{ .pulse }   Join our awesome sponsors][github sponsor profile]{ .md-button .md-button--primary }

    If you sponsor publicly, you're automatically added here with a link to your profile and avatar to show your support for *Markdown Exec*. Alternatively, if you wish to keep your sponsorship private, you'll be a silent +1. You can select visibility during checkout and change it afterwards. ## Funding ### Goals The following section lists all funding goals. Each goal contains a list of features prefixed with a checkmark symbol, denoting whether a feature is :octicons-check-circle-fill-24:{ style="color: #00e676" } already available or :octicons-check-circle-fill-24:{ style="color: var(--md-default-fg-color--lightest)" } planned, but not yet implemented. When the funding goal is hit, the features are released for general availability. ```python exec="1" session="insiders" idprefix="" for goal in goals.values(): if not goal.complete: goal.render() ``` ### Goals completed This section lists all funding goals that were previously completed, which means that those features were part of Insiders, but are now generally available and can be used by all users. ```python exec="1" session="insiders" idprefix="" for goal in goals.values(): if goal.complete: goal.render() ``` ## Frequently asked questions ### Compatibility > We're building an open source project and want to allow outside collaborators to use *Markdown Exec* locally without having access to Insiders. Is this still possible? Yes. Insiders is compatible with *Markdown Exec*. Almost all new features and configuration options are either backward-compatible or implemented behind feature flags. Most Insiders features enhance the overall experience, though while these features add value for the users of your project, they shouldn't be necessary for previewing when making changes to content. ### Payment > We don't want to pay for sponsorship every month. Are there any other options? Yes. You can sponsor on a yearly basis by [switching your GitHub account to a yearly billing cycle][billing cycle]. If for some reason you cannot do that, you could also create a dedicated GitHub account with a yearly billing cycle, which you only use for sponsoring (some sponsors already do that). If you have any problems or further questions, please reach out to insiders@pawamoy.fr. ### Terms > Are we allowed to use Insiders under the same terms and conditions as *Markdown Exec*? Yes. Whether you're an individual or a company, you may use *Markdown Exec Insiders* precisely under the same terms as *Markdown Exec*, which are given by the [ISC license][license]. However, we kindly ask you to respect our **fair use policy**: - Please **don't distribute the source code** of Insiders. You may freely use it for public, private or commercial projects, privately fork or mirror it, but please don't make the source code public, as it would counteract the sponsorware strategy. - If you cancel your subscription, your access to the private repository is revoked, and you will miss out on all future updates of Insiders. However, you may **use the latest version** that's available to you **as long as you like**. Just remember that [GitHub deletes private forks][private forks]. [backlog]: https://pawamoy.github.io/backlog/ [insiders]: #what-is-insiders [sponsorship]: #what-sponsorships-achieve [sponsors]: #how-to-become-a-sponsor [features]: #whats-in-it-for-me [funding]: #funding [goals completed]: #goals-completed [github sponsor profile]: https://github.com/sponsors/pawamoy [billing cycle]: https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/changing-the-duration-of-your-billing-cycle [license]: ../license.md [private forks]: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/removing-a-collaborator-from-a-personal-repository markdown-exec-1.10.2/docs/insiders/installation.md000066400000000000000000000055041476636417300221570ustar00rootroot00000000000000--- title: Getting started with Insiders --- # Getting started with Insiders *Markdown Exec Insiders* is a compatible drop-in replacement for *Markdown Exec*, and can be installed similarly using `pip` or `git`. Note that in order to access the Insiders repository, you need to [become an eligible sponsor][] of @pawamoy on GitHub. ## Installation ### with the `insiders` tool [`insiders`][insiders-tool] is a tool that helps you keep up-to-date versions of Insiders projects in the PyPI index of your choice (self-hosted, Google registry, Artifactory, etc.). **We kindly ask that you do not upload the distributions to public registries, as it is against our [Terms of use][].** ### with pip (ssh/https) *Markdown Exec Insiders* can be installed with `pip` [using SSH][install-pip-ssh]: ```bash pip install git+ssh://git@github.com/pawamoy-insiders/markdown-exec.git ``` Or using HTTPS: ```bash pip install git+https://${GH_TOKEN}@github.com/pawamoy-insiders/markdown-exec.git ``` >? NOTE: **How to get a GitHub personal access token?** The `GH_TOKEN` environment variable is a GitHub token. It can be obtained by creating a [personal access token][github-pat] for your GitHub account. It will give you access to the Insiders repository, programmatically, from the command line or GitHub Actions workflows: > > 1. Go to https://github.com/settings/tokens > 2. Click on [Generate a new token][github-pat-new] > 3. Enter a name and select the [`repo`][scopes] scope > 4. Generate the token and store it in a safe place > > Note that the personal access token must be kept secret at all times, as it allows the owner to access your private repositories. ### with Git Of course, you can use *Markdown Exec Insiders* directly using Git: ``` git clone git@github.com:pawamoy-insiders/markdown-exec ``` When cloning with Git, the package must be installed: ``` pip install -e markdown-exec ``` ## Upgrading When upgrading Insiders, you should always check the version of *Markdown Exec* which makes up the first part of the version qualifier. For example, a version like `8.x.x.4.x.x` means that Insiders `4.x.x` is currently based on `8.x.x`. If the major version increased, it's a good idea to consult the [changelog][] and go through the steps to ensure your configuration is up to date and all necessary changes have been made. [become an eligible sponsor]: ./index.md#how-to-become-a-sponsor [changelog]: ./changelog.md [github-pat]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token [github-pat-new]: https://github.com/settings/tokens/new [insiders-tool]: https://pawamoy.github.io/insiders-project/ [install-pip-ssh]: https://docs.github.com/en/authentication/connecting-to-github-with-ssh [scopes]: https://docs.github.com/en/developers/apps/scopes-for-oauth-apps#available-scopes [terms of use]: ./index.md#terms markdown-exec-1.10.2/docs/js/000077500000000000000000000000001476636417300157245ustar00rootroot00000000000000markdown-exec-1.10.2/docs/js/feedback.js000066400000000000000000000007751476636417300200170ustar00rootroot00000000000000const feedback = document.forms.feedback; feedback.hidden = false; feedback.addEventListener("submit", function(ev) { ev.preventDefault(); const commentElement = document.getElementById("feedback"); commentElement.style.display = "block"; feedback.firstElementChild.disabled = true; const data = ev.submitter.getAttribute("data-md-value"); const note = feedback.querySelector(".md-feedback__note [data-md-value='" + data + "']"); if (note) { note.hidden = false; } }) markdown-exec-1.10.2/docs/js/insiders.js000066400000000000000000000054731476636417300201130ustar00rootroot00000000000000function humanReadableAmount(amount) { const strAmount = String(amount); if (strAmount.length >= 4) { return `${strAmount.slice(0, strAmount.length - 3)},${strAmount.slice(-3)}`; } return strAmount; } function getJSON(url, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'json'; xhr.onload = function () { var status = xhr.status; if (status === 200) { callback(null, xhr.response); } else { callback(status, xhr.response); } }; xhr.send(); } function updatePremiumSponsors(dataURL, rank) { let capRank = rank.charAt(0).toUpperCase() + rank.slice(1); getJSON(dataURL + `/sponsors${capRank}.json`, function (err, sponsors) { const sponsorsDiv = document.getElementById(`${rank}-sponsors`); if (sponsors.length > 0) { let html = ''; html += `${capRank} sponsors

    ` sponsors.forEach(function (sponsor) { html += ` ${sponsor.name} ` }); html += '

    ' sponsorsDiv.innerHTML = html; } }); } function updateInsidersPage(author_username) { const sponsorURL = `https://github.com/sponsors/${author_username}` const dataURL = `https://raw.githubusercontent.com/${author_username}/sponsors/main`; getJSON(dataURL + '/numbers.json', function (err, numbers) { document.getElementById('sponsors-count').innerHTML = numbers.count; Array.from(document.getElementsByClassName('sponsors-total')).forEach(function (element) { element.innerHTML = '$ ' + humanReadableAmount(numbers.total); }); getJSON(dataURL + '/sponsors.json', function (err, sponsors) { const sponsorsElem = document.getElementById('sponsors'); const privateSponsors = numbers.count - sponsors.length; sponsors.forEach(function (sponsor) { sponsorsElem.innerHTML += ` `; }); if (privateSponsors > 0) { sponsorsElem.innerHTML += ` +${privateSponsors} `; } }); }); updatePremiumSponsors(dataURL, "gold"); updatePremiumSponsors(dataURL, "silver"); updatePremiumSponsors(dataURL, "bronze"); } markdown-exec-1.10.2/docs/license.md000066400000000000000000000001151476636417300172510ustar00rootroot00000000000000--- title: License hide: - feedback --- # License ``` --8<-- "LICENSE" ``` markdown-exec-1.10.2/docs/reference/000077500000000000000000000000001476636417300172465ustar00rootroot00000000000000markdown-exec-1.10.2/docs/reference/api.md000066400000000000000000000001601476636417300203360ustar00rootroot00000000000000--- title: API reference hide: - navigation --- # ::: markdown_exec options: show_submodules: true markdown-exec-1.10.2/docs/schema.json000066400000000000000000000014701476636417300174450ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft-07/schema", "title": "Utilities to execute code blocks in Markdown files.nto your site.", "oneOf": [ { "markdownDescription": "https://pawamoy.github.io/markdown-exec", "enum": [ "markdown-exec" ] }, { "type": "object", "properties": { "markdown-exec": { "markdownDescription": "https://pawamoy.github.io/markdown-exec", "type": "object", "properties": { "languages": { "title": "The languages to enabled execution for.", "type": "array", "items": { "type": "string" } } }, "additionalProperties": false } }, "additionalProperties": false } ] }markdown-exec-1.10.2/docs/snippets/000077500000000000000000000000001476636417300171555ustar00rootroot00000000000000markdown-exec-1.10.2/docs/snippets/gallery/000077500000000000000000000000001476636417300206145ustar00rootroot00000000000000markdown-exec-1.10.2/docs/snippets/gallery/ansi.sh000066400000000000000000000010521476636417300221000ustar00rootroot00000000000000#!/bin/bash # credits to https://github.com/42picky/42picky.github.io text="xYz" # Some test text echo -e "\n 40m 41m 42m 43m 44m 45m 46m 47m" for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \ '1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \ ' 36m' '1;36m' ' 37m' '1;37m'; do FG=${FGs// /} echo -en " $FGs \033[$FG ${text} " for BG in 40m 41m 42m 43m 44m 45m 46m 47m; do echo -en "$EINS \033[$FG\033[${BG} ${text} \033[0m" done echo done echomarkdown-exec-1.10.2/docs/snippets/gallery/argparse.py000066400000000000000000000011321476636417300227670ustar00rootroot00000000000000import argparse from duty.cli import get_parser parser = get_parser() lines = [] lines.append(f"## duty") if parser.description: lines.append(parser.description) lines.append("\nOptions:\n") for action in parser._actions: opts = [f"`{opt}`" for opt in action.option_strings] if not opts: continue line = "- " + ",".join(opts) if action.metavar: line += f" `{action.metavar}`" line += f": {action.help}" if action.default and action.default != argparse.SUPPRESS: line += f"(default: {action.default})" lines.append(line) print("\n".join(lines)) markdown-exec-1.10.2/docs/snippets/gallery/argparse_format.py000066400000000000000000000001421476636417300243370ustar00rootroot00000000000000from duty.cli import get_parser parser = get_parser() print(f"```\n{parser.format_help()}\n```") markdown-exec-1.10.2/docs/snippets/gallery/chalk.py000066400000000000000000000011751476636417300222540ustar00rootroot00000000000000from tempfile import NamedTemporaryFile from chalk import Diagram, triangle, unit_x from colour import Color papaya = Color("#ff9700") def sierpinski(n: int, size: int) -> Diagram: if n <= 1: return triangle(size) else: smaller = sierpinski(n - 1, size / 2) return smaller.above(smaller.beside(smaller, unit_x).center_xy()) d = sierpinski(5, 4).fill_color(papaya) # Chalk doesn't provide an easy method to get a string directly, # so we use a temporary file. with NamedTemporaryFile("w+") as tmpfile: d.render_svg(tmpfile.name, height=256) tmpfile.seek(0) svg = tmpfile.read() print(svg) markdown-exec-1.10.2/docs/snippets/gallery/d2.py000066400000000000000000000023661476636417300215020ustar00rootroot00000000000000import os # markdown-exec: hide if "CI" in os.environ: # markdown-exec: hide print("D2 is not installed in CI, skipping this gallery example.") # markdown-exec: hide raise SystemExit(0) # markdown-exec: hide import subprocess diagram = """ direction: right Before and after becoming friends: { 2007: Office chatter in 2007 { shape: sequence_diagram alice: Alice bob: Bobby awkward small talk: { alice -> bob: uhm, hi bob -> alice: oh, hello icebreaker attempt: { alice -> bob: what did you have for lunch? } unfortunate outcome: { bob -> alice: that's personal } } } 2012: Office chatter in 2012 { shape: sequence_diagram alice: Alice bob: Bobby alice -> bob: Want to play with ChatGPT? bob -> alice: Yes! bob -> alice.play: Write a play... alice.play -> bob.play: about 2 friends... bob.play -> alice.play: who find love... alice.play -> bob.play: in a sequence diagram } 2007 -> 2012: Five\nyears\nlater } """ # We simply run `d2` in a subprocess, passing it our diagram as input and capturing its output to print it. svg = subprocess.check_output(["d2", "-", "-"], input=diagram, stderr=subprocess.DEVNULL, text=True) print(svg) markdown-exec-1.10.2/docs/snippets/gallery/diagrams.py000066400000000000000000000024441476636417300227610ustar00rootroot00000000000000from base64 import b64encode from contextlib import suppress from diagrams import Diagram from diagrams.k8s.clusterconfig import HPA from diagrams.k8s.compute import Deployment, Pod, ReplicaSet from diagrams.k8s.network import Ingress, Service # By default, Diagrams tries to write the result on disk, so we prevent that by patching its `render` method, # and by ignoring the `FileNotFoundError` that ensues. # # Then we use its internal `dot` object and its `pipe` method to store the diagram in a variable, # as base64 encoded PNG data. # # Finally we output an HTML image with the base64 data. # Using SVG is not possible here since Diagrams embeds actual, smaller PNG files in the result, # files which are not automatically added to the final site. with suppress(FileNotFoundError): with Diagram("Exposed Pod with 3 Replicas", show=False) as diagram: diagram.render = lambda: None net = Ingress("domain.com") >> Service("svc") net >> [Pod("pod1"), Pod("pod2"), Pod("pod3")] << ReplicaSet("rs") << Deployment("dp") << HPA("hpa") png = b64encode(diagram.dot.pipe(format="png")).decode() # Wrapping the image in a div prevents it from being wrapped in a paragraph, # which would add unnecessary space around it. print(f'
    ') markdown-exec-1.10.2/docs/snippets/gallery/drawsvg.py000066400000000000000000000025241476636417300226460ustar00rootroot00000000000000import drawsvg as draw d = draw.Drawing(200, 200, origin='center') # Animate the position and color of circle c = draw.Circle(0, 0, 20, fill='red') # See for supported attributes: # https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate c.append_anim(draw.Animate('cy', '6s', '-80;80;-80', repeatCount='indefinite')) c.append_anim(draw.Animate('cx', '6s', '0;80;0;-80;0', repeatCount='indefinite')) c.append_anim(draw.Animate('fill', '6s', 'red;green;blue;yellow', calc_mode='discrete', repeatCount='indefinite')) d.append(c) # Animate a black circle around an ellipse ellipse = draw.Path() ellipse.M(-90, 0) ellipse.A(90, 40, 360, True, True, 90, 0) # Ellipse path ellipse.A(90, 40, 360, True, True, -90, 0) ellipse.Z() c2 = draw.Circle(0, 0, 10) # See for supported attributes: # https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate_motion c2.append_anim(draw.AnimateMotion(ellipse, '3s', repeatCount='indefinite')) # See for supported attributes: # https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate_transform c2.append_anim(draw.AnimateTransform('scale', '3s', '1,2;2,1;1,2;2,1;1,2', repeatCount='indefinite')) d.append(c2) print(d.as_svg())markdown-exec-1.10.2/docs/snippets/gallery/expandable_filetree.py000066400000000000000000000025441476636417300251550ustar00rootroot00000000000000from fnmatch import fnmatch from pathlib import Path exclude = {"dist", "*cache*", ".devbox", ".hypothesis", ".pdm*", ".coverage*", "profile.*"} no_recurse = {".venv*", "site", "htmlcov", ".git"} def exptree(path: str, session: str) -> None: # List files and directories separately. files = [] dirs = [] for node in Path(path).iterdir(): if any(fnmatch(node.name, pattern) for pattern in exclude): continue if node.is_dir(): dirs.append(node) else: files.append(node) # Print directories first, then files (both sorted). recurse = [] print("```tree") for directory in sorted(dirs): if any(fnmatch(directory.name, pattern) for pattern in no_recurse): print(f"{directory.name}/") else: recurse.append(directory.name) # Add code annotation at the end. print(f"{directory.name}/ # ({len(recurse)})!") for file in sorted(files): print(file.name) print("```\n") # Print contents of each annotated directory. for index, directory in enumerate(recurse, 1): new_path = f"{path}/{directory}" print(f"{index}. \n") # The recursive part! print(f' ```python exec="1" session="{session}"') print(f' exptree("{new_path}", "{session}")') print(" ```\n") markdown-exec-1.10.2/docs/snippets/gallery/hyperbolic.py000066400000000000000000000033711476636417300233320ustar00rootroot00000000000000import math from drawsvg import Drawing from hyperbolic.poincare import * from hyperbolic.poincare.util import triangle_side_for_angles import hyperbolic.tiles as htiles p1 = 4 p2 = 3 q = 3 rotate = 0 theta1, theta2 = math.pi*2/p1, math.pi*2/p2 phi_sum = math.pi*2/q r1 = triangle_side_for_angles(theta1/2, phi_sum/2, theta2/2) r2 = triangle_side_for_angles(theta2/2, phi_sum/2, theta1/2) t_gen1 = htiles.TileGen.make_regular(p1, hr=r1, skip=1) t_gen2 = htiles.TileGen.make_regular(p2, hr=r2, skip=1) t_layout = htiles.TileLayout() t_layout.add_generator(t_gen1, (1,)*p1) t_layout.add_generator(t_gen2, (0,)*p2, htiles.TileDecoratorNull()) start_tile = t_layout.default_start_tile(rotate_deg=rotate) t1 = start_tile t2 = t_layout.place_tile(t1.sides[-1]) t3 = t_layout.place_tile(t2.sides[-1]) point_base = t3.vertices[-1] points = [Transform.rotation(deg=-i*360/p1).apply_to_point(point_base) for i in range(p1)] vertices = start_tile.vertices edges = [] for i, point in enumerate(points): v1 = vertices[i] v2 = vertices[(i+1)%p1] edge = Hypercycle.from_points(*v1, *v2, *point, segment=True, exclude_mid=True) edges.append(edge) decorate_poly = Polygon(edges=edges, vertices=vertices) decorator1 = htiles.TileDecoratorPolygons(decorate_poly) t_layout.set_decorator(decorator1, 0) start_tile = t_layout.default_start_tile(rotate_deg=rotate) tiles = t_layout.tile_plane(start_tile, depth=6) d = Drawing(2, 2, origin='center') #d.draw(euclid.Circle(0, 0, 1), fill='silver') for tile in tiles: d.draw(tile, hwidth=0.02, fill='red') tiles[0].decorator = None d.draw( Hypercycle.from_points( *tiles[0].vertices[0], *tiles[0].vertices[1], *point_base ), hwidth=0.02, fill='black', ) d.set_render_size(w=400) print(d.as_svg()) markdown-exec-1.10.2/docs/snippets/gallery/matplotlib.py000066400000000000000000000021761476636417300233430ustar00rootroot00000000000000# https://matplotlib.org/stable/gallery/lines_bars_and_markers/scatter_demo2.html from io import StringIO import matplotlib.cbook as cbook import matplotlib.pyplot as plt import numpy as np # Load a numpy record array from yahoo csv data with fields date, open, close, # volume, adj_close from the mpl-data/example directory. The record array # stores the date as an np.datetime64 with a day unit ('D') in the date column. price_data = cbook.get_sample_data("goog.npz")["price_data"] price_data = price_data[-250:] # get the most recent 250 trading days delta1 = np.diff(price_data["adj_close"]) / price_data["adj_close"][:-1] # Marker size in units of points^2 volume = (15 * price_data["volume"][:-2] / price_data["volume"][0])**2 close = 0.003 * price_data["close"][:-2] / 0.003 * price_data["open"][:-2] fig, ax = plt.subplots() ax.scatter(delta1[:-1], delta1[1:], c=close, s=volume, alpha=0.5) ax.set_xlabel(r"$\Delta_i$", fontsize=15) ax.set_ylabel(r"$\Delta_{i+1}$", fontsize=15) ax.set_title("Volume and percent change") ax.grid(True) fig.tight_layout() buffer = StringIO() plt.savefig(buffer, format="svg") print(buffer.getvalue()) markdown-exec-1.10.2/docs/snippets/gallery/plotly.py000066400000000000000000000002061476636417300225070ustar00rootroot00000000000000import plotly.express as px fig = px.bar(x=["a", "b", "c"], y=[1, 3, 2]) print(fig.to_html(full_html=False, include_plotlyjs="cdn")) markdown-exec-1.10.2/docs/snippets/gallery/pydeps.py000066400000000000000000000032241476636417300224730ustar00rootroot00000000000000from pydeps import cli, colors, dot, py2depgraph from pydeps.pydeps import depgraph_to_dotsrc from pydeps.target import Target # Note: pydeps wasn't designed to be used in such a programatic way, so the code is a bit convoluted, # but you could make a function of it, put it in an importable script/module, # and reuse it cleanly in your executed code blocks. cli.verbose = cli._not_verbose options = cli.parse_args(["src/markdown_exec", "--noshow"]) colors.START_COLOR = options["start_color"] target = Target(options["fname"]) with target.chdir_work(): dep_graph = py2depgraph.py2dep(target, **options) dot_src = depgraph_to_dotsrc(target, dep_graph, **options) svg = dot.call_graphviz_dot(dot_src, "svg").decode() svg = "".join(svg.splitlines()[6:]) svg = svg.replace('fill="white"', 'fill="transparent"') reference = "../reference" modules = ( "markdown_exec", "markdown_exec.formatters", "markdown_exec.formatters.base", "markdown_exec.formatters.bash", "markdown_exec.formatters.console", "markdown_exec.formatters.markdown", "markdown_exec.formatters.pycon", "markdown_exec.formatters.pyodide", "markdown_exec.formatters.python", "markdown_exec.formatters.sh", "markdown_exec.formatters.tree", "markdown_exec.logger", "markdown_exec.mkdocs_plugin", "markdown_exec.processors", "markdown_exec.rendering", ) for module in modules: svg_title = module.replace(".", "_") title_tag = f"{svg_title}" href = f"{reference}/{module.replace('.', '/')}/" svg = svg.replace(title_tag, f'{module}') svg = svg.replace("", "") print(svg) markdown-exec-1.10.2/docs/snippets/gallery/qrcode.py000066400000000000000000000003371476636417300224460ustar00rootroot00000000000000import qrcode from qrcode.image.svg import SvgPathImage img = qrcode.make("https://github.com/sponsors/lincolnloop", box_size=20, border=2, image_factory=SvgPathImage) print(f'
    {img.to_string().decode("utf8")}
    ') markdown-exec-1.10.2/docs/snippets/gallery/rich.py000066400000000000000000000024671476636417300221240ustar00rootroot00000000000000import os from rich.console import Console from rich.padding import Padding from rich.syntax import Syntax # Here we hardcode the code snippet we want to render, # but we could instead include it from somewhere else using the `pymdownx.snippets` extension # (https://facelessuser.github.io/pymdown-extensions/extensions/snippets/) # or by reading it dynamically from Python. code = """ from contextlib import asynccontextmanager import httpx class BookClient(httpx.AsyncClient): async def get_book(self, book_id: int) -> str: response = await self.get(f"/books/{book_id}") return response.text @asynccontextmanager async def book_client(*args, **kwargs): async with BookClient(*args, **kwargs) as client: yield client """ # We prevent Rich from actually writing to the terminal. with open(os.devnull, "w") as devnull: console = Console(record=True, width=65, file=devnull, markup=False) renderable = Syntax(code, "python", theme="material") renderable = Padding(renderable, (0,), expand=False) console.print(renderable, markup=False) svg = console.export_svg(title="async context manager") # Wrapping the SVG in a div prevents it from being wrapped in a paragraph, # which would add unnecessary space around it. print(f"
    {svg}
    ") markdown-exec-1.10.2/docs/snippets/gallery/rich_terminal.py000066400000000000000000000061721476636417300240140ustar00rootroot00000000000000import os from rich.console import Console report = """$ griffe check griffe -ssrc -b0.24.0 -a0.23.0 [bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter default was changed[/]: True -> None [bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter default was changed[/]: True -> None [bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]max_iterations[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter was removed[/] [bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter was removed[/] [bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]commit[/]): [#afaf72]Parameter was removed[/] [bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]repo[/]): [#afaf72]Positional parameter was moved[/]: position: from 2 to 1 (-1) [bold]src/griffe/git.py[/]:75: load_git([#7faeff]commit[/]): [#afaf72]Parameter was removed[/] [bold]src/griffe/git.py[/]:75: load_git([#7faeff]repo[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]submodules[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]try_relative_path[/]): [#afaf72]Parameter was removed[/] [bold]src/griffe/git.py[/]:75: load_git([#7faeff]extensions[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]search_paths[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_parser[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_options[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]lines_collection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]modules_collection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only [bold]src/griffe/git.py[/]:75: load_git([#7faeff]allow_inspection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only """ with open(os.devnull, "w") as devnull: console = Console(record=True, width=150, file=devnull) console.print(report, markup=True, highlight=False) print(console.export_html(inline_styles=True, code_format="
    {code}
    ")) markdown-exec-1.10.2/docs/snippets/gallery/runpy.py000066400000000000000000000006761476636417300223540ustar00rootroot00000000000000import sys import warnings from contextlib import suppress from io import StringIO from runpy import run_module old_argv = list(sys.argv) sys.argv = ["mkdocs"] old_stdout = sys.stdout sys.stdout = StringIO() warnings.filterwarnings("ignore", category=RuntimeWarning) with suppress(SystemExit): run_module("mkdocs", run_name="__main__") output = sys.stdout.getvalue() sys.stdout = old_stdout sys.argv = old_argv print(f"```\n{output}\n```") markdown-exec-1.10.2/docs/snippets/gallery/textual.py000066400000000000000000000011321476636417300226510ustar00rootroot00000000000000from textual.app import App, ComposeResult from textual.widgets import Static from textual._doc import take_svg_screenshot class TextApp(App): CSS = """ Screen { background: darkblue; color: white; layout: vertical; } Static { height: auto; padding: 2; border: heavy white; background: #ffffff 30%; content-align: center middle; } """ def compose(self) -> ComposeResult: yield Static("Hello") yield Static("[b]World![/b]") print(take_svg_screenshot(app=TextApp(), terminal_size=(80, 24))) markdown-exec-1.10.2/docs/snippets/usage/000077500000000000000000000000001476636417300202615ustar00rootroot00000000000000markdown-exec-1.10.2/docs/snippets/usage/boolean_matrix.py000066400000000000000000000002441476636417300236360ustar00rootroot00000000000000print() print("a | b | a \\|\\| b") print("--- | --- | ---") for a in (True, False): for b in (True, False): print(f"{a} | {b} | {a or b}") print() markdown-exec-1.10.2/docs/snippets/usage/hide.py000066400000000000000000000000731476636417300215440ustar00rootroot00000000000000print("Hello World!") print("
    ") # markdown-exec: hide markdown-exec-1.10.2/docs/snippets/usage/multiple.pycon000066400000000000000000000001141476636417300231620ustar00rootroot00000000000000>>> name = "Baron" >>> print(name) Baron >>> age = "???" >>> print(age) ??? markdown-exec-1.10.2/docs/snippets/usage/platform_html.py000066400000000000000000000004361476636417300235060ustar00rootroot00000000000000import platform print( f""" """ ) markdown-exec-1.10.2/docs/snippets/usage/platform_md.py000066400000000000000000000005301476636417300231350ustar00rootroot00000000000000import platform from textwrap import dedent print( # we must dedent, otherwise Markdown # will render it as a code block! dedent( f""" - machine: `{platform.machine()}` - version: `{platform.version()}` - platform: `{platform.platform()}` - system: `{platform.system()}` """ ) ) markdown-exec-1.10.2/docs/snippets/usage/source.py000066400000000000000000000000311476636417300221250ustar00rootroot00000000000000print("I'm the result!") markdown-exec-1.10.2/docs/snippets/usage/source.pycon000066400000000000000000000000631476636417300226320ustar00rootroot00000000000000>>> print("I'm the result!") I'm not the result... markdown-exec-1.10.2/docs/usage/000077500000000000000000000000001476636417300164145ustar00rootroot00000000000000markdown-exec-1.10.2/docs/usage/index.md000066400000000000000000000340661476636417300200560ustar00rootroot00000000000000# Usage Once the extension is configured (see README/Overview), you can execute code blocks by enabling the `exec` option: ````md ```python exec="on" print("Hello Markdown!") ``` ```` The `exec` option will be true for every possible value except `0`, `no`, `off` and `false` (case insensitive). To enable automatic execution of code blocks for specific languages (without having to add the `exec="on"` option to your code blocks), set the `MARKDOWN_EXEC_AUTO` environment variable: ```bash MARKDOWN_EXEC_AUTO=python,bash ``` ## Options summary As the number of options grew over time, we now provide this summary listing every option, linking to their related documentation: - [`exec`](#usage): The mother of all other options, enabling code execution. - [`html`](#html-vs-markdown): Whether the output is alredady HTML, or needs to be converted from Markdown to HTML. - [`id`](#handling-errors): Give an identifier to your code blocks to help [debugging errors](#handling-errors), or to [prefix HTML ids](#html-ids). - [`idprefix`](#html-ids): Change or remove the prefix in front of HTML ids/hrefs. - [`result`](#wrap-result-in-a-code-block): Choose the syntax highlight of your code block output. - [`returncode`](shell.md#expecting-a-non-zero-exit-code): Tell what return code is expected (shell code). - [`session`](#sessions): Execute code blocks within a named session, reusing previously defined variables, etc.. - [`source`](#render-the-source-code-as-well): Render the source as well as the output. - [`tabs`](#change-the-titles-of-tabs): When rendering the source using tabs, choose the tabs titles. - [`width`](#change-the-console-width): Change the console width through the `COLUMNS` environment variable. - [`workdir`](#change-the-working-directory): Change the working directory. - [`title`](#additional-options): Title is a [Material for MkDocs][material] option. - [`updatetoc`](#generated-headings-in-table-of-contents): Whether to update the Table of Contents with generated headings. ## HTML vs. Markdown By default, Markdown Exec will render what you print as Markdown. If you want to skip rendering, to inject HTML directly, you can set the `html` option to true. HTML Example: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" System information: ```python exec="true" html="true" --8<-- "usage/platform_html.py" ``` ```` Markdown Example: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" System information: ```python exec="true" --8<-- "usage/platform_md.py" ``` ```` ## Generated headings in Table of Contents If you are using Python Markdown's `toc` extension, or writing docs with MkDocs, you will notice that the headings you generated by executing a code block appear in the table of contents. If you don't want those headings to appear in the ToC, you can use the `updatetoc="no"` boolean option: ````md ```python exec="1" updatetoc="no" print("# XL heading\n") print("## L heading\n") print("### M heading\n") print("#### S heading\n") ``` ```` ## HTML ids When your executed code blocks output Markdown, this Markdown is rendered to HTML, and every HTML id is automatically prefixed with `exec-N--`, where N is an integer incremented with each code block. To avoid breaking links, every `href` attribute is also updated when relevant. You can change this prefix, or completely remove it with the `idprefix` option. The following ids are not prefixed: ````md exec="1" source="material-block" ```python exec="1" idprefix="" updatetoc="no" print("#### Commands") print("\n[link to commands](#commands)") ``` ```` The following ids are prefixed with `cli-`: ````md exec="1" source="material-block" ```python exec="1" idprefix="cli-" updatetoc="no" print("#### Commands") print("\n[link to commands](#commands)") ``` ```` If `idprefix` is not specified, and `id` is specified, then the id is used as prefix: The following ids are prefixed with `super-cli-`: ````md exec="1" source="material-block" ```python exec="1" id="super-cli" updatetoc="no" print("#### Commands") print("\n[link to commands](#commands)") ``` ```` ## Render the source code as well It's possible to render both the result of the executed code block *and* the code block itself. For this, use the `source` option with one of the following values: - `above`: The source code will be rendered above the result. - `below`: The source code will be rendered below the result. - `material-block`: The source code and result will be wrapped in a nice-looking block (only works with [Material for MkDocs][material], and requires the [`md_in_html`][md_in_html] extension) - `tabbed-left`: The source code and result will be rendered in tabs, in that order (requires the [`pymdownx.tabbed`][pymdownx.tabbed] extension). - `tabbed-right`: The result and source code will be rendered in tabs, in that order (requires the [`pymdownx.tabbed`][pymdownx.tabbed] extension). - `console`: The source and result are concatenated in a single code block, like an interactive console session. **Source above:** ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="true" source="above" --8<-- "usage/source.py" ``` ```` --- **Source below:** ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="true" source="below" --8<-- "usage/source.py" ``` ```` --- **Material block:** ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="true" source="material-block" --8<-- "usage/source.py" ``` ```` NOTE: **Important:** The `material-block` source option requires that you enable the [`md_in_html`][md_in_html] Markdown extension. --- **Tabbed on the left:** ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="true" source="tabbed-left" --8<-- "usage/source.py" ``` ```` NOTE: **Important:** The `tabbed-left` source option requires that you enable the [`pymdownx.tabbed`][pymdownx.tabbed] Markdown extension. --- **Tabbed on the right:** ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="true" source="tabbed-right" --8<-- "usage/source.py" ``` ```` NOTE: **Important:** The `tabbed-left` source option requires that you enable the [`pymdownx.tabbed`][pymdownx.tabbed] Markdown extension. --- **Console** (best used with actual session syntax like [`pycon`](python.md#python-console-code) or [`console`](shell.md#console)): ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```pycon exec="true" source="console" --8<-- "usage/source.pycon" ``` ```` [md_in_html]: https://python-markdown.github.io/extensions/md_in_html/ [pymdownx.tabbed]: https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/ ## Hiding lines from the source Every line that contains the string `markdown-exec: hide` will be hidden from the displayed source. === "Markdown" ````md ```python exec="true" source="above" --8<-- "usage/hide.py" ``` ```` === "Rendered" ```python exec="true" source="above" --8<-- "usage/hide.py" ``` ## Change the titles of tabs In the previous example, we didn't specify any title for tabs, so Markdown Exec used "Source" and "Result" by default. You can customize the titles with the `tabs` option: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="1" source="tabbed-left" tabs="Source code|Output" --8<-- "usage/source.py" ``` ```` As you can see, titles are separated with a pipe `|`. Both titles are stripped so you can add space around the pipe. If you need to use that character in a title, simply escape it with `\|`: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="1" source="tabbed-left" tabs="OR operator: a \|\| b | Boolean matrix" --8<-- "usage/boolean_matrix.py" ``` ```` IMPORTANT: The `tabs` option ***always*** expects the "Source" tab title first, and the "Result" tab title second. It allows to switch from tabbed-left to tabbed-right and inversely without having to switch the titles as well. WARNING: **Limitation:** Changing the title for only one tab is not supported. ## Wrap result in a code block You can wrap the result in a code block by specifying a code block language: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```console exec="1" result="ini" $ cat .git/config ``` ```` WARNING: **Limitation:** Wrapping the result is not possible when HTML output is enabled. ## Change the console width To change the console width for the execution of a code block, use the `width` option. Internally, Markdown Exec will set the `COLUMNS` environment variable accordingly, and restore its previous value after execution. If the executed code doesn't support this environment variable, the default console width will be used (it could be the current width or some arbitrary value). ````md exec="1" source="tabbed-left" ```bash exec="1" width="10" echo $COLUMNS ``` ```bash exec="1" width="1000" echo $COLUMNS ``` ```` ## Change the working directory To change the working directory for the execution of a code block, use the `workdir` option. ````md exec="1" source="tabbed-left" ```bash exec="1" pwd ``` ```bash exec="1" workdir=".." pwd ``` ```` ## Additional options If you are using [Material for MkDocs][material], you are probably familiar with the `title` option on code blocks: ````md ```python title="setup.py" from setuptools import setup setup(...) ``` ```` Markdown Exec will add back these unrecognized options when rendering the source, so you can keep using them normally. Example: ````md exec="1" source="tabbed-left" tabs="Markdown|Rendered" ```python exec="1" source="above" title="source.py" --8<-- "usage/source.py" ``` ```` ## Handling errors Code blocks execution can fail. For example, your Python code may raise exceptions, or your shell code may return a non-zero exit code (for shell commands that are expected to return non-zero, see [Expecting a non-zero exit code](shell.md#expecting-a-non-zero-exit-code)). In these cases, the exception and traceback (Python), or the current output (shell) will be rendered instead of the result, and a warning will be logged. Example of failing code: ````md ```python exec="true" print("hello") assert 1 + 1 == 11 ``` ```` ```text title="MkDocs output" WARNING - markdown_exec: Execution of python code block exited with errors ``` ```python title="Rendered traceback" Traceback (most recent call last): File "/path/to/markdown_exec/formatters/python.py", line 23, in _run_python exec(code, exec_globals) # noqa: S102 File "", line 2, in assert 1 + 1 == 11 AssertionError ``` With many executed code blocks in your docs, it will be hard to know which code block failed exactly. To make it easier, you can set an ID on each code block with the `id` option, and this ID will be shown in the logs: ````md ```python exec="true" id="print hello" print("hello") assert 1 + 1 == 11 ``` ```` ```text title="MkDocs output" WARNING - markdown_exec: Execution of python code block 'print hello' exited with errors ``` > TIP: **Titles act as IDs as well!** > You *don't need* to provide an ID > if you already set a (Material for MkDocs) title: > > ````md > ```python exec="true" title="print world" > print("world") > assert 1 + 1 == 11 > ``` > ```` > > ```text title="MkDocs output" > WARNING - markdown_exec: Execution of python code block 'print world' exited with errors > ``` ## Sessions Markdown Exec makes it possible to persist state between executed code blocks. To persist state and reuse it in other code blocks, give a session name to your blocks: ````md exec="1" source="material-block" title="Sessions" ```python exec="1" session="greet" def greet(name): print(f"Hello {name}!") ``` Hello Mushu! ```python exec="1" session="greet" greet("Ping") ``` ```` WARNING: **Limitation:** Sessions only work with Python and Pycon syntax for now. ## Literate Markdown With this extension, it is also possible to write "literate programming" Markdown. From [Wikipedia](https://en.wikipedia.org/wiki/Literate_programming): > Literate programming (LP) tools are used to obtain two representations from a source file: one understandable by a compiler or interpreter, the "tangled" code, and another for viewing as formatted documentation, which is said to be "woven" from the literate source. We effectively support executing multiple *nested* code blocks to generate complex output. That makes for a very meta-markdown markup: ````md exec="1" source="tabbed-left" ```md exec="1" source="material-block" title="Markdown link" [Link to example.com](https://example.com) ``` ```` > TIP: **So power, such meta.** > The above example (both tabs) was entirely generated using *a literate code block in a literate code block* 🤯: > > `````md > ````md exec="1" source="tabbed-left" > ```md exec="1" source="material-block" title="Markdown link" > [Link to example.com](https://example.com) > ``` > ```` > ````` > > In fact, all the examples on this page were generated using this method! > Check out the source here: https://github.com/pawamoy/markdown-exec/blob/master/docs/usage/index.md > (click on "Raw" to see the code blocks execution options). Of course "executing" Markdown (or rather, making it "literate") only makes sense when the source is shown as well. ## MkDocs integration As seen in the [Configuration section](../index.md#configuration), Markdown Exec can be configured directly as a MkDocs plugin: ```yaml # mkdocs.yml plugins: - search - markdown-exec ``` When configured this way, it will set a `MKDOCS_CONFIG_DIR` environment variable that you can use in your code snippets to compute file paths as relative to the MkDocs configuration file directory, instead of relative to the current working directory. This will make it possible to use the `-f` option of MkDocs, to build the documentation from a different directory than the repository root. Example: ```python exec="1" source="material-block" import os config_dir = os.environ['MKDOCS_CONFIG_DIR'] # This will show my local path since I deploy docs from my machine: print(f"Configuration file directory: `{config_dir}`") ``` The environment variable will be restored to its previous value, if any, at the end of the build. [material]: https://squidfunk.github.io/mkdocs-material/markdown-exec-1.10.2/docs/usage/pyodide.md000066400000000000000000000114131476636417300203730ustar00rootroot00000000000000# Pyodide [:octicons-tag-24: Insiders 1.0.0](../insiders/changelog.md#1.0.0) This special `pyodide` fence uses [Pyodide](https://pyodide.org), [Ace](https://ace.c9.io/) and [Highlight.js](https://highlightjs.org/) to render an interactive Python editor. Everything runs on the client side. The first time Pyodide is loaded by the browser can be a bit long, but then it will be cached and the next time you load the page it will be much faster. Click the **:material-play: Run** button in the top-right corner, or hit ++ctrl+enter++ to run the code. You can install packages with Micropip: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide import micropip print("Installing cowsay...") await micropip.install("cowsay") print("done!") ``` ```` Then you can import and use the packages you installed: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide import cowsay cowsay.cow("Hello World") ``` ```` Packages installed with Micropip are cached by the browser as well, making future installations much faster. ## Pyodide version You can select a specific Pyodide version with the `version` option: ````md ```pyodide version="0.26.4" print("Hello.") ``` ```` NOTE: **All Pyodide blocks on the same page should use the same version!** ## Sessions Editors with the same session share the same `globals()` dictionary, so you can reuse variables, classes, imports, etc., from another editor within the same session. This is why you can import `cowsay` in this editor, given you actually installed it in the first. Sessions are ephemeral: everything is reset when reloading the page. This means you cannot persist sessions across multiple pages. Try refreshing your page and running the code of the second editor: you should get a ModuleNotFoundError. To use other sessions, simply pass the `session="name"` option to the code block: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide session="something" something = "hello" ``` ```` Now lets print it in another editor with the same session: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide session="something" print(something) ``` ```` And in another editor with the default session: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide print(something) ``` ```` ## Pre-installing packages In your own documentation pages, you might not want to add `import micropip; await micropip.install("your-package")` to every editor to show how to use your package. In this case, you can use the `install` option to pre-install packages. The option takes a list of comma-separated package distribution names: ````md exec="1" source="tabbed-right" tabs="Markdown|Rendered" ```pyodide install="griffe,dependenpy" import griffe import dependenpy print("OK!") ``` ```` ## Excluding assets When you add a Pyodide fence to a page, Markdown Exec will inject ` """ _template = """
    Editor (session: %(session)s)%(play_emoji)s Run
    %(initial_code)s
    Output%(clear_emoji)s Clear
    """ _counter = 0 def _format_pyodide(code: str, md: Markdown, session: str, extra: dict, **options: Any) -> str: # noqa: ARG001 global _counter # noqa: PLW0603 _counter += 1 version = extra.pop("version", "0.26.4").lstrip("v") install = extra.pop("install", "") install = install.split(",") if install else [] exclude_assets = extra.pop("assets", "1").lower() in {"0", "false", "no", "off"} theme = extra.pop("theme", "tomorrow,tomorrow_night") if "," not in theme: theme = f"{theme},{theme}" theme_light, theme_dark = theme.split(",") data = { "id_prefix": f"exec-{_counter}--", "initial_code": code, "install": install, "theme_light": theme_light.strip(), "theme_dark": theme_dark.strip(), "session": session or "default", "play_emoji": _play_emoji, "clear_emoji": _clear_emoji, } rendered = _template % data if exclude_assets: return rendered return _assets.format(version=version) + rendered markdown-exec-1.10.2/src/markdown_exec/_internal/formatters/python.py000066400000000000000000000062301476636417300260320ustar00rootroot00000000000000# Formatter for executing Python code. from __future__ import annotations import re import sys import traceback from collections import defaultdict from functools import partial from io import StringIO from types import ModuleType from typing import Any from markdown_exec._internal.formatters._exec_python import exec_python from markdown_exec._internal.formatters.base import ExecutionError, base_format from markdown_exec._internal.rendering import code_block _sessions_globals: dict[str, dict] = defaultdict(dict) _sessions_counter: dict[str | None, int] = defaultdict(int) _code_blocks: dict[str, list[str]] = {} def _buffer_print(buffer: StringIO, *texts: str, end: str = "\n", **kwargs: Any) -> None: # noqa: ARG001 buffer.write(" ".join(str(text) for text in texts) + end) def _code_block_id( id: str | None = None, # noqa: A002 session: str | None = None, title: str | None = None, ) -> str: _sessions_counter[session] += 1 if id: code_block_id = f"id {id}" elif session: code_block_id = f"session {session}; n{_sessions_counter[session]}" if title: code_block_id = f"{code_block_id}; title {title}" else: code_block_id = f"n{_sessions_counter[session]}" if title: code_block_id = f"{code_block_id}; title {title}" return f"" def _run_python( code: str, returncode: int | None = None, # noqa: ARG001 session: str | None = None, id: str | None = None, # noqa: A002 **extra: str, ) -> str: title = extra.get("title") code_block_id = _code_block_id(id, session, title) _code_blocks[code_block_id] = code.split("\n") exec_globals = _sessions_globals[session] if session else {} # Other libraries expect functions to have a valid `__module__` attribute. # To achieve this, we need to add a `__name__` attribute to the globals. # We compute the name from the code block ID, replacing invalid characters with `_`. # We also create a module object with the same name and add it to `sys.modules`, # because that's what yet other libraries expect (`dataclasses` for example). module_name = re.sub(r"[^a-zA-Z\d]+", "_", code_block_id) exec_globals["__name__"] = module_name sys.modules[module_name] = ModuleType(module_name) buffer = StringIO() exec_globals["print"] = partial(_buffer_print, buffer) try: exec_python(code, code_block_id, exec_globals) except Exception as error: trace = traceback.TracebackException.from_exception(error) for frame in trace.stack: if frame.filename.startswith("= (3, 13): frame._lines = _code_blocks[frame.filename][frame.lineno - 1] # type: ignore[attr-defined,operator] else: frame._line = _code_blocks[frame.filename][frame.lineno - 1] # type: ignore[attr-defined,operator] raise ExecutionError(code_block("python", "".join(trace.format()), **extra)) from error return buffer.getvalue() def _format_python(**kwargs: Any) -> str: return base_format(language="python", run=_run_python, **kwargs) markdown-exec-1.10.2/src/markdown_exec/_internal/formatters/sh.py000066400000000000000000000016051476636417300251240ustar00rootroot00000000000000# Formatter for executing shell code. from __future__ import annotations import subprocess from typing import Any from markdown_exec._internal.formatters.base import ExecutionError, base_format from markdown_exec._internal.rendering import code_block def _run_sh( code: str, returncode: int | None = None, session: str | None = None, # noqa: ARG001 id: str | None = None, # noqa: A002,ARG001 **extra: str, ) -> str: process = subprocess.run( # noqa: S603 ["sh", "-c", code], # noqa: S607 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, check=False, ) if process.returncode != returncode: raise ExecutionError(code_block("sh", process.stdout, **extra), process.returncode) return process.stdout def _format_sh(**kwargs: Any) -> str: return base_format(language="sh", run=_run_sh, **kwargs) markdown-exec-1.10.2/src/markdown_exec/_internal/formatters/tree.py000066400000000000000000000040041476636417300254450ustar00rootroot00000000000000# Formatter for file-system trees. from __future__ import annotations from textwrap import dedent from typing import TYPE_CHECKING, Any from markdown_exec._internal.rendering import MarkdownConverter, code_block if TYPE_CHECKING: from markdown import Markdown def _rec_build_tree(lines: list[str], parent: list, offset: int, base_indent: int) -> int: while offset < len(lines): line = lines[offset] lstripped = line.lstrip() indent = len(line) - len(lstripped) if indent == base_indent: parent.append((lstripped, [])) offset += 1 elif indent > base_indent: offset = _rec_build_tree(lines, parent[-1][1], offset, indent) else: return offset return offset def _build_tree(code: str) -> list[tuple[str, list]]: lines = dedent(code.strip()).split("\n") root_layer: list[tuple[str, list]] = [] _rec_build_tree(lines, root_layer, 0, 0) return root_layer def _rec_format_tree(tree: list[tuple[str, list]], *, root: bool = True) -> list[str]: lines = [] n_items = len(tree) for index, node in enumerate(tree): last = index == n_items - 1 prefix = "" if root else f"{'└' if last else '├'}── " if node[1]: lines.append(f"{prefix}📁 {node[0]}") sublines = _rec_format_tree(node[1], root=False) if root: lines.extend(sublines) else: indent_char = " " if last else "│" lines.extend([f"{indent_char} {line}" for line in sublines]) else: name = node[0].split()[0] icon = "📁" if name.endswith("/") else "📄" lines.append(f"{prefix}{icon} {node[0]}") return lines def _format_tree(code: str, md: Markdown, result: str, **options: Any) -> str: markdown = MarkdownConverter(md) output = "\n".join(_rec_format_tree(_build_tree(code))) return markdown.convert(code_block(result or "bash", output, **options.get("extra", {}))) markdown-exec-1.10.2/src/markdown_exec/_internal/logger.py000066400000000000000000000045471476636417300236130ustar00rootroot00000000000000from __future__ import annotations import logging from typing import Any, Callable, ClassVar class _Logger: _default_logger: Any = logging.getLogger _instances: ClassVar[dict[str, _Logger]] = {} # See same code in Griffe project. def __init__(self, name: str) -> None: # Default logger that can be patched by third-party. self._logger = self.__class__._default_logger(name) def __getattr__(self, name: str) -> Any: # Forward everything to the logger. return getattr(self._logger, name) @classmethod def get(cls, name: str) -> _Logger: """Get a logger instance. Parameters: name: The logger name. Returns: The logger instance. """ if name not in cls._instances: cls._instances[name] = cls(name) return cls._instances[name] @classmethod def _patch_loggers(cls, get_logger_func: Callable) -> None: # Patch current instances. for name, instance in cls._instances.items(): instance._logger = get_logger_func(name) # Future instances will be patched as well. cls._default_logger = get_logger_func def get_logger(name: str) -> _Logger: """Create and return a new logger instance. Parameters: name: The logger name. Returns: The logger. """ return _Logger.get(name) def patch_loggers(get_logger_func: Callable[[str], Any]) -> None: """Patch loggers. We provide the `patch_loggers`function so dependant libraries can patch loggers as they see fit. For example, to fit in the MkDocs logging configuration and prefix each log message with the module name: ```python import logging from markdown_exec.logger import patch_loggers class LoggerAdapter(logging.LoggerAdapter): def __init__(self, prefix, logger): super().__init__(logger, {}) self.prefix = prefix def process(self, msg, kwargs): return f"{self.prefix}: {msg}", kwargs def get_logger(name): logger = logging.getLogger(f"mkdocs.plugins.{name}") return LoggerAdapter(name.split(".", 1)[0], logger) patch_loggers(get_logger) ``` Parameters: get_logger_func: A function accepting a name as parameter and returning a logger. """ _Logger._patch_loggers(get_logger_func) markdown-exec-1.10.2/src/markdown_exec/_internal/main.py000066400000000000000000000102441476636417300232470ustar00rootroot00000000000000from __future__ import annotations import os import re from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from markdown import Markdown from markdown_exec._internal.formatters.base import default_tabs from markdown_exec._internal.formatters.bash import _format_bash from markdown_exec._internal.formatters.console import _format_console from markdown_exec._internal.formatters.markdown import _format_markdown from markdown_exec._internal.formatters.pycon import _format_pycon from markdown_exec._internal.formatters.pyodide import _format_pyodide from markdown_exec._internal.formatters.python import _format_python from markdown_exec._internal.formatters.sh import _format_sh from markdown_exec._internal.formatters.tree import _format_tree MARKDOWN_EXEC_AUTO = [lang.strip() for lang in os.getenv("MARKDOWN_EXEC_AUTO", "").split(",")] """Languages to automatically execute.""" formatters = { "bash": _format_bash, "console": _format_console, "md": _format_markdown, "markdown": _format_markdown, "py": _format_python, "python": _format_python, "pycon": _format_pycon, "pyodide": _format_pyodide, "sh": _format_sh, "tree": _format_tree, } """Formatters for each language.""" # negative look behind: matches only if | (pipe) if not preceded by \ (backslash) _tabs_re = re.compile(r"(? bool: """Validate code blocks inputs. Parameters: language: The code language, like python or bash. inputs: The code block inputs, to be sorted into options and attrs. options: The container for options. attrs: The container for attrs: md: The Markdown instance. Returns: Success or not. """ exec_value = language in MARKDOWN_EXEC_AUTO or _to_bool(inputs.pop("exec", "no")) if language not in {"tree", "pyodide"} and not exec_value: return False id_value = inputs.pop("id", "") id_prefix_value = inputs.pop("idprefix", None) html_value = _to_bool(inputs.pop("html", "no")) source_value = inputs.pop("source", "") result_value = inputs.pop("result", "") returncode_value = int(inputs.pop("returncode", "0")) session_value = inputs.pop("session", "") update_toc_value = _to_bool(inputs.pop("updatetoc", "yes")) tabs_value = inputs.pop("tabs", "|".join(default_tabs)) tabs = tuple(_tabs_re.split(tabs_value, maxsplit=1)) workdir_value = inputs.pop("workdir", None) width_value = int(inputs.pop("width", "0")) options["id"] = id_value options["id_prefix"] = id_prefix_value options["html"] = html_value options["source"] = source_value options["result"] = result_value options["returncode"] = returncode_value options["session"] = session_value options["update_toc"] = update_toc_value options["tabs"] = tabs options["workdir"] = workdir_value options["width"] = width_value options["extra"] = inputs return True def formatter( source: str, language: str, css_class: str, # noqa: ARG001 options: dict[str, Any], md: Markdown, classes: list[str] | None = None, # noqa: ARG001 id_value: str = "", # noqa: ARG001 attrs: dict[str, Any] | None = None, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> str: """Execute code and return HTML. Parameters: source: The code to execute. language: The code language, like python or bash. css_class: The CSS class to add to the HTML element. options: The container for options. attrs: The container for attrs: md: The Markdown instance. classes: Additional CSS classes. id_value: An optional HTML id. attrs: Additional attributes **kwargs: Additional arguments passed to SuperFences default formatters. Returns: HTML contents. """ fmt = formatters.get(language, lambda source, **kwargs: source) return fmt(code=source, md=md, **options) # type: ignore[operator] def _to_bool(value: str) -> bool: return value.lower() not in {"", "no", "off", "false", "0"} markdown-exec-1.10.2/src/markdown_exec/_internal/mkdocs_plugin.py000066400000000000000000000124651476636417300251700ustar00rootroot00000000000000# This module contains an optional plugin for MkDocs. from __future__ import annotations import logging import os from pathlib import Path from typing import TYPE_CHECKING, Any from mkdocs.config import config_options from mkdocs.config.base import Config from mkdocs.exceptions import PluginError from mkdocs.plugins import BasePlugin from mkdocs.utils import write_file from markdown_exec._internal.logger import patch_loggers from markdown_exec._internal.main import formatter, formatters, validator from markdown_exec._internal.rendering import MarkdownConverter, markdown_config if TYPE_CHECKING: from collections.abc import MutableMapping from jinja2 import Environment from mkdocs.config.defaults import MkDocsConfig from mkdocs.structure.files import Files try: __import__("pygments_ansi_color") except ImportError: _ansi_ok = False else: _ansi_ok = True class _LoggerAdapter(logging.LoggerAdapter): def __init__(self, prefix: str, logger: logging.Logger) -> None: super().__init__(logger, {}) self.prefix = prefix def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, MutableMapping[str, Any]]: return f"{self.prefix}: {msg}", kwargs def _get_logger(name: str) -> _LoggerAdapter: logger = logging.getLogger(f"mkdocs.plugins.{name}") return _LoggerAdapter(name.split(".", 1)[0], logger) patch_loggers(_get_logger) class MarkdownExecPluginConfig(Config): """Configuration of the plugin (for `mkdocs.yml`).""" ansi = config_options.Choice(("auto", "off", "required", True, False), default="auto") """Whether the `ansi` extra is required when installing the package.""" languages = config_options.ListOfItems( config_options.Choice(formatters.keys()), default=list(formatters.keys()), ) """Which languages to enabled the extension for.""" class MarkdownExecPlugin(BasePlugin[MarkdownExecPluginConfig]): """MkDocs plugin to easily enable custom fences for code blocks execution.""" def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None: """Configure the plugin. Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config). In this hook, we add custom fences for all the supported languages. We also save the Markdown extensions configuration into [`markdown_config`][markdown_exec.markdown_config]. Arguments: config: The MkDocs config object. Returns: The modified config. """ if "pymdownx.superfences" not in config["markdown_extensions"]: message = "The 'markdown-exec' plugin requires the 'pymdownx.superfences' Markdown extension to work." raise PluginError(message) if self.config.ansi in ("required", True) and not _ansi_ok: raise PluginError( "The configuration for the 'markdown-exec' plugin requires " "that it is installed with the 'ansi' extra. " "Install it with 'pip install markdown-exec[ansi]'.", ) self.mkdocs_config_dir = os.getenv("MKDOCS_CONFIG_DIR") os.environ["MKDOCS_CONFIG_DIR"] = os.path.dirname(config["config_file_path"]) self.languages = self.config.languages mdx_configs = config.setdefault("mdx_configs", {}) superfences = mdx_configs.setdefault("pymdownx.superfences", {}) custom_fences = superfences.setdefault("custom_fences", []) for language in self.languages: custom_fences.append( { "name": language, "class": language, "validator": validator, "format": formatter, }, ) markdown_config.save(config.markdown_extensions, config.mdx_configs) return config def on_env( self, env: Environment, *, config: MkDocsConfig, files: Files, # noqa: ARG002 ) -> Environment | None: """Add assets to the environment.""" if self.config.ansi in ("required", True) or (self.config.ansi == "auto" and _ansi_ok): self._add_css(config, "ansi.css") if "pyodide" in self.languages: self._add_css(config, "pyodide.css") self._add_js(config, "pyodide.js") return env def on_post_build(self, *, config: MkDocsConfig) -> None: # noqa: ARG002 """Reset the plugin state.""" MarkdownConverter.counter = 0 markdown_config.reset() if self.mkdocs_config_dir is None: os.environ.pop("MKDOCS_CONFIG_DIR", None) else: os.environ["MKDOCS_CONFIG_DIR"] = self.mkdocs_config_dir def _add_asset(self, config: MkDocsConfig, asset_file: str, asset_type: str) -> None: asset_filename = f"assets/_markdown_exec_{asset_file}" asset_content = Path(__file__).parent.parent.joinpath("assets", asset_file).read_text() write_file(asset_content.encode("utf-8"), os.path.join(config.site_dir, asset_filename)) config[f"extra_{asset_type}"].insert(0, asset_filename) def _add_css(self, config: MkDocsConfig, css_file: str) -> None: self._add_asset(config, css_file, "css") def _add_js(self, config: MkDocsConfig, js_file: str) -> None: self._add_asset(config, js_file, "javascript") markdown-exec-1.10.2/src/markdown_exec/_internal/processors.py000066400000000000000000000112571476636417300245320ustar00rootroot00000000000000# This module contains a Markdown extension # allowing to integrate generated headings into the ToC. from __future__ import annotations import copy import re from typing import TYPE_CHECKING from xml.etree.ElementTree import Element from markdown.treeprocessors import Treeprocessor from markdown.util import HTML_PLACEHOLDER_RE if TYPE_CHECKING: from markdown import Markdown from markupsafe import Markup # code taken from mkdocstrings, credits to @oprypin class IdPrependingTreeprocessor(Treeprocessor): """Prepend the configured prefix to IDs of all HTML elements.""" name = "markdown_exec_ids" """The name of the treeprocessor.""" def __init__(self, md: Markdown, id_prefix: str) -> None: super().__init__(md) self.id_prefix = id_prefix """The prefix to prepend to IDs.""" def run(self, root: Element) -> None: """Run the treeprocessor.""" if not self.id_prefix: return for el in root.iter(): id_attr = el.get("id") if id_attr: el.set("id", self.id_prefix + id_attr) href_attr = el.get("href") if href_attr and href_attr.startswith("#"): el.set("href", "#" + self.id_prefix + href_attr[1:]) name_attr = el.get("name") if name_attr: el.set("name", self.id_prefix + name_attr) if el.tag == "label": for_attr = el.get("for") if for_attr: el.set("for", self.id_prefix + for_attr) # code taken from mkdocstrings, credits to @oprypin class HeadingReportingTreeprocessor(Treeprocessor): """Records the heading elements encountered in the document.""" name = "markdown_exec_record_headings" """The name of the treeprocessor.""" regex = re.compile("[Hh][1-6]") """The regex to match heading tags.""" def __init__(self, md: Markdown, headings: list[Element]): super().__init__(md) self.headings = headings """The list of heading elements.""" def run(self, root: Element) -> None: """Run the treeprocessor.""" for el in root.iter(): if self.regex.fullmatch(el.tag): el = copy.copy(el) # noqa: PLW2901 # 'toc' extension's first pass (which we require to build heading stubs/ids) also edits the HTML. # Undo the permalink edit so we can pass this heading to the outer pass of the 'toc' extension. if len(el) > 0 and el[-1].get("class") == self.md.treeprocessors["toc"].permalink_class: # type: ignore[attr-defined] del el[-1] self.headings.append(el) class InsertHeadings(Treeprocessor): """Our headings insertor.""" name = "markdown_exec_insert_headings" """The name of the treeprocessor.""" def __init__(self, md: Markdown): """Initialize the object. Arguments: md: A `markdown.Markdown` instance. """ super().__init__(md) self.headings: dict[Markup, list[Element]] = {} """The dictionary of headings.""" def run(self, root: Element) -> None: """Run the treeprocessor.""" if not self.headings: return for el in root.iter(): match = HTML_PLACEHOLDER_RE.match(el.text or "") if match: counter = int(match.group(1)) markup: Markup = self.md.htmlStash.rawHtmlBlocks[counter] # type: ignore[assignment] if headings := self.headings.get(markup): div = Element("div", {"class": "markdown-exec"}) div.extend(headings) el.append(div) class RemoveHeadings(Treeprocessor): """Our headings remover.""" name = "markdown_exec_remove_headings" """The name of the treeprocessor.""" def run(self, root: Element) -> None: """Run the treeprocessor.""" self._remove_duplicated_headings(root) def _remove_duplicated_headings(self, parent: Element) -> None: carry_text = "" for el in reversed(parent): # Reversed mainly for the ability to mutate during iteration. if el.tag == "div" and el.get("class") == "markdown-exec": # Delete the duplicated headings along with their container, but keep the text (i.e. the actual HTML). carry_text = (el.text or "") + carry_text parent.remove(el) else: if carry_text: el.tail = (el.tail or "") + carry_text carry_text = "" self._remove_duplicated_headings(el) if carry_text: parent.text = (parent.text or "") + carry_text markdown-exec-1.10.2/src/markdown_exec/_internal/rendering.py000066400000000000000000000225771476636417300243140ustar00rootroot00000000000000# Markdown extensions and helpers. from __future__ import annotations from contextlib import contextmanager from functools import cache from textwrap import indent from typing import TYPE_CHECKING, Any from markdown import Markdown from markupsafe import Markup from markdown_exec._internal.processors import ( HeadingReportingTreeprocessor, IdPrependingTreeprocessor, InsertHeadings, RemoveHeadings, ) if TYPE_CHECKING: from collections.abc import Iterator from xml.etree.ElementTree import Element from markdown import Extension def code_block(language: str, code: str, **options: str) -> str: """Format code as a code block. Parameters: language: The code block language. code: The source code to format. **options: Additional options passed from the source, to add back to the generated code block. Returns: The formatted code block. """ opts = " ".join(f'{opt_name}="{opt_value}"' for opt_name, opt_value in options.items()) return f"````````{language} {opts}\n{code}\n````````" def tabbed(*tabs: tuple[str, str]) -> str: """Format tabs using `pymdownx.tabbed` extension. Parameters: *tabs: Tuples of strings: title and text. Returns: The formatted tabs. """ parts = [] for title, text in tabs: title = title.replace(r"\|", "|").strip() # noqa: PLW2901 parts.append(f'=== "{title}"') parts.append(indent(text, prefix=" " * 4)) parts.append("") return "\n".join(parts) def _hide_lines(source: str) -> str: return "\n".join(line for line in source.split("\n") if "markdown-exec: hide" not in line).strip() def add_source( *, source: str, location: str, output: str, language: str, tabs: tuple[str, str], result: str = "", **extra: str, ) -> str: """Add source code block to the output. Parameters: source: The source code block. location: Where to add the source (above, below, tabbed-left, tabbed-right, console). output: The current output. language: The code language. tabs: Tabs titles (if used). result: Syntax to use when concatenating source and result with "console" location. **extra: Extra options added back to source code block. Raises: ValueError: When the given location is not supported. Returns: The updated output. """ source = _hide_lines(source) if location == "console": return code_block(result or language, source + "\n" + output, **extra) source_block = code_block(language, source, **extra) if location == "above": return source_block + "\n\n" + output if location == "below": return output + "\n\n" + source_block if location == "material-block": return source_block + f'\n\n
    \n\n{output}\n\n
    ' source_tab_title, result_tab_title = tabs if location == "tabbed-left": return tabbed((source_tab_title, source_block), (result_tab_title, output)) if location == "tabbed-right": return tabbed((result_tab_title, output), (source_tab_title, source_block)) raise ValueError(f"unsupported location for sources: {location}") class MarkdownConfig: """This class returns a singleton used to store Markdown extensions configuration. You don't have to instantiate the singleton yourself: we provide it as [`markdown_config`][markdown_exec.markdown_config]. """ _singleton: MarkdownConfig | None = None def __new__(cls) -> MarkdownConfig: # noqa: PYI034 """Return the singleton instance.""" if cls._singleton is None: cls._singleton = super().__new__(cls) return cls._singleton def __init__(self) -> None: self.exts: list[str] | None = None """The Markdown extensions.""" self.exts_config: dict[str, dict[str, Any]] | None = None """The extensions configuration.""" def save(self, exts: list[str], exts_config: dict[str, dict[str, Any]]) -> None: """Save Markdown extensions and their configuration. Parameters: exts: The Markdown extensions. exts_config: The extensions configuration. """ self.exts = exts self.exts_config = exts_config def reset(self) -> None: """Reset Markdown extensions and their configuration.""" self.exts = None self.exts_config = None markdown_config = MarkdownConfig() """This object can be used to save the configuration of your Markdown extensions. For example, since we provide a MkDocs plugin, we use it to store the configuration that was read from `mkdocs.yml`: ```python from markdown_exec.rendering import markdown_config # ...in relevant events/hooks, access and modify extensions and their configs, then: markdown_config.save(extensions, extensions_config) ``` See the actual event hook: [`on_config`][markdown_exec.MarkdownExecPlugin.on_config]. See the [`save`][markdown_exec.MarkdownConfig.save] and [`reset`][markdown_exec.MarkdownConfig.reset] methods. Without it, Markdown Exec will rely on the `registeredExtensions` attribute of the original Markdown instance, which does not forward everything that was configured, notably extensions like `tables`. Other extensions such as `attr_list` are visible, but fail to register properly when reusing their instances. It means that the rendered HTML might differ from what you expect (tables not rendered, attribute lists not injected, emojis not working, etc.). """ # FIXME: When a heading contains an XML entity such as —, # the entity is stashed and replaced with a placeholder. # The heading therefore contains this placeholder. # When reporting the heading to the upper conversion layer (for the ToC), # the placeholder gets unstashed using the upper Markdown instance # instead of the neste one. If the upper instance doesn't know the placeholder, # nothing happens. But if it knows it, we then get a heading with garbabe/previous # contents within it, messing up the ToC. # We should fix this somehow. In the meantime, the workaround is to avoid # XML entities that get stashed in headings. @cache def _register_headings_processors(md: Markdown) -> None: md.treeprocessors.register( InsertHeadings(md), InsertHeadings.name, priority=75, # right before markdown.blockprocessors.HashHeaderProcessor ) md.treeprocessors.register( RemoveHeadings(md), RemoveHeadings.name, priority=4, # right after toc ) def _mimic(md: Markdown, headings: list[Element], *, update_toc: bool = True) -> Markdown: new_md = Markdown() extensions: list[Extension | str] = markdown_config.exts or md.registeredExtensions # type: ignore[assignment] extensions_config: dict[str, dict[str, Any]] = markdown_config.exts_config or {} new_md.registerExtensions(extensions, extensions_config) new_md.treeprocessors.register( IdPrependingTreeprocessor(md, ""), IdPrependingTreeprocessor.name, priority=4, # right after 'toc' (needed because that extension adds ids to headings) ) new_md._original_md = md # type: ignore[attr-defined] if update_toc: _register_headings_processors(md) new_md.treeprocessors.register( HeadingReportingTreeprocessor(new_md, headings), HeadingReportingTreeprocessor.name, priority=1, # Close to the end. ) return new_md @contextmanager def _id_prefix(md: Markdown, prefix: str | None) -> Iterator[None]: MarkdownConverter.counter += 1 id_prepending_processor = md.treeprocessors[IdPrependingTreeprocessor.name] id_prepending_processor.id_prefix = prefix if prefix is not None else f"exec-{MarkdownConverter.counter}--" # type: ignore[attr-defined] try: yield finally: id_prepending_processor.id_prefix = "" # type: ignore[attr-defined] class MarkdownConverter: """Helper class to avoid breaking the original Markdown instance state.""" counter: int = 0 """A counter to generate unique IDs for code blocks.""" def __init__(self, md: Markdown, *, update_toc: bool = True) -> None: self._md_ref: Markdown = md self._headings: list[Element] = [] self._update_toc = update_toc @property def _original_md(self) -> Markdown: return getattr(self._md_ref, "_original_md", self._md_ref) def _report_headings(self, markup: Markup) -> None: self._original_md.treeprocessors[InsertHeadings.name].headings[markup] = self._headings # type: ignore[attr-defined] self._headings = [] def convert(self, text: str, stash: dict[str, str] | None = None, id_prefix: str | None = None) -> Markup: """Convert Markdown text to safe HTML. Parameters: text: Markdown text. stash: An HTML stash. Returns: Safe HTML. """ md = _mimic(self._original_md, self._headings, update_toc=self._update_toc) # convert markdown to html with _id_prefix(md, id_prefix): converted = md.convert(text) # restore html from stash for placeholder, stashed in (stash or {}).items(): converted = converted.replace(placeholder, stashed) markup = Markup(converted) # noqa: S704 # pass headings to upstream conversion layer if self._update_toc: self._report_headings(markup) return markup markdown-exec-1.10.2/src/markdown_exec/assets/000077500000000000000000000000001476636417300212775ustar00rootroot00000000000000markdown-exec-1.10.2/src/markdown_exec/assets/ansi.css000066400000000000000000000176321476636417300227540ustar00rootroot00000000000000/* Inspired by https://spec.draculatheme.com/ specification, they should work decently with both dark and light themes. */ :root { --ansi-red: #ff5555; --ansi-green: #50fa7b; --ansi-blue: #265285; --ansi-yellow: #ffb86c; --ansi-magenta: #bd93f9; --ansi-cyan: #8be9fd; --ansi-black: #282a36; --ansi-white: #f8f8f2; } .-Color-Green, .-Color-Faint-Green, .-Color-Bold-Green, .-Color-BrightGreen { color: var(--ansi-green); } .-Color-Red, .-Color-Faint-Red, .-Color-Bold-Red, .-Color-BrightRed { color: var(--ansi-red); } .-Color-Yellow, .-Color-Faint-Yellow, .-Color-Bold-Yellow, .-Color-BrightYellow { color: var(--ansi-yellow); } .-Color-Blue, .-Color-Faint-Blue, .-Color-Bold-Blue, .-Color-BrightBlue { color: var(--ansi-blue); } .-Color-Magenta, .-Color-Faint-Magenta, .-Color-Bold-Magenta, .-Color-BrightMagenta { color: var(--ansi-magenta); } .-Color-Cyan, .-Color-Faint-Cyan, .-Color-Bold-Cyan, .-Color-BrightCyan { color: var(--ansi-cyan); } .-Color-White, .-Color-Faint-White, .-Color-Bold-White, .-Color-BrightWhite { color: var(--ansi-white); } .-Color-Black, .-Color-Faint-Black, .-Color-Bold-Black, .-Color-BrightBlack { color: var(--ansi-black); } .-Color-Faint { opacity: 0.5; } .-Color-Bold { font-weight: bold; } .-Color-BGBlack, .-Color-Black-BGBlack, .-Color-Blue-BGBlack, .-Color-Bold-BGBlack, .-Color-BrightBGBlack, .-Color-Bold-Black-BGBlack, .-Color-BrightBlack-BGBlack, .-Color-Bold-Green-BGBlack, .-Color-BrightGreen-BGBlack, .-Color-Bold-Cyan-BGBlack, .-Color-BrightCyan-BGBlack, .-Color-Bold-Blue-BGBlack, .-Color-BrightBlue-BGBlack, .-Color-Bold-Magenta-BGBlack, .-Color-BrightMagenta-BGBlack, .-Color-Bold-Red-BGBlack, .-Color-BrightRed-BGBlack, .-Color-Bold-White-BGBlack, .-Color-BrightWhite-BGBlack, .-Color-Bold-Yellow-BGBlack, .-Color-BrightYellow-BGBlack, .-Color-Cyan-BGBlack, .-Color-Green-BGBlack, .-Color-Magenta-BGBlack, .-Color-Red-BGBlack, .-Color-White-BGBlack, .-Color-Yellow-BGBlack { background-color: var(--ansi-black); } .-Color-BGRed, .-Color-Black-BGRed, .-Color-Blue-BGRed, .-Color-Bold-BGRed, .-Color-BrightBGRed, .-Color-Bold-Black-BGRed, .-Color-BrightBlack-BGRed, .-Color-Bold-Green-BGRed, .-Color-BrightGreen-BGRed, .-Color-Bold-Cyan-BGRed, .-Color-BrightCyan-BGRed, .-Color-Bold-Blue-BGRed, .-Color-BrightBlue-BGRed, .-Color-Bold-Magenta-BGRed, .-Color-BrightMagenta-BGRed, .-Color-Bold-Red-BGRed, .-Color-BrightRed-BGRed, .-Color-Bold-White-BGRed, .-Color-BrightWhite-BGRed, .-Color-Bold-Yellow-BGRed, .-Color-BrightYellow-BGRed, .-Color-Cyan-BGRed, .-Color-Green-BGRed, .-Color-Magenta-BGRed, .-Color-Red-BGRed, .-Color-White-BGRed, .-Color-Yellow-BGRed { background-color: var(--ansi-red); } .-Color-BGGreen, .-Color-Black-BGGreen, .-Color-Blue-BGGreen, .-Color-Bold-BGGreen, .-Color-BrightBGGreen, .-Color-Bold-Black-BGGreen, .-Color-BrightBlack-BGGreen, .-Color-Bold-Green-BGGreen, .-Color-BrightGreen-BGGreen, .-Color-Bold-Cyan-BGGreen, .-Color-BrightCyan-BGGreen, .-Color-Bold-Blue-BGGreen, .-Color-BrightBlue-BGGreen, .-Color-Bold-Magenta-BGGreen, .-Color-BrightMagenta-BGGreen, .-Color-Bold-Red-BGGreen, .-Color-BrightRed-BGGreen, .-Color-Bold-White-BGGreen, .-Color-BrightWhite-BGGreen, .-Color-Bold-Yellow-BGGreen, .-Color-BrightYellow-BGGreen, .-Color-Cyan-BGGreen, .-Color-Green-BGGreen, .-Color-Magenta-BGGreen, .-Color-Red-BGGreen, .-Color-White-BGGreen, .-Color-Yellow-BGGreen { background-color: var(--ansi-green); } .-Color-BGYellow, .-Color-Black-BGYellow, .-Color-Blue-BGYellow, .-Color-Bold-BGYellow, .-Color-BrightBGYellow, .-Color-Bold-Black-BGYellow, .-Color-BrightBlack-BGYellow, .-Color-Bold-Green-BGYellow, .-Color-BrightGreen-BGYellow, .-Color-Bold-Cyan-BGYellow, .-Color-BrightCyan-BGYellow, .-Color-Bold-Blue-BGYellow, .-Color-BrightBlue-BGYellow, .-Color-Bold-Magenta-BGYellow, .-Color-BrightMagenta-BGYellow, .-Color-Bold-Red-BGYellow, .-Color-BrightRed-BGYellow, .-Color-Bold-White-BGYellow, .-Color-BrightWhite-BGYellow, .-Color-Bold-Yellow-BGYellow, .-Color-BrightYellow-BGYellow, .-Color-Cyan-BGYellow, .-Color-Green-BGYellow, .-Color-Magenta-BGYellow, .-Color-Red-BGYellow, .-Color-White-BGYellow, .-Color-Yellow-BGYellow { background-color: var(--ansi-yellow); } .-Color-BGBlue, .-Color-Black-BGBlue, .-Color-Blue-BGBlue, .-Color-Bold-BGBlue, .-Color-BrightBGBlue, .-Color-Bold-Black-BGBlue, .-Color-BrightBlack-BGBlue, .-Color-Bold-Green-BGBlue, .-Color-BrightGreen-BGBlue, .-Color-Bold-Cyan-BGBlue, .-Color-BrightCyan-BGBlue, .-Color-Bold-Blue-BGBlue, .-Color-BrightBlue-BGBlue, .-Color-Bold-Magenta-BGBlue, .-Color-BrightMagenta-BGBlue, .-Color-Bold-Red-BGBlue, .-Color-BrightRed-BGBlue, .-Color-Bold-White-BGBlue, .-Color-BrightWhite-BGBlue, .-Color-Bold-Yellow-BGBlue, .-Color-BrightYellow-BGBlue, .-Color-Cyan-BGBlue, .-Color-Green-BGBlue, .-Color-Magenta-BGBlue, .-Color-Red-BGBlue, .-Color-White-BGBlue, .-Color-Yellow-BGBlue { background-color: var(--ansi-blue); } .-Color-BGMagenta, .-Color-Black-BGMagenta, .-Color-Blue-BGMagenta, .-Color-Bold-BGMagenta, .-Color-BrightBGMagenta, .-Color-Bold-Black-BGMagenta, .-Color-BrightBlack-BGMagenta, .-Color-Bold-Green-BGMagenta, .-Color-BrightGreen-BGMagenta, .-Color-Bold-Cyan-BGMagenta, .-Color-BrightCyan-BGMagenta, .-Color-Bold-Blue-BGMagenta, .-Color-BrightBlue-BGMagenta, .-Color-Bold-Magenta-BGMagenta, .-Color-BrightMagenta-BGMagenta, .-Color-Bold-Red-BGMagenta, .-Color-BrightRed-BGMagenta, .-Color-Bold-White-BGMagenta, .-Color-BrightWhite-BGMagenta, .-Color-Bold-Yellow-BGMagenta, .-Color-BrightYellow-BGMagenta, .-Color-Cyan-BGMagenta, .-Color-Green-BGMagenta, .-Color-Magenta-BGMagenta, .-Color-Red-BGMagenta, .-Color-White-BGMagenta, .-Color-Yellow-BGMagenta { background-color: var(--ansi-magenta); } .-Color-BGCyan, .-Color-Black-BGCyan, .-Color-Blue-BGCyan, .-Color-Bold-BGCyan, .-Color-BrightBGCyan, .-Color-Bold-Black-BGCyan, .-Color-BrightBlack-BGCyan, .-Color-Bold-Green-BGCyan, .-Color-BrightGreen-BGCyan, .-Color-Bold-Cyan-BGCyan, .-Color-BrightCyan-BGCyan, .-Color-Bold-Blue-BGCyan, .-Color-BrightBlue-BGCyan, .-Color-Bold-Magenta-BGCyan, .-Color-BrightMagenta-BGCyan, .-Color-Bold-Red-BGCyan, .-Color-BrightRed-BGCyan, .-Color-Bold-White-BGCyan, .-Color-BrightWhite-BGCyan, .-Color-Bold-Yellow-BGCyan, .-Color-BrightYellow-BGCyan, .-Color-Cyan-BGCyan, .-Color-Green-BGCyan, .-Color-Magenta-BGCyan, .-Color-Red-BGCyan, .-Color-White-BGCyan, .-Color-Yellow-BGCyan { background-color: var(--ansi-cyan); } .-Color-BGWhite, .-Color-Black-BGWhite, .-Color-Blue-BGWhite, .-Color-Bold-BGWhite, .-Color-BrightBGWhite, .-Color-Bold-Black-BGWhite, .-Color-BrightBlack-BGWhite, .-Color-Bold-Green-BGWhite, .-Color-BrightGreen-BGWhite, .-Color-Bold-Cyan-BGWhite, .-Color-BrightCyan-BGWhite, .-Color-Bold-Blue-BGWhite, .-Color-BrightBlue-BGWhite, .-Color-Bold-Magenta-BGWhite, .-Color-BrightMagenta-BGWhite, .-Color-Bold-Red-BGWhite, .-Color-BrightRed-BGWhite, .-Color-Bold-White-BGWhite, .-Color-BrightWhite-BGWhite, .-Color-Bold-Yellow-BGWhite, .-Color-BrightYellow-BGWhite, .-Color-Cyan-BGWhite, .-Color-Green-BGWhite, .-Color-Magenta-BGWhite, .-Color-Red-BGWhite, .-Color-White-BGWhite, .-Color-Yellow-BGWhite { background-color: var(--ansi-white); } .-Color-Black, .-Color-Bold-Black, .-Color-BrightBlack, .-Color-Black-BGBlack, .-Color-Bold-Black-BGBlack, .-Color-BrightBlack-BGBlack, .-Color-Black-BGGreen, .-Color-Red-BGRed, .-Color-Bold-Red-BGRed, .-Color-BrightRed-BGRed, .-Color-Bold-Blue-BGBlue, .-Color-BrightBlue-BGBlue, .-Color-Blue-BGBlue { text-shadow: 0 0 1px var(--ansi-white); } .-Color-Bold-Cyan-BGCyan, .-Color-BrightCyan-BGCyan, .-Color-Bold-Magenta-BGMagenta, .-Color-BrightMagenta-BGMagenta, .-Color-Bold-White, .-Color-BrightWhite, .-Color-Bold-Yellow-BGYellow, .-Color-BrightYellow-BGYellow, .-Color-Bold-Green-BGGreen, .-Color-BrightGreen-BGGreen, .-Color-Cyan-BGCyan, .-Color-Cyan-BGGreen, .-Color-Green-BGCyan, .-Color-Green-BGGreen, .-Color-Magenta-BGMagenta, .-Color-White, .-Color-White-BGWhite, .-Color-Yellow-BGYellow { text-shadow: 0 0 1px var(--ansi-black); }markdown-exec-1.10.2/src/markdown_exec/assets/pyodide.css000066400000000000000000000016721476636417300234540ustar00rootroot00000000000000html[data-theme="light"] { @import "https://cdn.jsdelivr.net/npm/highlightjs-themes@1.0.0/tomorrow.css" } html[data-theme="dark"] { @import "https://cdn.jsdelivr.net/npm/highlightjs-themes@1.0.0/tomorrow-night-blue.min.css" } .ace_gutter { z-index: 1; } .pyodide-editor { width: 100%; min-height: 200px; max-height: 400px; font-size: .85em; } .pyodide-editor-bar { color: var(--md-primary-bg-color); background-color: var(--md-primary-fg-color); width: 100%; font: monospace; font-size: 0.75em; padding: 2px 0 2px; } .pyodide-bar-item { padding: 0 18px 0; display: inline-block; width: 50%; } .pyodide pre { margin: 0; } .pyodide-output { width: 100%; margin-bottom: -15px; min-height: 46px; max-height: 400px } .pyodide-clickable { cursor: pointer; text-align: right; } /* For themes other than Material. */ .pyodide .twemoji svg { width: 1rem; } markdown-exec-1.10.2/src/markdown_exec/assets/pyodide.js000066400000000000000000000101201476636417300232640ustar00rootroot00000000000000var _sessions = {}; function getSession(name, pyodide) { if (!(name in _sessions)) { _sessions[name] = pyodide.globals.get("dict")(); } return _sessions[name]; } function writeOutput(element, string) { element.innerHTML += string + '\n'; } function clearOutput(element) { element.innerHTML = ''; } async function evaluatePython(pyodide, editor, output, session) { pyodide.setStdout({ batched: (string) => { writeOutput(output, string); } }); let result, code = editor.getValue(); clearOutput(output); try { result = await pyodide.runPythonAsync(code, { globals: getSession(session, pyodide) }); } catch (error) { writeOutput(output, new Option(error.toString()).innerHTML); } if (result) writeOutput(output, new Option(result).innerHTML); hljs.highlightElement(output); } async function initPyodide() { try { let pyodide = await loadPyodide(); await pyodide.loadPackage("micropip"); return pyodide; } catch(error) { return null; } } function getTheme() { return document.body.getAttribute('data-md-color-scheme'); } function setTheme(editor, currentTheme, light, dark) { // https://gist.github.com/RyanNutt/cb8d60997d97905f0b2aea6c3b5c8ee0 if (currentTheme === "default") { editor.setTheme("ace/theme/" + light); document.querySelector(`link[title="light"]`).removeAttribute("disabled"); document.querySelector(`link[title="dark"]`).setAttribute("disabled", "disabled"); } else if (currentTheme === "slate") { editor.setTheme("ace/theme/" + dark); document.querySelector(`link[title="dark"]`).removeAttribute("disabled"); document.querySelector(`link[title="light"]`).setAttribute("disabled", "disabled"); } } function updateTheme(editor, light, dark) { // Create a new MutationObserver instance const observer = new MutationObserver((mutations) => { // Loop through the mutations that occurred mutations.forEach((mutation) => { // Check if the mutation was a change to the data-md-color-scheme attribute if (mutation.attributeName === 'data-md-color-scheme') { // Get the new value of the attribute const newColorScheme = mutation.target.getAttribute('data-md-color-scheme'); // Update the editor theme setTheme(editor, newColorScheme, light, dark); } }); }); // Configure the observer to watch for changes to the data-md-color-scheme attribute observer.observe(document.body, { attributes: true, attributeFilter: ['data-md-color-scheme'], }); } async function setupPyodide(idPrefix, install = null, themeLight = 'tomorrow', themeDark = 'tomorrow_night', session = null) { const editor = ace.edit(idPrefix + "editor"); const run = document.getElementById(idPrefix + "run"); const clear = document.getElementById(idPrefix + "clear"); const output = document.getElementById(idPrefix + "output"); updateTheme(editor, themeLight, themeDark); editor.session.setMode("ace/mode/python"); setTheme(editor, getTheme(), themeLight, themeDark); writeOutput(output, "Initializing..."); let pyodide = await pyodidePromise; if (install && install.length) { try { micropip = pyodide.pyimport("micropip"); for (const package of install) await micropip.install(package); clearOutput(output); } catch (error) { clearOutput(output); writeOutput(output, `Could not install one or more packages: ${install.join(", ")}\n`); writeOutput(output, new Option(error.toString()).innerHTML); } } run.onclick = () => evaluatePython(pyodide, editor, output, session); clear.onclick = () => clearOutput(output); output.parentElement.parentElement.addEventListener("keydown", (event) => { if (event.ctrlKey && event.key.toLowerCase() === 'enter') { event.preventDefault(); run.click(); } }); } var pyodidePromise = initPyodide(); markdown-exec-1.10.2/src/markdown_exec/formatters/000077500000000000000000000000001476636417300221635ustar00rootroot00000000000000markdown-exec-1.10.2/src/markdown_exec/formatters/__init__.py000066400000000000000000000006551476636417300243020ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal import formatters def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(formatters, name) markdown-exec-1.10.2/src/markdown_exec/formatters/base.py000066400000000000000000000006611476636417300234520ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import base def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.base` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(base, name) markdown-exec-1.10.2/src/markdown_exec/formatters/bash.py000066400000000000000000000006611476636417300234550ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import bash def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.bash` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(bash, name) markdown-exec-1.10.2/src/markdown_exec/formatters/console.py000066400000000000000000000006721476636417300242040ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import console def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.console` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(console, name) markdown-exec-1.10.2/src/markdown_exec/formatters/markdown.py000066400000000000000000000006751476636417300243670ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import markdown def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.markdown` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(markdown, name) markdown-exec-1.10.2/src/markdown_exec/formatters/pycon.py000066400000000000000000000006641476636417300236730ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import pycon def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.pycon` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(pycon, name) markdown-exec-1.10.2/src/markdown_exec/formatters/pyodide.py000066400000000000000000000006721476636417300241770ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import pyodide def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.pyodide` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(pyodide, name) markdown-exec-1.10.2/src/markdown_exec/formatters/python.py000066400000000000000000000006671476636417300240670ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import python def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.python` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(python, name) markdown-exec-1.10.2/src/markdown_exec/formatters/sh.py000066400000000000000000000006531476636417300231530ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import sh def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.sh` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(sh, name) markdown-exec-1.10.2/src/markdown_exec/formatters/tree.py000066400000000000000000000006611476636417300234770ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal.formatters import tree def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.formatters.tree` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(tree, name) markdown-exec-1.10.2/src/markdown_exec/logger.py000066400000000000000000000006411476636417300216270ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal import logger def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.logger` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(logger, name) markdown-exec-1.10.2/src/markdown_exec/mkdocs_plugin.py000066400000000000000000000006661476636417300232150ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal import mkdocs_plugin def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.mkdocs_plugin` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(mkdocs_plugin, name) markdown-exec-1.10.2/src/markdown_exec/processors.py000066400000000000000000000006551476636417300225570ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal import processors def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.processors` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(processors, name) markdown-exec-1.10.2/src/markdown_exec/py.typed000066400000000000000000000000001476636417300214620ustar00rootroot00000000000000markdown-exec-1.10.2/src/markdown_exec/rendering.py000066400000000000000000000006521476636417300223270ustar00rootroot00000000000000"""Deprecated. Import from `markdown_exec` directly.""" # YORE: Bump 2: Remove file. import warnings from typing import Any from markdown_exec._internal import rendering def __getattr__(name: str) -> Any: warnings.warn( "Importing from `markdown_exec.rendering` is deprecated. Import from `markdown_exec` directly.", DeprecationWarning, stacklevel=2, ) return getattr(rendering, name) markdown-exec-1.10.2/tests/000077500000000000000000000000001476636417300155225ustar00rootroot00000000000000markdown-exec-1.10.2/tests/__init__.py000066400000000000000000000002461476636417300176350ustar00rootroot00000000000000"""Tests suite for `markdown_exec`.""" from pathlib import Path TESTS_DIR = Path(__file__).parent TMP_DIR = TESTS_DIR / "tmp" FIXTURES_DIR = TESTS_DIR / "fixtures" markdown-exec-1.10.2/tests/conftest.py000066400000000000000000000012141476636417300177170ustar00rootroot00000000000000"""Configuration for the pytest test suite.""" import pytest from markdown import Markdown from markdown_exec import formatter, formatters, validator @pytest.fixture def md() -> Markdown: """Return a Markdown instance. Returns: Markdown instance. """ fences = [ { "name": language, "class": language, "validator": validator, "format": formatter, } for language in formatters ] return Markdown( extensions=["pymdownx.superfences", "pymdownx.tabbed"], extension_configs={"pymdownx.superfences": {"custom_fences": fences}}, ) markdown-exec-1.10.2/tests/test_api.py000066400000000000000000000172501476636417300177110ustar00rootroot00000000000000"""Tests for our own API exposition.""" from __future__ import annotations from collections import defaultdict from pathlib import Path from typing import TYPE_CHECKING import griffe import pytest from mkdocstrings import Inventory import markdown_exec if TYPE_CHECKING: from collections.abc import Iterator @pytest.fixture(name="loader", scope="module") def _fixture_loader() -> griffe.GriffeLoader: loader = griffe.GriffeLoader() loader.load("markdown_exec") loader.resolve_aliases() return loader @pytest.fixture(name="internal_api", scope="module") def _fixture_internal_api(loader: griffe.GriffeLoader) -> griffe.Module: return loader.modules_collection["markdown_exec._internal"] @pytest.fixture(name="public_api", scope="module") def _fixture_public_api(loader: griffe.GriffeLoader) -> griffe.Module: return loader.modules_collection["markdown_exec"] def _yield_public_objects( obj: griffe.Module | griffe.Class, *, modules: bool = False, modulelevel: bool = True, inherited: bool = False, special: bool = False, ) -> Iterator[griffe.Object | griffe.Alias]: for member in obj.all_members.values() if inherited else obj.members.values(): try: if member.is_module: if member.is_alias or not member.is_public: continue if modules: yield member yield from _yield_public_objects( member, # type: ignore[arg-type] modules=modules, modulelevel=modulelevel, inherited=inherited, special=special, ) elif member.is_public and (special or not member.is_special): yield member else: continue if member.is_class and not modulelevel: yield from _yield_public_objects( member, # type: ignore[arg-type] modules=modules, modulelevel=False, inherited=inherited, special=special, ) except (griffe.AliasResolutionError, griffe.CyclicAliasError): continue @pytest.fixture(name="modulelevel_internal_objects", scope="module") def _fixture_modulelevel_internal_objects(internal_api: griffe.Module) -> list[griffe.Object | griffe.Alias]: return list(_yield_public_objects(internal_api, modulelevel=True)) @pytest.fixture(name="internal_objects", scope="module") def _fixture_internal_objects(internal_api: griffe.Module) -> list[griffe.Object | griffe.Alias]: return list(_yield_public_objects(internal_api, modulelevel=False, special=True)) @pytest.fixture(name="public_objects", scope="module") def _fixture_public_objects(public_api: griffe.Module) -> list[griffe.Object | griffe.Alias]: return list(_yield_public_objects(public_api, modulelevel=False, inherited=True, special=True)) @pytest.fixture(name="inventory", scope="module") def _fixture_inventory() -> Inventory: inventory_file = Path(__file__).parent.parent / "site" / "objects.inv" if not inventory_file.exists(): raise pytest.skip("The objects inventory is not available.") with inventory_file.open("rb") as file: return Inventory.parse_sphinx(file) def test_exposed_objects(modulelevel_internal_objects: list[griffe.Object | griffe.Alias]) -> None: """All public objects in the internal API are exposed under `markdown_exec`.""" not_exposed = [ obj.path for obj in modulelevel_internal_objects if obj.name not in markdown_exec.__all__ or not hasattr(markdown_exec, obj.name) ] assert not not_exposed, "Objects not exposed:\n" + "\n".join(sorted(not_exposed)) def test_unique_names(modulelevel_internal_objects: list[griffe.Object | griffe.Alias]) -> None: """All internal objects have unique names.""" names_to_paths = defaultdict(list) for obj in modulelevel_internal_objects: names_to_paths[obj.name].append(obj.path) non_unique = [paths for paths in names_to_paths.values() if len(paths) > 1] assert not non_unique, "Non-unique names:\n" + "\n".join(str(paths) for paths in non_unique) def test_single_locations(public_api: griffe.Module) -> None: """All objects have a single public location.""" def _public_path(obj: griffe.Object | griffe.Alias) -> bool: return obj.is_public and (obj.parent is None or _public_path(obj.parent)) multiple_locations = {} for obj_name in markdown_exec.__all__: obj = public_api[obj_name] if obj.aliases and ( public_aliases := [path for path, alias in obj.aliases.items() if path != obj.path and _public_path(alias)] ): multiple_locations[obj.path] = public_aliases assert not multiple_locations, "Multiple public locations:\n" + "\n".join( f"{path}: {aliases}" for path, aliases in multiple_locations.items() ) def test_api_matches_inventory(inventory: Inventory, public_objects: list[griffe.Object | griffe.Alias]) -> None: """All public objects are added to the inventory.""" ignore_names = {"__getattr__", "__init__", "__repr__", "__str__", "__post_init__"} not_in_inventory = [ obj.path for obj in public_objects if obj.name not in ignore_names and obj.path not in inventory ] msg = "Objects not in the inventory (try running `make run mkdocs build`):\n{paths}" assert not not_in_inventory, msg.format(paths="\n".join(sorted(not_in_inventory))) def test_inventory_matches_api( inventory: Inventory, public_objects: list[griffe.Object | griffe.Alias], loader: griffe.GriffeLoader, ) -> None: """The inventory doesn't contain any additional Python object.""" not_in_api = [] public_api_paths = {obj.path for obj in public_objects} public_api_paths.add("markdown_exec") # YORE: Bump 2: Remove block. ignore_modules = { "markdown_exec.formatters", "markdown_exec.formatters.base", "markdown_exec.formatters.bash", "markdown_exec.formatters.console", "markdown_exec.formatters.markdown", "markdown_exec.formatters.pycon", "markdown_exec.formatters.pyodide", "markdown_exec.formatters.python", "markdown_exec.formatters.sh", "markdown_exec.formatters.tree", "markdown_exec.logger", "markdown_exec.mkdocs_plugin", "markdown_exec.processors", "markdown_exec.rendering", } for item in inventory.values(): # YORE: Bump 2: Remove block. if item.name in ignore_modules: continue if ( item.domain == "py" and "(" not in item.name and (item.name == "markdown_exec" or item.name.startswith("markdown_exec.")) ): obj = loader.modules_collection[item.name] if obj.path not in public_api_paths and not any(path in public_api_paths for path in obj.aliases): not_in_api.append(item.name) msg = "Inventory objects not in public API (try running `make run mkdocs build`):\n{paths}" assert not not_in_api, msg.format(paths="\n".join(sorted(not_in_api))) def test_no_module_docstrings_in_internal_api(internal_api: griffe.Module) -> None: """No module docstrings should be written in our internal API. The reasoning is that docstrings are addressed to users of the public API, but internal modules are not exposed to users, so they should not have docstrings. """ def _modules(obj: griffe.Module) -> Iterator[griffe.Module]: for member in obj.modules.values(): yield member yield from _modules(member) for obj in _modules(internal_api): assert not obj.docstring markdown-exec-1.10.2/tests/test_base_formatter.py000066400000000000000000000063411476636417300221340ustar00rootroot00000000000000"""Tests for the base formatter.""" import os import subprocess import pytest from markdown import Markdown from markdown_exec import base_format def test_no_p_around_html(md: Markdown) -> None: """Assert HTML isn't wrapped in a `p` tag. Parameters: md: A Markdown instance (fixture). """ code = "
    hello
    " html = base_format( language="whatever", run=lambda code, **_: code, code=code, md=md, html=True, ) assert html == code @pytest.mark.parametrize("html", [True, False]) def test_render_source(md: Markdown, html: bool) -> None: """Assert source is rendered. Parameters: md: A Markdown instance (fixture). html: Whether output is HTML or not. """ markup = base_format( language="python", run=lambda code, **_: code, code="hello", md=md, html=html, source="tabbed-left", ) assert "Source" in markup def test_render_console_plus_ansi_result(md: Markdown) -> None: """Assert we can render source as console style with `ansi` highlight. Parameters: md: A Markdown instance (fixture). """ markup = base_format( language="bash", run=lambda code, **_: code, code="echo -e '\033[31mhello'", md=md, html=False, source="console", result="ansi", ) assert "ansi" in markup def test_dont_render_anything_if_output_is_empty(md: Markdown) -> None: """Assert nothing is rendered if output is empty. Parameters: md: A Markdown instance (fixture). """ markup = base_format( language="bash", run=lambda code, **_: "", code="whatever", md=md, ) assert not markup def test_render_source_even_if_output_is_empty(md: Markdown) -> None: """Assert source is rendered even if output is empty. Parameters: md: A Markdown instance (fixture). """ markup = base_format( language="bash", run=lambda code, **_: "", code="whatever", md=md, source="tabbed-left", ) assert "Source" in markup @pytest.mark.skipif(os.name != "posix", reason="No time for the annoying OS.") def test_changing_working_directory(md: Markdown) -> None: """Assert we can change the working directory with `workdir`. Parameters: md: A Markdown instance (fixture). """ markup = base_format( language="python", run=lambda code, **_: subprocess.check_output(code, shell=True, text=True), # noqa: S602 code="pwd", md=md, workdir="/", ) assert markup == "

    /

    " @pytest.mark.skipif(os.name != "posix", reason="No time for the annoying OS.") def test_console_width(md: Markdown) -> None: """Assert we can change the console width with `width`. Parameters: md: A Markdown instance (fixture). """ for width in (10, 1000): markup = base_format( language="bash", run=lambda code, **_: subprocess.check_output(code, shell=True, text=True), # noqa: S602, code="echo width: $COLUMNS", md=md, width=width, ) assert f"width: {width}" in markup markdown-exec-1.10.2/tests/test_converter.py000066400000000000000000000040261476636417300211440ustar00rootroot00000000000000"""Tests for the Markdown converter.""" from __future__ import annotations import re from textwrap import dedent from typing import TYPE_CHECKING import pytest from markdown.extensions.toc import TocExtension from markdown_exec import MarkdownConfig, markdown_config if TYPE_CHECKING: from markdown import Markdown def test_rendering_nested_blocks(md: Markdown) -> None: """Assert nested blocks are properly handled. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ````md exec="1" ```python exec="1" print("**Bold!**") ``` ```` """, ), ) assert html == "

    Bold!

    " def test_instantiating_config_singleton() -> None: """Assert that the Markdown config instances act as a singleton.""" assert MarkdownConfig() is markdown_config markdown_config.save([], {}) markdown_config.reset() @pytest.mark.parametrize( ("id", "id_prefix", "expected"), [ ("", None, 'id="exec-\\d+--heading"'), ("", "", 'id="heading"'), ("", "some-prefix-", 'id="some-prefix-heading"'), ("some-id", None, 'id="some-id-heading"'), ("some-id", "", 'id="heading"'), ("some-id", "some-prefix-", 'id="some-prefix-heading"'), ], ) def test_prefixing_headings(md: Markdown, id: str, id_prefix: str | None, expected: str) -> None: # noqa: A002 """Assert that we prefix headings as specified. Parameters: md: A Markdown instance (fixture). id: The code block id. id_prefix: The code block id prefix. expected: The id we expect to find in the HTML. """ TocExtension().extendMarkdown(md) prefix = f'idprefix="{id_prefix}"' if id_prefix is not None else "" html = md.convert( dedent( f""" ```python exec="1" id="{id}" {prefix} print("# HEADING") ``` """, ), ) assert re.search(expected, html) markdown-exec-1.10.2/tests/test_headings.py000066400000000000000000000011311476636417300207110ustar00rootroot00000000000000"""Tests for headings.""" from __future__ import annotations from textwrap import dedent from typing import TYPE_CHECKING if TYPE_CHECKING: from markdown import Markdown def test_headings_removal(md: Markdown) -> None: """Headings should leave no trace behind. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ === "File layout" ```tree ./ hello.md ``` """, ), ) assert 'class="markdown-exec"' not in html markdown-exec-1.10.2/tests/test_python.py000066400000000000000000000124361476636417300204620ustar00rootroot00000000000000"""Tests for the Python formatters.""" from __future__ import annotations import re from textwrap import dedent from typing import TYPE_CHECKING if TYPE_CHECKING: import pytest from markdown import Markdown def test_output_markdown(md: Markdown) -> None: """Assert Markdown is converted to HTML. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="yes" print("**Bold!**") ``` """, ), ) assert html == "

    Bold!

    " def test_output_html(md: Markdown) -> None: """Assert HTML is injected as is. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="yes" html="yes" print("**Bold!**") ``` """, ), ) assert html == "

    **Bold!**\n

    " def test_error_raised(md: Markdown, caplog: pytest.LogCaptureFixture) -> None: """Assert errors properly log a warning and return a formatted traceback. Parameters: md: A Markdown instance (fixture). caplog: Pytest fixture to capture logs. """ html = md.convert( dedent( """ ```python exec="yes" raise ValueError("oh no!") ``` """, ), ) assert "Traceback" in html assert "ValueError" in html assert "oh no!" in html assert "Execution of python code block exited with errors" in caplog.text def test_can_print_non_string_objects(md: Markdown) -> None: """Assert we can print non-string objects. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="yes" class NonString: def __str__(self): return "string" nonstring = NonString() print(nonstring, nonstring) ``` """, ), ) assert "Traceback" not in html def test_sessions(md: Markdown) -> None: """Assert sessions can be reused. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="1" session="a" a = 1 ``` ```pycon exec="1" session="b" >>> b = 2 ``` ```pycon exec="1" session="a" >>> print(f"a = {a}") >>> try: ... print(b) ... except NameError: ... print("ok") ... else: ... print("ko") ``` ```python exec="1" session="b" print(f"b = {b}") try: print(a) except NameError: print("ok") else: print("ko") ``` """, ), ) assert "a = 1" in html assert "b = 2" in html assert "ok" in html assert "ko" not in html def test_reporting_errors_in_sessions(md: Markdown, caplog: pytest.LogCaptureFixture) -> None: """Assert errors and source lines are correctly reported across sessions. Parameters: md: A Markdown instance (fixture). caplog: Pytest fixture to capture logs. """ html = md.convert( dedent( """ ```python exec="1" session="a" def fraise(): raise RuntimeError("strawberry") ``` ```python exec="1" session="a" print("hello") fraise() ``` """, ), ) assert "Traceback" in html assert "strawberry" in html assert "fraise()" in caplog.text assert 'raise RuntimeError("strawberry")' in caplog.text def test_removing_output_from_pycon_code(md: Markdown) -> None: """Assert output lines are removed from pycon snippets. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```pycon exec="1" source="console" >>> print("ok") ko ``` """, ), ) assert "ok" in html assert "ko" not in html def test_functions_have_a_module_attribute(md: Markdown) -> None: """Assert functions have a `__module__` attribute. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="1" def func(): pass print(f"`{func.__module__}`") ``` """, ), ) assert "_code_block_n" in html def test_future_annotations_do_not_leak_into_user_code(md: Markdown) -> None: """Assert future annotations do not leak into user code. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```python exec="1" class Int: ... def f(x: Int) -> None: return x + 1.0 print(f"`{f.__annotations__['x']}`") ``` """, ), ) assert "Int" not in html assert re.search(r"class '_code_block_n\d+_\.Int'", html) markdown-exec-1.10.2/tests/test_shell.py000066400000000000000000000037531476636417300202520ustar00rootroot00000000000000"""Tests for the shell formatters.""" from __future__ import annotations from textwrap import dedent from typing import TYPE_CHECKING if TYPE_CHECKING: import pytest from markdown import Markdown def test_output_markdown(md: Markdown) -> None: """Assert Markdown is converted to HTML. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```sh exec="yes" echo "**Bold!**" ``` """, ), ) assert html == "

    Bold!

    " def test_output_html(md: Markdown) -> None: """Assert HTML is injected as is. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```sh exec="yes" html="yes" echo "**Bold!**" ``` """, ), ) assert html == "

    **Bold!**\n

    " def test_error_raised(md: Markdown, caplog: pytest.LogCaptureFixture) -> None: """Assert errors properly log a warning and return a formatted traceback. Parameters: md: A Markdown instance (fixture). caplog: Pytest fixture to capture logs. """ html = md.convert( dedent( """ ```sh exec="yes" echo("wrong syntax") ``` """, ), ) assert "error" in html assert "Execution of sh code block exited with unexpected code 2" in caplog.text def test_return_code(md: Markdown, caplog: pytest.LogCaptureFixture) -> None: """Assert return code is used correctly. Parameters: md: A Markdown instance (fixture). caplog: Pytest fixture to capture logs. """ html = md.convert( dedent( """ ```sh exec="yes" returncode="1" echo Not in the mood exit 1 ``` """, ), ) assert "Not in the mood" in html assert "exited with" not in caplog.text markdown-exec-1.10.2/tests/test_toc.py000066400000000000000000000044371476636417300177300ustar00rootroot00000000000000"""Tests for the logic updating the table of contents.""" from __future__ import annotations from textwrap import dedent from typing import TYPE_CHECKING from markdown.extensions.toc import TocExtension if TYPE_CHECKING: from markdown import Markdown def test_updating_toc(md: Markdown) -> None: """Assert ToC is updated with generated headings. Parameters: md: A Markdown instance (fixture). """ TocExtension().extendMarkdown(md) html = md.convert( dedent( """ ```python exec="yes" print("# big heading") ``` """, ), ) assert " None: """Assert ToC is not updated with generated headings. Parameters: md: A Markdown instance (fixture). """ TocExtension().extendMarkdown(md) html = md.convert( dedent( """ ```python exec="yes" updatetoc="no" print("# big heading") ``` """, ), ) assert " None: """Assert ToC is not updated with generated headings. Parameters: md: A Markdown instance (fixture). """ TocExtension().extendMarkdown(md) html = md.convert( dedent( """ ```python exec="yes" updatetoc="no" print("# big heading") ``` ```python exec="yes" updatetoc="yes" print("## medium heading") ``` ```python exec="yes" updatetoc="no" print("### small heading") ``` ```python exec="yes" updatetoc="yes" print("#### tiny heading") ``` """, ), ) assert " None: """Assert we can highlight lines in the output. Parameters: md: A Markdown instance (fixture). """ html = md.convert( dedent( """ ```tree hl_lines="2" 1 2 3 ``` """, ), ) assert '' in html markdown-exec-1.10.2/tests/test_validator.py000066400000000000000000000017431476636417300211250ustar00rootroot00000000000000"""Tests for the `validator` function.""" import pytest from markdown.core import Markdown from markdown_exec import validator @pytest.mark.parametrize( ("exec_value", "expected"), [ ("yes", True), ("YES", True), ("on", True), ("ON", True), ("whynot", True), ("true", True), ("TRUE", True), ("1", True), ("-1", True), ("0", False), ("no", False), ("NO", False), ("off", False), ("OFF", False), ("false", False), ("FALSE", False), ], ) def test_validate(md: Markdown, exec_value: str, expected: bool) -> None: """Assert the validator returns True or False given inputs. Parameters: md: A Markdown instance. exec_value: The exec option value, passed from the code block. expected: Expected validation result. """ assert validator("whatever", inputs={"exec": exec_value}, options={}, attrs={}, md=md) is expected