├── .copier-answers.yml ├── .envrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitpod.dockerfile ├── .gitpod.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CREDITS.md ├── LICENSE ├── Makefile ├── README.md ├── config ├── coverage.ini ├── git-changelog.toml ├── mypy.ini ├── pytest.ini ├── ruff.toml └── vscode │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── devdeps.txt ├── docs ├── .overrides │ └── main.html ├── changelog.md ├── code_of_conduct.md ├── contributing.md ├── credits.md ├── css │ ├── material.css │ └── mkdocstrings.css ├── gen_credits.py ├── index.md ├── license.md └── schema.json ├── duties.py ├── mkdocs.yml ├── pyproject.toml ├── scripts ├── gen_credits.py ├── gen_ref_nav.py └── make ├── src └── mkdocs_coverage │ ├── __init__.py │ ├── debug.py │ ├── loggers.py │ ├── plugin.py │ └── py.typed └── tests ├── __init__.py ├── conftest.py └── test_plugin.py /.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier 2 | _commit: 1.2.8 3 | _src_path: gh:pawamoy/copier-uv 4 | author_email: dev@pawamoy.fr 5 | author_fullname: Timothée Mazzucotelli 6 | author_username: pawamoy 7 | copyright_date: '2021' 8 | copyright_holder: Timothée Mazzucotelli 9 | copyright_holder_email: dev@pawamoy.fr 10 | copyright_license: ISC License 11 | insiders: false 12 | project_description: MkDocs plugin to integrate your coverage HTML report into your site. 13 | project_name: MkDocs Coverage Plugin 14 | python_package_command_line_name: '' 15 | python_package_distribution_name: mkdocs-coverage 16 | python_package_import_name: mkdocs_coverage 17 | repository_name: mkdocs-coverage 18 | repository_namespace: pawamoy 19 | repository_provider: github.com 20 | 21 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | PATH_add scripts 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: pawamoy 2 | ko_fi: pawamoy 3 | polar: pawamoy 4 | custom: 5 | - https://www.paypal.me/pawamoy 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve. 4 | title: "bug: " 5 | labels: unconfirmed 6 | assignees: [pawamoy] 7 | --- 8 | 9 | ### Description of the bug 10 | 11 | 12 | ### To Reproduce 13 | 28 | 29 | ``` 30 | WRITE MRE / INSTRUCTIONS HERE 31 | ``` 32 | 33 | ### Full traceback 34 | 36 | 37 |
Full traceback 38 | 39 | ```python 40 | PASTE TRACEBACK HERE 41 | ``` 42 | 43 |
44 | 45 | ### Expected behavior 46 | 47 | 48 | ### Environment information 49 | 51 | 52 | ```bash 53 | python -m mkdocs_coverage.debug # | xclip -selection clipboard 54 | ``` 55 | 56 | PASTE OUTPUT HERE 57 | 58 | ### Additional context 59 | 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a question / I need help 4 | url: https://github.com/pawamoy/mkdocs-coverage/discussions/new?category=q-a 5 | about: Ask and answer questions in the Discussions tab. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: "feature: " 5 | labels: feature 6 | assignees: pawamoy 7 | --- 8 | 9 | ### Is your feature request related to a problem? Please describe. 10 | 11 | 12 | ### Describe the solution you'd like 13 | 14 | 15 | ### Describe alternatives you've considered 16 | 17 | 18 | ### Additional context 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | env: 14 | LANG: en_US.utf-8 15 | LC_ALL: en_US.utf-8 16 | PYTHONIOENCODING: UTF-8 17 | PYTHON_VERSIONS: "" 18 | 19 | jobs: 20 | 21 | quality: 22 | 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Fetch all tags 30 | run: git fetch --depth=1 --tags 31 | 32 | - name: Set up Python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: "3.11" 36 | 37 | - name: Install uv 38 | run: pip install uv 39 | 40 | - name: Install dependencies 41 | run: make setup 42 | 43 | - name: Check if the documentation builds correctly 44 | run: make check-docs 45 | 46 | - name: Check the code quality 47 | run: make check-quality 48 | 49 | - name: Check if the code is correctly typed 50 | run: make check-types 51 | 52 | - name: Check for breaking changes in the API 53 | run: make check-api 54 | 55 | tests: 56 | 57 | strategy: 58 | matrix: 59 | os: 60 | - ubuntu-latest 61 | - macos-latest 62 | - windows-latest 63 | python-version: 64 | - "3.8" 65 | - "3.9" 66 | - "3.10" 67 | - "3.11" 68 | - "3.12" 69 | - "3.13" 70 | resolution: 71 | - highest 72 | - lowest-direct 73 | exclude: 74 | - os: macos-latest 75 | resolution: lowest-direct 76 | - os: windows-latest 77 | resolution: lowest-direct 78 | runs-on: ${{ matrix.os }} 79 | continue-on-error: ${{ matrix.python-version == '3.13' }} 80 | 81 | steps: 82 | - name: Checkout 83 | uses: actions/checkout@v4 84 | 85 | - name: Set up Python 86 | uses: actions/setup-python@v5 87 | with: 88 | python-version: ${{ matrix.python-version }} 89 | allow-prereleases: true 90 | 91 | - name: Install uv 92 | run: pip install uv 93 | 94 | - name: Install dependencies 95 | env: 96 | UV_RESOLUTION: ${{ matrix.resolution }} 97 | run: make setup 98 | 99 | - name: Run the test suite 100 | run: make test 101 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: push 4 | permissions: 5 | contents: write 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | if: startsWith(github.ref, 'refs/tags/') 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Fetch all tags 15 | run: git fetch --depth=1 --tags 16 | - name: Setup Python 17 | uses: actions/setup-python@v4 18 | - name: Install git-changelog 19 | run: pip install git-changelog 20 | - name: Prepare release notes 21 | run: git-changelog --release-notes > release-notes.md 22 | - name: Create release 23 | uses: softprops/action-gh-release@v1 24 | with: 25 | body_path: release-notes.md 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editors 2 | .idea/ 3 | .vscode/ 4 | 5 | # python 6 | *.egg-info/ 7 | *.py[cod] 8 | .venv/ 9 | .venvs/ 10 | /build/ 11 | /dist/ 12 | 13 | # tools 14 | .coverage* 15 | /.pdm-build/ 16 | /htmlcov/ 17 | /site/ 18 | 19 | # cache 20 | .cache/ 21 | .pytest_cache/ 22 | .mypy_cache/ 23 | .ruff_cache/ 24 | __pycache__/ 25 | -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | USER gitpod 3 | ENV PIP_USER=no 4 | RUN pip3 install pipx; \ 5 | pipx install uv; \ 6 | pipx ensurepath 7 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | vscode: 2 | extensions: 3 | - ms-python.python 4 | 5 | image: 6 | file: .gitpod.dockerfile 7 | 8 | ports: 9 | - port: 8000 10 | onOpen: notify 11 | 12 | tasks: 13 | - init: make setup 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## [1.1.0](https://github.com/pawamoy/mkdocs-coverage/releases/tag/1.1.0) - 2024-06-11 9 | 10 | [Compare with 1.0.0](https://github.com/pawamoy/mkdocs-coverage/compare/1.0.0...1.1.0) 11 | 12 | ### Build 13 | 14 | - Depend on MkDocs 1.6+ ([d2e93b6](https://github.com/pawamoy/mkdocs-coverage/commit/d2e93b6b23ca714351c09f96cf8dd2c444c77b00) by Timothée Mazzucotelli). 15 | 16 | ### Code Refactoring 17 | 18 | - Use more modern features of MkDocs, rename `page_name` to `page_path` to allow nested pages ([6087394](https://github.com/pawamoy/mkdocs-coverage/commit/60873943a87c88349956efd9fd854f20ff134968) by Timothée Mazzucotelli). 19 | 20 | ## [1.0.0](https://github.com/pawamoy/mkdocs-coverage/releases/tag/1.0.0) - 2023-08-02 21 | 22 | [Compare with 0.2.7](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.7...1.0.0) 23 | 24 | ### Breaking Changes 25 | 26 | - Drop support for Python 3.7 27 | 28 | ### Code Refactoring 29 | 30 | - Stop using deprecated warning filter ([fb4d9e6](https://github.com/pawamoy/mkdocs-coverage/commit/fb4d9e6f7b34ecc66c596b7dc4f475a44ce0404c) by Timothée Mazzucotelli). 31 | 32 | ## [0.2.7](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.7) - 2023-04-11 33 | 34 | [Compare with 0.2.6](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.6...0.2.7) 35 | 36 | ### Code Refactoring 37 | 38 | - Stop using deprecated distutils ([47c129c](https://github.com/pawamoy/mkdocs-coverage/commit/47c129ce783cc5d908ec946d19010adb059fed0d) by Timothée Mazzucotelli). 39 | 40 | ## [0.2.6](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.6) - 2022-11-13 41 | 42 | [Compare with 0.2.5](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.5...0.2.6) 43 | 44 | ### Bug Fixes 45 | - Fix iframe width for recent Material versions ([67c530b](https://github.com/pawamoy/mkdocs-coverage/commit/67c530be834f2e0af251d3bc1db5138a54e6de72) by Timothée Mazzucotelli). 46 | 47 | 48 | ## [0.2.5](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.5) - 2021-12-16 49 | 50 | [Compare with 0.2.4](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.4...0.2.5) 51 | 52 | ### Bug Fixes 53 | - Support no directory URLs ([e427be0](https://github.com/pawamoy/mkdocs-coverage/commit/e427be0d8089629c23fba1879fb06fb4715d00e7) by Timothée Mazzucotelli). [Issue #5](https://github.com/pawamoy/mkdocs-coverage/issues/5) 54 | 55 | 56 | ## [0.2.4](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.4) - 2021-05-20 57 | 58 | [Compare with 0.2.3](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.3...0.2.4) 59 | 60 | ### Bug Fixes 61 | - Reset iframe height between page changes ([5519c13](https://github.com/pawamoy/mkdocs-coverage/commit/5519c1352759f36b5ff3e1f800ac41fd12cd4acb) by Timothée Mazzucotelli). [Issue #1](https://github.com/pawamoy/mkdocs-coverage/issues/1) 62 | 63 | 64 | ## [0.2.3](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.3) - 2021-05-16 65 | 66 | [Compare with 0.2.2](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.2...0.2.3) 67 | 68 | ### Packaging 69 | 70 | - Don't restrict supported Python versions to less than 3.10. 71 | 72 | 73 | ## [0.2.2](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.2) - 2021-05-06 74 | 75 | [Compare with 0.2.1](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.1...0.2.2) 76 | 77 | ### Packaging 78 | 79 | - Switch to PDM as project management tool. 80 | - Stop including README.md and pyproject.toml in wheels. It was causing errors in PDM and Poetry when installed in parallel. 81 | 82 | 83 | ## [0.2.1](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.1) - 2021-02-03 84 | 85 | [Compare with 0.2.0](https://github.com/pawamoy/mkdocs-coverage/compare/0.2.0...0.2.1) 86 | 87 | ### Bug Fixes 88 | - Don't replace `index.html` everywhere ([ca1da70](https://github.com/pawamoy/mkdocs-coverage/commit/ca1da7003282b20af4cda72ae0ae62849dab1f63) by Timothée Mazzucotelli). [Issue #2](https://github.com/pawamoy/mkdocs-coverage/issues/2) 89 | 90 | 91 | ## [0.2.0](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.2.0) - 2021-02-03 92 | 93 | [Compare with 0.1.0](https://github.com/pawamoy/mkdocs-coverage/compare/0.1.0...0.2.0) 94 | 95 | ### Features 96 | - Implement coverage integration ([b52ac1d](https://github.com/pawamoy/mkdocs-coverage/commit/b52ac1def13c2dda648f4021b3d81f0e850001e4) by Timothée Mazzucotelli). 97 | 98 | 99 | ## [0.1.0](https://github.com/pawamoy/mkdocs-coverage/releases/tag/0.1.0) - 2021-02-03 100 | 101 | [Compare with first commit](https://github.com/pawamoy/mkdocs-coverage/compare/de2b9feab0e3f1a8ff8809a5ef9e9da55e201838...0.1.0) 102 | 103 | ### Features 104 | - Skeleton ([de2b9fe](https://github.com/pawamoy/mkdocs-coverage/commit/de2b9feab0e3f1a8ff8809a5ef9e9da55e201838) by Timothée Mazzucotelli). 105 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | dev@pawamoy.fr. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! 4 | Every little bit helps, and credit will always be given. 5 | 6 | ## Environment setup 7 | 8 | Nothing easier! 9 | 10 | Fork and clone the repository, then: 11 | 12 | ```bash 13 | cd mkdocs-coverage 14 | make setup 15 | ``` 16 | 17 | > NOTE: 18 | > If it fails for some reason, 19 | > you'll need to install 20 | > [uv](https://github.com/astral-sh/uv) 21 | > manually. 22 | > 23 | > You can install it with: 24 | > 25 | > ```bash 26 | > python3 -m pip install --user pipx 27 | > pipx install uv 28 | > ``` 29 | > 30 | > Now you can try running `make setup` again, 31 | > or simply `uv install`. 32 | 33 | You now have the dependencies installed. 34 | 35 | Run `make help` to see all the available actions! 36 | 37 | ## Tasks 38 | 39 | This project uses [duty](https://github.com/pawamoy/duty) to run tasks. 40 | A Makefile is also provided. The Makefile will try to run certain tasks 41 | on multiple Python versions. If for some reason you don't want to run the task 42 | on multiple Python versions, you run the task directly with `make run duty TASK`. 43 | 44 | The Makefile detects if a virtual environment is activated, 45 | so `make` will work the same with the virtualenv activated or not. 46 | 47 | If you work in VSCode, we provide 48 | [an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup) 49 | for the project. 50 | 51 | ## Development 52 | 53 | As usual: 54 | 55 | 1. create a new branch: `git switch -c feature-or-bugfix-name` 56 | 1. edit the code and/or the documentation 57 | 58 | **Before committing:** 59 | 60 | 1. run `make format` to auto-format the code 61 | 1. run `make check` to check everything (fix any warning) 62 | 1. run `make test` to run the tests (fix any issue) 63 | 1. if you updated the documentation or the project dependencies: 64 | 1. run `make docs` 65 | 1. go to http://localhost:8000 and check that everything looks good 66 | 1. follow our [commit message convention](#commit-message-convention) 67 | 68 | If you are unsure about how to fix or ignore a warning, 69 | just let the continuous integration fail, 70 | and we will help you during review. 71 | 72 | Don't bother updating the changelog, we will take care of this. 73 | 74 | ## Commit message convention 75 | 76 | Commit messages must follow our convention based on the 77 | [Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) 78 | or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html): 79 | 80 | ``` 81 | [(scope)]: Subject 82 | 83 | [Body] 84 | ``` 85 | 86 | **Subject and body must be valid Markdown.** 87 | Subject must have proper casing (uppercase for first letter 88 | if it makes sense), but no dot at the end, and no punctuation 89 | in general. 90 | 91 | Scope and body are optional. Type can be: 92 | 93 | - `build`: About packaging, building wheels, etc. 94 | - `chore`: About packaging or repo/files management. 95 | - `ci`: About Continuous Integration. 96 | - `deps`: Dependencies update. 97 | - `docs`: About documentation. 98 | - `feat`: New feature. 99 | - `fix`: Bug fix. 100 | - `perf`: About performance. 101 | - `refactor`: Changes that are not features or bug fixes. 102 | - `style`: A change in code style/format. 103 | - `tests`: About tests. 104 | 105 | If you write a body, please add trailers at the end 106 | (for example issues and PR references, or co-authors), 107 | without relying on GitHub's flavored Markdown: 108 | 109 | ``` 110 | Body. 111 | 112 | Issue #10: https://github.com/namespace/project/issues/10 113 | Related to PR namespace/other-project#15: https://github.com/namespace/other-project/pull/15 114 | ``` 115 | 116 | These "trailers" must appear at the end of the body, 117 | without any blank lines between them. The trailer title 118 | can contain any character except colons `:`. 119 | We expect a full URI for each trailer, not just GitHub autolinks 120 | (for example, full GitHub URLs for commits and issues, 121 | not the hash or the #issue-number). 122 | 123 | We do not enforce a line length on commit messages summary and body, 124 | but please avoid very long summaries, and very long lines in the body, 125 | unless they are part of code blocks that must not be wrapped. 126 | 127 | ## Pull requests guidelines 128 | 129 | Link to any related issue in the Pull Request message. 130 | 131 | During the review, we recommend using fixups: 132 | 133 | ```bash 134 | # SHA is the SHA of the commit you want to fix 135 | git commit --fixup=SHA 136 | ``` 137 | 138 | Once all the changes are approved, you can squash your commits: 139 | 140 | ```bash 141 | git rebase -i --autosquash main 142 | ``` 143 | 144 | And force-push: 145 | 146 | ```bash 147 | git push -f 148 | ``` 149 | 150 | If this seems all too complicated, you can push or force-push each new commit, 151 | and we will squash them ourselves if needed, before merging. 152 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Credits 6 | These projects were used to build `mkdocs-coverage`. **Thank you!** 7 | 8 | [`python`](https://www.python.org/) | 9 | [`poetry`](https://poetry.eustace.io/) | 10 | [`copier-poetry`](https://github.com/pawamoy/copier-poetry) 11 | 12 | ### Direct dependencies 13 | [`autoflake`](https://github.com/myint/autoflake) | 14 | [`black`](https://github.com/psf/black) | 15 | [`duty`](https://github.com/pawamoy/duty) | 16 | [`flake8-black`](https://github.com/peterjc/flake8-black) | 17 | [`flake8-builtins`](https://github.com/gforcada/flake8-builtins) | 18 | [`flake8-pytest-style`](https://pypi.org/project/flake8-pytest-style) | 19 | [`flake8-tidy-imports`](https://github.com/adamchainz/flake8-tidy-imports) | 20 | [`flake8-variables-names`](https://github.com/best-doctor/flake8-variables-names) | 21 | [`flakehell`](None) | 22 | [`git-changelog`](https://github.com/pawamoy/git-changelog) | 23 | [`httpx`](https://github.com/encode/httpx) | 24 | [`ipython`](https://ipython.org) | 25 | [`isort`](https://github.com/timothycrosley/isort) | 26 | [`jinja2-cli`](https://github.com/mattrobenolt/jinja2-cli) | 27 | [`markdown-include`](https://github.com/cmacmackin/markdown-include/) | 28 | [`mkdocs`](https://www.mkdocs.org) | 29 | [`mkdocs-material`](https://squidfunk.github.io/mkdocs-material/) | 30 | [`mkdocstrings`](https://github.com/pawamoy/mkdocstrings) | 31 | [`mypy`](http://www.mypy-lang.org/) | 32 | [`pytest`](https://docs.pytest.org/en/latest/) | 33 | [`pytest-cov`](https://github.com/pytest-dev/pytest-cov) | 34 | [`pytest-randomly`](https://github.com/pytest-dev/pytest-randomly) | 35 | [`pytest-sugar`](http://pivotfinland.com/pytest-sugar/) | 36 | [`pytest-xdist`](https://github.com/pytest-dev/pytest-xdist) | 37 | [`toml`](https://github.com/uiri/toml) | 38 | [`wemake-python-styleguide`](https://wemake-python-stylegui.de) 39 | 40 | ### Indirect dependencies 41 | [`ansimarkup`](https://github.com/gvalkov/python-ansimarkup) | 42 | [`apipkg`](https://github.com/pytest-dev/apipkg) | 43 | [`appdirs`](http://github.com/ActiveState/appdirs) | 44 | [`appnope`](http://github.com/minrk/appnope) | 45 | [`astor`](https://github.com/berkerpeksag/astor) | 46 | [`atomicwrites`](https://github.com/untitaker/python-atomicwrites) | 47 | [`attrs`](https://www.attrs.org/) | 48 | [`backcall`](https://github.com/takluyver/backcall) | 49 | [`bandit`](https://bandit.readthedocs.io/en/latest/) | 50 | [`certifi`](https://certifiio.readthedocs.io/en/latest/) | 51 | [`click`](https://palletsprojects.com/p/click/) | 52 | [`colorama`](https://github.com/tartley/colorama) | 53 | [`contextvars`](http://github.com/MagicStack/contextvars) | 54 | [`coverage`](https://github.com/nedbat/coveragepy) | 55 | [`darglint`](None) | 56 | [`dataclasses`](https://github.com/ericvsmith/dataclasses) | 57 | [`decorator`](https://github.com/micheles/decorator) | 58 | [`docutils`](http://docutils.sourceforge.net/) | 59 | [`entrypoints`](https://github.com/takluyver/entrypoints) | 60 | [`eradicate`](https://github.com/myint/eradicate) | 61 | [`execnet`](https://execnet.readthedocs.io/en/latest/) | 62 | [`failprint`](https://github.com/pawamoy/failprint) | 63 | [`flake8`](https://gitlab.com/pycqa/flake8) | 64 | [`flake8-bandit`](https://github.com/tylerwince/flake8-bandit) | 65 | [`flake8-broken-line`](https://github.com/sobolevn/flake8-broken-line) | 66 | [`flake8-bugbear`](https://github.com/PyCQA/flake8-bugbear) | 67 | [`flake8-commas`](https://github.com/PyCQA/flake8-commas/) | 68 | [`flake8-comprehensions`](https://github.com/adamchainz/flake8-comprehensions) | 69 | [`flake8-debugger`](https://github.com/jbkahn/flake8-debugger) | 70 | [`flake8-docstrings`](https://gitlab.com/pycqa/flake8-docstrings) | 71 | [`flake8-eradicate`](https://github.com/sobolevn/flake8-eradicate) | 72 | [`flake8-isort`](https://github.com/gforcada/flake8-isort) | 73 | [`flake8-plugin-utils`](https://pypi.org/project/flake8-plugin-utils) | 74 | [`flake8-polyfill`](https://gitlab.com/pycqa/flake8-polyfill) | 75 | [`flake8-quotes`](http://github.com/zheller/flake8-quotes/) | 76 | [`flake8-rst-docstrings`](https://github.com/peterjc/flake8-rst-docstrings) | 77 | [`flake8-string-format`](https://github.com/xZise/flake8-string-format) | 78 | [`future`](https://python-future.org) | 79 | [`gitdb`](https://github.com/gitpython-developers/gitdb) | 80 | [`GitPython`](https://github.com/gitpython-developers/GitPython) | 81 | [`h11`](https://github.com/python-hyper/h11) | 82 | [`httpcore`](https://github.com/encode/httpcore) | 83 | [`idna`](https://github.com/kjd/idna) | 84 | [`immutables`](https://github.com/MagicStack/immutables) | 85 | [`importlib-metadata`](https://github.com/python/importlib_metadata) | 86 | [`iniconfig`](http://github.com/RonnyPfannschmidt/iniconfig) | 87 | [`ipython-genutils`](http://ipython.org) | 88 | [`jedi`](https://github.com/davidhalter/jedi) | 89 | [`Jinja2`](https://palletsprojects.com/p/jinja/) | 90 | [`joblib`](https://joblib.readthedocs.io) | 91 | [`livereload`](https://github.com/lepture/python-livereload) | 92 | [`lunr`](https://github.com/yeraydiazdiaz/lunr.py) | 93 | [`Markdown`](https://Python-Markdown.github.io/) | 94 | [`MarkupSafe`](https://palletsprojects.com/p/markupsafe/) | 95 | [`mccabe`](https://github.com/pycqa/mccabe) | 96 | [`mkdocs-material-extensions`](https://github.com/facelessuser/mkdocs-material-extensions) | 97 | [`mypy-extensions`](https://github.com/python/mypy_extensions) | 98 | [`nltk`](http://nltk.org/) | 99 | [`packaging`](https://github.com/pypa/packaging) | 100 | [`parso`](https://github.com/davidhalter/parso) | 101 | [`pathspec`](https://github.com/cpburnz/python-path-specification) | 102 | [`pbr`](https://docs.openstack.org/pbr/latest/) | 103 | [`pep8-naming`](https://github.com/PyCQA/pep8-naming) | 104 | [`pexpect`](https://pexpect.readthedocs.io/) | 105 | [`pickleshare`](https://github.com/pickleshare/pickleshare) | 106 | [`pluggy`](https://github.com/pytest-dev/pluggy) | 107 | [`prompt-toolkit`](https://github.com/prompt-toolkit/python-prompt-toolkit) | 108 | [`ptyprocess`](https://github.com/pexpect/ptyprocess) | 109 | [`py`](https://py.readthedocs.io/) | 110 | [`pycodestyle`](https://pycodestyle.readthedocs.io/) | 111 | [`pydocstyle`](https://github.com/PyCQA/pydocstyle/) | 112 | [`pyflakes`](https://github.com/PyCQA/pyflakes) | 113 | [`Pygments`](https://pygments.org/) | 114 | [`pymdown-extensions`](https://github.com/facelessuser/pymdown-extensions) | 115 | [`pyparsing`](https://github.com/pyparsing/pyparsing/) | 116 | [`pytest-forked`](https://github.com/pytest-dev/pytest-forked) | 117 | [`pytkdocs`](https://github.com/pawamoy/pytkdocs) | 118 | [`PyYAML`](https://pyyaml.org/) | 119 | [`regex`](https://bitbucket.org/mrabarnett/mrab-regex) | 120 | [`restructuredtext-lint`](https://github.com/twolfson/restructuredtext-lint) | 121 | [`rfc3986`](http://rfc3986.readthedocs.io) | 122 | [`six`](https://github.com/benjaminp/six) | 123 | [`smmap`](https://github.com/gitpython-developers/smmap) | 124 | [`sniffio`](https://github.com/python-trio/sniffio) | 125 | [`snowballstemmer`](https://github.com/snowballstem/snowball) | 126 | [`stevedore`](https://docs.openstack.org/stevedore/latest/) | 127 | [`termcolor`](http://pypi.python.org/pypi/termcolor) | 128 | [`testfixtures`](https://github.com/Simplistix/testfixtures) | 129 | [`tornado`](http://www.tornadoweb.org/) | 130 | [`tqdm`](https://github.com/tqdm/tqdm) | 131 | [`traitlets`](http://ipython.org) | 132 | [`typed-ast`](https://github.com/python/typed_ast) | 133 | [`typing-extensions`](https://github.com/python/typing/blob/master/typing_extensions/README.rst) | 134 | [`urllib3`](https://urllib3.readthedocs.io/) | 135 | [`wcwidth`](https://github.com/jquast/wcwidth) | 136 | [`zipp`](https://github.com/jaraco/zipp) 137 | 138 | **[More credits from the author](http://pawamoy.github.io/credits/)** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2021, Timothée Mazzucotelli 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If you have `direnv` loaded in your shell, and allow it in the repository, 2 | # the `make` command will point at the `scripts/make` shell script. 3 | # This Makefile is just here to allow auto-completion in the terminal. 4 | 5 | actions = \ 6 | allrun \ 7 | changelog \ 8 | check \ 9 | check-api \ 10 | check-docs \ 11 | check-quality \ 12 | check-types \ 13 | clean \ 14 | coverage \ 15 | docs \ 16 | docs-deploy \ 17 | format \ 18 | help \ 19 | multirun \ 20 | release \ 21 | run \ 22 | setup \ 23 | test \ 24 | vscode 25 | 26 | .PHONY: $(actions) 27 | $(actions): 28 | @python scripts/make "$@" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MkDocs Coverage Plugin 2 | 3 | [![ci](https://github.com/pawamoy/mkdocs-coverage/workflows/ci/badge.svg)](https://github.com/pawamoy/mkdocs-coverage/actions?query=workflow%3Aci) 4 | [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/mkdocs-coverage/) 5 | [![pypi version](https://img.shields.io/pypi/v/mkdocs-coverage.svg)](https://pypi.org/project/mkdocs-coverage/) 6 | [![gitpod](https://img.shields.io/badge/gitpod-workspace-708FCC.svg?style=flat)](https://gitpod.io/#https://github.com/pawamoy/mkdocs-coverage) 7 | [![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#mkdocs-coverage:gitter.im) 8 | 9 | MkDocs plugin to integrate your coverage HTML report into your site. 10 | 11 | ## Installation 12 | 13 | With `pip`: 14 | 15 | ```bash 16 | pip install mkdocs-coverage 17 | ``` 18 | 19 | With [`pipx`](https://github.com/pipxproject/pipx): 20 | 21 | ```bash 22 | python3.8 -m pip install --user pipx 23 | pipx install mkdocs-coverage 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```yaml 29 | # mkdocs.yml 30 | nav: 31 | - Coverage report: coverage.md 32 | 33 | plugins: 34 | - coverage: 35 | page_path: coverage # default 36 | html_report_dir: htmlcov # default 37 | ``` 38 | 39 | The page path can be nested: 40 | 41 | ```yaml 42 | # mkdocs.yml 43 | nav: 44 | - Coverage report: dev/reports/coverage.md 45 | 46 | plugins: 47 | - coverage: 48 | page_path: dev/reports/coverage 49 | ``` 50 | 51 | Now serve your documentation, 52 | and go to http://localhost:8000/coverage/ 53 | to see your coverage report! 54 | 55 | ![coverage index](https://user-images.githubusercontent.com/3999221/106802970-f4376a80-6663-11eb-8665-e9e09f0f4ac0.png) 56 | ![coverage module](https://user-images.githubusercontent.com/3999221/106803017-fe596900-6663-11eb-9df9-973755c5b63e.png) 57 | -------------------------------------------------------------------------------- /config/coverage.ini: -------------------------------------------------------------------------------- 1 | [coverage:run] 2 | branch = true 3 | parallel = true 4 | source = 5 | src/ 6 | tests/ 7 | 8 | [coverage:paths] 9 | equivalent = 10 | src/ 11 | .venv/lib/*/site-packages/ 12 | .venvs/*/lib/*/site-packages/ 13 | 14 | [coverage:report] 15 | precision = 2 16 | omit = 17 | src/*/__init__.py 18 | tests/__init__.py 19 | exclude_lines = 20 | pragma: no cover 21 | if TYPE_CHECKING 22 | 23 | [coverage:json] 24 | output = htmlcov/coverage.json 25 | -------------------------------------------------------------------------------- /config/git-changelog.toml: -------------------------------------------------------------------------------- 1 | bump = "auto" 2 | convention = "angular" 3 | in-place = true 4 | output = "CHANGELOG.md" 5 | parse-refs = false 6 | parse-trailers = true 7 | sections = ["build", "deps", "feat", "fix", "refactor"] 8 | template = "keepachangelog" 9 | versioning = "pep440" 10 | -------------------------------------------------------------------------------- /config/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = true 3 | exclude = tests/fixtures/ 4 | warn_unused_ignores = true 5 | show_error_codes = true 6 | -------------------------------------------------------------------------------- /config/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = 3 | test_*.py 4 | addopts = 5 | --cov 6 | --cov-config config/coverage.ini 7 | testpaths = 8 | tests 9 | 10 | # action:message_regex:warning_class:module_regex:line 11 | filterwarnings = 12 | error 13 | # TODO: remove once pytest-xdist 4 is released 14 | ignore:.*rsyncdir:DeprecationWarning:xdist 15 | -------------------------------------------------------------------------------- /config/ruff.toml: -------------------------------------------------------------------------------- 1 | target-version = "py38" 2 | line-length = 120 3 | 4 | [lint] 5 | exclude = [ 6 | "tests/fixtures/*.py", 7 | ] 8 | select = [ 9 | "A", "ANN", "ARG", 10 | "B", "BLE", 11 | "C", "C4", 12 | "COM", 13 | "D", "DTZ", 14 | "E", "ERA", "EXE", 15 | "F", "FBT", 16 | "G", 17 | "I", "ICN", "INP", "ISC", 18 | "N", 19 | "PGH", "PIE", "PL", "PLC", "PLE", "PLR", "PLW", "PT", "PYI", 20 | "Q", 21 | "RUF", "RSE", "RET", 22 | "S", "SIM", "SLF", 23 | "T", "T10", "T20", "TCH", "TID", "TRY", 24 | "UP", 25 | "W", 26 | "YTT", 27 | ] 28 | ignore = [ 29 | "A001", # Variable is shadowing a Python builtin 30 | "ANN101", # Missing type annotation for self 31 | "ANN102", # Missing type annotation for cls 32 | "ANN204", # Missing return type annotation for special method __str__ 33 | "ANN401", # Dynamically typed expressions (typing.Any) are disallowed 34 | "ARG005", # Unused lambda argument 35 | "C901", # Too complex 36 | "D105", # Missing docstring in magic method 37 | "D417", # Missing argument description in the docstring 38 | "E501", # Line too long 39 | "ERA001", # Commented out code 40 | "G004", # Logging statement uses f-string 41 | "PLR0911", # Too many return statements 42 | "PLR0912", # Too many branches 43 | "PLR0913", # Too many arguments to function call 44 | "PLR0915", # Too many statements 45 | "SLF001", # Private member accessed 46 | "TRY003", # Avoid specifying long messages outside the exception class 47 | ] 48 | 49 | [lint.per-file-ignores] 50 | "src/*/cli.py" = [ 51 | "T201", # Print statement 52 | ] 53 | "src/*/debug.py" = [ 54 | "T201", # Print statement 55 | ] 56 | "scripts/*.py" = [ 57 | "INP001", # File is part of an implicit namespace package 58 | "T201", # Print statement 59 | ] 60 | "tests/*.py" = [ 61 | "ARG005", # Unused lambda argument 62 | "FBT001", # Boolean positional arg in function definition 63 | "PLR2004", # Magic value used in comparison 64 | "S101", # Use of assert detected 65 | ] 66 | 67 | [lint.flake8-quotes] 68 | docstring-quotes = "double" 69 | 70 | [lint.flake8-tidy-imports] 71 | ban-relative-imports = "all" 72 | 73 | [lint.isort] 74 | known-first-party = ["mkdocs_coverage"] 75 | 76 | [lint.pydocstyle] 77 | convention = "google" 78 | 79 | [format] 80 | exclude = [ 81 | "tests/fixtures/*.py", 82 | ] 83 | docstring-code-format = true 84 | docstring-code-line-length = 80 85 | -------------------------------------------------------------------------------- /config/vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "python (current file)", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal", 10 | "justMyCode": false 11 | }, 12 | { 13 | "name": "docs", 14 | "type": "debugpy", 15 | "request": "launch", 16 | "module": "mkdocs", 17 | "justMyCode": false, 18 | "args": [ 19 | "serve", 20 | "-v" 21 | ] 22 | }, 23 | { 24 | "name": "test", 25 | "type": "debugpy", 26 | "request": "launch", 27 | "module": "pytest", 28 | "justMyCode": false, 29 | "args": [ 30 | "-c=config/pytest.ini", 31 | "-vvv", 32 | "--no-cov", 33 | "--dist=no", 34 | "tests", 35 | "-k=${input:tests_selection}" 36 | ] 37 | } 38 | ], 39 | "inputs": [ 40 | { 41 | "id": "tests_selection", 42 | "type": "promptString", 43 | "description": "Tests selection", 44 | "default": "" 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /config/vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/.venv*/**": true, 4 | "**/.venvs*/**": true, 5 | "**/venv*/**": true 6 | }, 7 | "mypy-type-checker.args": [ 8 | "--config-file=config/mypy.ini" 9 | ], 10 | "python.testing.unittestEnabled": false, 11 | "python.testing.pytestEnabled": true, 12 | "python.testing.pytestArgs": [ 13 | "--config-file=config/pytest.ini" 14 | ], 15 | "ruff.enable": true, 16 | "ruff.format.args": [ 17 | "--config=config/ruff.toml" 18 | ], 19 | "ruff.lint.args": [ 20 | "--config=config/ruff.toml" 21 | ], 22 | "yaml.schemas": { 23 | "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" 24 | }, 25 | "yaml.customTags": [ 26 | "!ENV scalar", 27 | "!ENV sequence", 28 | "!relative scalar", 29 | "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", 30 | "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", 31 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" 32 | ] 33 | } -------------------------------------------------------------------------------- /config/vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "changelog", 6 | "type": "process", 7 | "command": "scripts/make", 8 | "args": ["changelog"] 9 | }, 10 | { 11 | "label": "check", 12 | "type": "process", 13 | "command": "scripts/make", 14 | "args": ["check"] 15 | }, 16 | { 17 | "label": "check-quality", 18 | "type": "process", 19 | "command": "scripts/make", 20 | "args": ["check-quality"] 21 | }, 22 | { 23 | "label": "check-types", 24 | "type": "process", 25 | "command": "scripts/make", 26 | "args": ["check-types"] 27 | }, 28 | { 29 | "label": "check-docs", 30 | "type": "process", 31 | "command": "scripts/make", 32 | "args": ["check-docs"] 33 | }, 34 | { 35 | "label": "check-api", 36 | "type": "process", 37 | "command": "scripts/make", 38 | "args": ["check-api"] 39 | }, 40 | { 41 | "label": "clean", 42 | "type": "process", 43 | "command": "scripts/make", 44 | "args": ["clean"] 45 | }, 46 | { 47 | "label": "docs", 48 | "type": "process", 49 | "command": "scripts/make", 50 | "args": ["docs"] 51 | }, 52 | { 53 | "label": "docs-deploy", 54 | "type": "process", 55 | "command": "scripts/make", 56 | "args": ["docs-deploy"] 57 | }, 58 | { 59 | "label": "format", 60 | "type": "process", 61 | "command": "scripts/make", 62 | "args": ["format"] 63 | }, 64 | { 65 | "label": "release", 66 | "type": "process", 67 | "command": "scripts/make", 68 | "args": ["release", "${input:version}"] 69 | }, 70 | { 71 | "label": "setup", 72 | "type": "process", 73 | "command": "scripts/make", 74 | "args": ["setup"] 75 | }, 76 | { 77 | "label": "test", 78 | "type": "process", 79 | "command": "scripts/make", 80 | "args": ["test", "coverage"], 81 | "group": "test" 82 | }, 83 | { 84 | "label": "vscode", 85 | "type": "process", 86 | "command": "scripts/make", 87 | "args": ["vscode"] 88 | } 89 | ], 90 | "inputs": [ 91 | { 92 | "id": "version", 93 | "type": "promptString", 94 | "description": "Version" 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /devdeps.txt: -------------------------------------------------------------------------------- 1 | # dev 2 | editables>=0.5 3 | 4 | # maintenance 5 | build>=1.2 6 | git-changelog>=2.5 7 | twine>=5.1; python_version < '3.13' 8 | 9 | # ci 10 | duty>=1.4 11 | ruff>=0.4 12 | pytest>=8.2 13 | pytest-cov>=5.0 14 | pytest-randomly>=3.15 15 | pytest-xdist>=3.6 16 | mypy>=1.10 17 | types-markdown>=3.6 18 | types-pyyaml>=6.0 19 | 20 | # docs 21 | black>=24.4 22 | markdown-callouts>=0.4 23 | markdown-exec>=1.8 24 | mkdocs>=1.6 25 | mkdocs-coverage>=1.0 26 | mkdocs-gen-files>=0.5 27 | mkdocs-git-committers-plugin-2>=2.3 28 | mkdocs-literate-nav>=0.6 29 | mkdocs-material>=9.5 30 | mkdocs-minify-plugin>=0.8 31 | mkdocstrings[python]>=0.25 32 | tomli>=2.0; python_version < '3.11' 33 | -------------------------------------------------------------------------------- /docs/.overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block announce %} 4 | 5 | For updates follow @pawamoy on 6 | 7 | 8 | {% include ".icons/fontawesome/brands/mastodon.svg" %} 9 | 10 | Fosstodon 11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --8<-- "CHANGELOG.md" 2 | -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | --8<-- "CODE_OF_CONDUCT.md" 2 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | --8<-- "CONTRIBUTING.md" 2 | -------------------------------------------------------------------------------- /docs/credits.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - toc 4 | --- 5 | 6 | 7 | ```python exec="yes" 8 | --8<-- "scripts/gen_credits.py" 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /docs/css/material.css: -------------------------------------------------------------------------------- 1 | /* More space at the bottom of the page. */ 2 | .md-main__inner { 3 | margin-bottom: 1.5rem; 4 | } 5 | -------------------------------------------------------------------------------- /docs/css/mkdocstrings.css: -------------------------------------------------------------------------------- 1 | /* Indentation. */ 2 | div.doc-contents:not(.first) { 3 | padding-left: 25px; 4 | border-left: .05rem solid var(--md-typeset-table-color); 5 | } 6 | 7 | /* Mark external links as such. */ 8 | a.external::after, 9 | a.autorefs-external::after { 10 | /* https://primer.style/octicons/arrow-up-right-24 */ 11 | mask-image: url('data:image/svg+xml,'); 12 | -webkit-mask-image: url('data:image/svg+xml,'); 13 | content: ' '; 14 | 15 | display: inline-block; 16 | vertical-align: middle; 17 | position: relative; 18 | 19 | height: 1em; 20 | width: 1em; 21 | background-color: currentColor; 22 | } 23 | 24 | a.external:hover::after, 25 | a.autorefs-external:hover::after { 26 | background-color: var(--md-accent-fg-color); 27 | } -------------------------------------------------------------------------------- /docs/gen_credits.py: -------------------------------------------------------------------------------- 1 | """Generate the credits page.""" 2 | 3 | import functools 4 | import re 5 | from itertools import chain 6 | from pathlib import Path 7 | from urllib.request import urlopen 8 | 9 | import mkdocs_gen_files 10 | import toml 11 | from jinja2 import StrictUndefined 12 | from jinja2.sandbox import SandboxedEnvironment 13 | 14 | 15 | def get_credits_data() -> dict: 16 | """Return data used to generate the credits file. 17 | 18 | Returns: 19 | Data required to render the credits template. 20 | """ 21 | project_dir = Path(__file__).parent.parent 22 | metadata = toml.load(project_dir / "pyproject.toml")["project"] 23 | metadata_pdm = toml.load(project_dir / "pyproject.toml")["tool"]["pdm"] 24 | lock_data = toml.load(project_dir / "pdm.lock") 25 | project_name = metadata["name"] 26 | 27 | all_dependencies = chain( 28 | metadata.get("dependencies", []), 29 | chain(*metadata.get("optional-dependencies", {}).values()), 30 | chain(*metadata_pdm.get("dev-dependencies", {}).values()), 31 | ) 32 | direct_dependencies = {re.sub(r"[^\w-].*$", "", dep) for dep in all_dependencies} 33 | direct_dependencies = {dep.lower() for dep in direct_dependencies} 34 | indirect_dependencies = {pkg["name"].lower() for pkg in lock_data["package"]} 35 | indirect_dependencies -= direct_dependencies 36 | 37 | return { 38 | "project_name": project_name, 39 | "direct_dependencies": sorted(direct_dependencies), 40 | "indirect_dependencies": sorted(indirect_dependencies), 41 | "more_credits": "http://pawamoy.github.io/credits/", 42 | } 43 | 44 | 45 | @functools.lru_cache(maxsize=None) 46 | def get_credits(): 47 | """Return credits as Markdown. 48 | 49 | Returns: 50 | The credits page Markdown. 51 | """ 52 | jinja_env = SandboxedEnvironment(undefined=StrictUndefined) 53 | commit = "c78c29caa345b6ace19494a98b1544253cbaf8c1" 54 | template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/credits.md" 55 | template_data = get_credits_data() 56 | template_text = urlopen(template_url).read().decode("utf8") # noqa: S310 57 | return jinja_env.from_string(template_text).render(**template_data) 58 | 59 | 60 | with mkdocs_gen_files.open("credits.md", "w") as fd: 61 | fd.write(get_credits()) 62 | mkdocs_gen_files.set_edit_path("credits.md", "gen_credits.py") 63 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --8<-- "README.md" 2 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ``` 4 | --8<-- "LICENSE" 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema", 3 | "title": "MkDocs plugin to integrate your coverage HTML report into your site.", 4 | "oneOf": [ 5 | { 6 | "markdownDescription": "https://pawamoy.github.io/mkdocs-coverage", 7 | "enum": [ 8 | "coverage" 9 | ] 10 | }, 11 | { 12 | "type": "object", 13 | "properties": { 14 | "coverage": { 15 | "markdownDescription": "https://pawamoy.github.io/mkdocs-coverage", 16 | "type": "object", 17 | "properties": { 18 | "page_name": { 19 | "title": "The name of the page (file) where the coverage report is integrated.", 20 | "type": "string", 21 | "default": "coverage" 22 | }, 23 | "html_report_dir": { 24 | "title": "The path to the HTML report directory generated by coverage.", 25 | "type": "string", 26 | "default": "htmlcov", 27 | "format": "path" 28 | } 29 | }, 30 | "additionalProperties": false 31 | } 32 | }, 33 | "additionalProperties": false 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /duties.py: -------------------------------------------------------------------------------- 1 | """Development tasks.""" 2 | 3 | from __future__ import annotations 4 | 5 | import os 6 | import sys 7 | from contextlib import contextmanager 8 | from importlib.metadata import version as pkgversion 9 | from pathlib import Path 10 | from typing import TYPE_CHECKING, Iterator 11 | 12 | from duty import duty, tools 13 | 14 | if TYPE_CHECKING: 15 | from duty.context import Context 16 | 17 | 18 | PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts")) 19 | PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS) 20 | PY_SRC = " ".join(PY_SRC_LIST) 21 | CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""} 22 | WINDOWS = os.name == "nt" 23 | PTY = not WINDOWS and not CI 24 | MULTIRUN = os.environ.get("MULTIRUN", "0") == "1" 25 | 26 | 27 | def pyprefix(title: str) -> str: # noqa: D103 28 | if MULTIRUN: 29 | prefix = f"(python{sys.version_info.major}.{sys.version_info.minor})" 30 | return f"{prefix:14}{title}" 31 | return title 32 | 33 | 34 | @contextmanager 35 | def material_insiders() -> Iterator[bool]: # noqa: D103 36 | if "+insiders" in pkgversion("mkdocs-material"): 37 | os.environ["MATERIAL_INSIDERS"] = "true" 38 | try: 39 | yield True 40 | finally: 41 | os.environ.pop("MATERIAL_INSIDERS") 42 | else: 43 | yield False 44 | 45 | 46 | @duty 47 | def changelog(ctx: Context, bump: str = "") -> None: 48 | """Update the changelog in-place with latest commits. 49 | 50 | Parameters: 51 | bump: Bump option passed to git-changelog. 52 | """ 53 | ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog") 54 | 55 | 56 | @duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"]) 57 | def check(ctx: Context) -> None: # noqa: ARG001 58 | """Check it all!""" 59 | 60 | 61 | @duty 62 | def check_quality(ctx: Context) -> None: 63 | """Check the code quality.""" 64 | ctx.run( 65 | tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml"), 66 | title=pyprefix("Checking code quality"), 67 | ) 68 | 69 | 70 | @duty 71 | def check_docs(ctx: Context) -> None: 72 | """Check if the documentation builds correctly.""" 73 | Path("htmlcov").mkdir(parents=True, exist_ok=True) 74 | Path("htmlcov/index.html").touch(exist_ok=True) 75 | with material_insiders(): 76 | ctx.run( 77 | tools.mkdocs.build(strict=True, verbose=True), 78 | title=pyprefix("Building documentation"), 79 | ) 80 | 81 | 82 | @duty 83 | def check_types(ctx: Context) -> None: 84 | """Check that the code is correctly typed.""" 85 | ctx.run( 86 | tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"), 87 | title=pyprefix("Type-checking"), 88 | ) 89 | 90 | 91 | @duty 92 | def check_api(ctx: Context, *cli_args: str) -> None: 93 | """Check for API breaking changes.""" 94 | ctx.run( 95 | tools.griffe.check("mkdocs_coverage", search=["src"], color=True).add_args(*cli_args), 96 | title="Checking for API breaking changes", 97 | nofail=True, 98 | ) 99 | 100 | 101 | @duty 102 | def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000) -> None: 103 | """Serve the documentation (localhost:8000). 104 | 105 | Parameters: 106 | host: The host to serve the docs from. 107 | port: The port to serve the docs on. 108 | """ 109 | with material_insiders(): 110 | ctx.run( 111 | tools.mkdocs.serve(dev_addr=f"{host}:{port}").add_args(*cli_args), 112 | title="Serving documentation", 113 | capture=False, 114 | ) 115 | 116 | 117 | @duty 118 | def docs_deploy(ctx: Context) -> None: 119 | """Deploy the documentation to GitHub pages.""" 120 | os.environ["DEPLOY"] = "true" 121 | with material_insiders() as insiders: 122 | if not insiders: 123 | ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") 124 | ctx.run(tools.mkdocs.gh_deploy(), title="Deploying documentation") 125 | 126 | 127 | @duty 128 | def format(ctx: Context) -> None: 129 | """Run formatting tools on the code.""" 130 | ctx.run( 131 | tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True), 132 | title="Auto-fixing code", 133 | ) 134 | ctx.run(tools.ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code") 135 | 136 | 137 | @duty 138 | def build(ctx: Context) -> None: 139 | """Build source and wheel distributions.""" 140 | ctx.run( 141 | tools.build(), 142 | title="Building source and wheel distributions", 143 | pty=PTY, 144 | ) 145 | 146 | 147 | @duty 148 | def publish(ctx: Context) -> None: 149 | """Publish source and wheel distributions to PyPI.""" 150 | if not Path("dist").exists(): 151 | ctx.run("false", title="No distribution files found") 152 | dists = [str(dist) for dist in Path("dist").iterdir()] 153 | ctx.run( 154 | tools.twine.upload(*dists, skip_existing=True), 155 | title="Publishing source and wheel distributions to PyPI", 156 | pty=PTY, 157 | ) 158 | 159 | 160 | @duty(post=["build", "publish", "docs-deploy"]) 161 | def release(ctx: Context, version: str = "") -> None: 162 | """Release a new Python package. 163 | 164 | Parameters: 165 | version: The new version number to use. 166 | """ 167 | if not (version := (version or input("> Version to release: ")).strip()): 168 | ctx.run("false", title="A version must be provided") 169 | ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY) 170 | ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY) 171 | ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY) 172 | ctx.run("git push", title="Pushing commits", pty=False) 173 | ctx.run("git push --tags", title="Pushing tags", pty=False) 174 | 175 | 176 | @duty(silent=True, aliases=["cov"]) 177 | def coverage(ctx: Context) -> None: 178 | """Report coverage as text and HTML.""" 179 | ctx.run(tools.coverage.combine(), nofail=True) 180 | ctx.run(tools.coverage.report(rcfile="config/coverage.ini"), capture=False) 181 | ctx.run(tools.coverage.html(rcfile="config/coverage.ini")) 182 | 183 | 184 | @duty 185 | def test(ctx: Context, *cli_args: str, match: str = "") -> None: 186 | """Run the test suite. 187 | 188 | Parameters: 189 | match: A pytest expression to filter selected tests. 190 | """ 191 | py_version = f"{sys.version_info.major}{sys.version_info.minor}" 192 | os.environ["COVERAGE_FILE"] = f".coverage.{py_version}" 193 | ctx.run( 194 | tools.pytest( 195 | "tests", 196 | config_file="config/pytest.ini", 197 | select=match, 198 | color="yes", 199 | ).add_args("-n", "auto", *cli_args), 200 | title=pyprefix("Running tests"), 201 | ) 202 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "MkDocs Coverage Plugin" 2 | site_description: "MkDocs plugin to integrate your coverage HTML report into your site." 3 | site_url: "https://pawamoy.github.io/mkdocs-coverage" 4 | repo_url: "https://github.com/pawamoy/mkdocs-coverage" 5 | repo_name: "pawamoy/mkdocs-coverage" 6 | site_dir: "site" 7 | watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocs_coverage] 8 | copyright: Copyright © 2021 Timothée Mazzucotelli 9 | edit_uri: edit/main/docs/ 10 | 11 | validation: 12 | omitted_files: warn 13 | absolute_links: warn 14 | unrecognized_links: warn 15 | 16 | nav: 17 | - Home: 18 | - Overview: index.md 19 | - Changelog: changelog.md 20 | - Credits: credits.md 21 | - License: license.md 22 | # defer to gen-files + literate-nav 23 | - API reference: 24 | - MkDocs Coverage Plugin: reference/ 25 | - Development: 26 | - Contributing: contributing.md 27 | - Code of Conduct: code_of_conduct.md 28 | - Coverage report: coverage.md 29 | - Author's website: https://pawamoy.github.io/ 30 | 31 | theme: 32 | name: material 33 | custom_dir: docs/.overrides 34 | icon: 35 | logo: material/currency-sign 36 | features: 37 | - announce.dismiss 38 | - content.action.edit 39 | - content.action.view 40 | - content.code.annotate 41 | - content.code.copy 42 | - content.tooltips 43 | - navigation.footer 44 | - navigation.indexes 45 | - navigation.sections 46 | - navigation.tabs 47 | - navigation.tabs.sticky 48 | - navigation.top 49 | - search.highlight 50 | - search.suggest 51 | - toc.follow 52 | palette: 53 | - media: "(prefers-color-scheme)" 54 | toggle: 55 | icon: material/brightness-auto 56 | name: Switch to light mode 57 | - media: "(prefers-color-scheme: light)" 58 | scheme: default 59 | primary: teal 60 | accent: purple 61 | toggle: 62 | icon: material/weather-sunny 63 | name: Switch to dark mode 64 | - media: "(prefers-color-scheme: dark)" 65 | scheme: slate 66 | primary: black 67 | accent: lime 68 | toggle: 69 | icon: material/weather-night 70 | name: Switch to system preference 71 | 72 | extra_css: 73 | - css/material.css 74 | - css/mkdocstrings.css 75 | 76 | markdown_extensions: 77 | - attr_list 78 | - admonition 79 | - callouts 80 | - footnotes 81 | - pymdownx.emoji: 82 | emoji_index: !!python/name:material.extensions.emoji.twemoji 83 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 84 | - pymdownx.magiclink 85 | - pymdownx.snippets: 86 | base_path: [!relative $config_dir] 87 | check_paths: true 88 | - pymdownx.superfences 89 | - pymdownx.tabbed: 90 | alternate_style: true 91 | slugify: !!python/object/apply:pymdownx.slugs.slugify 92 | kwds: 93 | case: lower 94 | - pymdownx.tasklist: 95 | custom_checkbox: true 96 | - toc: 97 | permalink: "¤" 98 | 99 | plugins: 100 | - search 101 | - markdown-exec 102 | - gen-files: 103 | scripts: 104 | - scripts/gen_ref_nav.py 105 | - literate-nav: 106 | nav_file: SUMMARY.md 107 | - coverage 108 | - mkdocstrings: 109 | handlers: 110 | python: 111 | import: 112 | - https://docs.python.org/3/objects.inv 113 | paths: [src] 114 | options: 115 | docstring_options: 116 | ignore_init_summary: true 117 | docstring_section_style: list 118 | filters: ["!^_"] 119 | heading_level: 1 120 | inherited_members: true 121 | merge_init_into_class: true 122 | separate_signature: true 123 | show_root_heading: true 124 | show_root_full_path: false 125 | show_signature_annotations: true 126 | show_symbol_type_heading: true 127 | show_symbol_type_toc: true 128 | signature_crossrefs: true 129 | summary: true 130 | - git-committers: 131 | enabled: !ENV [DEPLOY, false] 132 | repository: pawamoy/mkdocs-coverage 133 | - minify: 134 | minify_html: !ENV [DEPLOY, false] 135 | - group: 136 | enabled: !ENV [MATERIAL_INSIDERS, false] 137 | plugins: 138 | - typeset 139 | 140 | extra: 141 | social: 142 | - icon: fontawesome/brands/github 143 | link: https://github.com/pawamoy 144 | - icon: fontawesome/brands/mastodon 145 | link: https://fosstodon.org/@pawamoy 146 | - icon: fontawesome/brands/twitter 147 | link: https://twitter.com/pawamoy 148 | - icon: fontawesome/brands/gitter 149 | link: https://gitter.im/mkdocs-coverage/community 150 | - icon: fontawesome/brands/python 151 | link: https://pypi.org/project/mkdocs-coverage/ 152 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pdm-backend"] 3 | build-backend = "pdm.backend" 4 | 5 | [project] 6 | name = "mkdocs-coverage" 7 | description = "MkDocs plugin to integrate your coverage HTML report into your site." 8 | authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}] 9 | license = {text = "ISC"} 10 | readme = "README.md" 11 | requires-python = ">=3.8" 12 | keywords = [] 13 | dynamic = ["version"] 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Intended Audience :: Developers", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3 :: Only", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | "Programming Language :: Python :: 3.13", 26 | "Topic :: Documentation", 27 | "Topic :: Software Development", 28 | "Topic :: Software Development :: Documentation", 29 | "Topic :: Utilities", 30 | "Typing :: Typed", 31 | ] 32 | dependencies = [ 33 | "mkdocs>=1.6", 34 | ] 35 | 36 | [project.urls] 37 | Homepage = "https://pawamoy.github.io/mkdocs-coverage" 38 | Documentation = "https://pawamoy.github.io/mkdocs-coverage" 39 | Changelog = "https://pawamoy.github.io/mkdocs-coverage/changelog" 40 | Repository = "https://github.com/pawamoy/mkdocs-coverage" 41 | Issues = "https://github.com/pawamoy/mkdocs-coverage/issues" 42 | Discussions = "https://github.com/pawamoy/mkdocs-coverage/discussions" 43 | Gitter = "https://gitter.im/mkdocs-coverage/community" 44 | Funding = "https://github.com/sponsors/pawamoy" 45 | 46 | [project.entry-points."mkdocs.plugins"] 47 | coverage = "mkdocs_coverage.plugin:MkDocsCoveragePlugin" 48 | 49 | [tool.pdm] 50 | version = {source = "scm"} 51 | 52 | [tool.pdm.build] 53 | package-dir = "src" 54 | editable-backend = "editables" 55 | source-includes = ["share"] 56 | 57 | [tool.pdm.build.wheel-data] 58 | data = [ 59 | {path = "share/**/*", relative-to = "."}, 60 | ] 61 | -------------------------------------------------------------------------------- /scripts/gen_credits.py: -------------------------------------------------------------------------------- 1 | """Script to generate the project's credits.""" 2 | 3 | from __future__ import annotations 4 | 5 | import os 6 | import sys 7 | from collections import defaultdict 8 | from importlib.metadata import distributions 9 | from itertools import chain 10 | from pathlib import Path 11 | from textwrap import dedent 12 | from typing import Dict, Iterable, Union 13 | 14 | from jinja2 import StrictUndefined 15 | from jinja2.sandbox import SandboxedEnvironment 16 | from packaging.requirements import Requirement 17 | 18 | # TODO: Remove once support for Python 3.10 is dropped. 19 | if sys.version_info >= (3, 11): 20 | import tomllib 21 | else: 22 | import tomli as tomllib 23 | 24 | project_dir = Path(os.getenv("MKDOCS_CONFIG_DIR", ".")) 25 | with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file: 26 | pyproject = tomllib.load(pyproject_file) 27 | project = pyproject["project"] 28 | project_name = project["name"] 29 | with project_dir.joinpath("devdeps.txt").open() as devdeps_file: 30 | devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))] 31 | 32 | PackageMetadata = Dict[str, Union[str, Iterable[str]]] 33 | Metadata = Dict[str, PackageMetadata] 34 | 35 | 36 | def _merge_fields(metadata: dict) -> PackageMetadata: 37 | fields = defaultdict(list) 38 | for header, value in metadata.items(): 39 | fields[header.lower()].append(value.strip()) 40 | return { 41 | field: value if len(value) > 1 or field in ("classifier", "requires-dist") else value[0] 42 | for field, value in fields.items() 43 | } 44 | 45 | 46 | def _norm_name(name: str) -> str: 47 | return name.replace("_", "-").replace(".", "-").lower() 48 | 49 | 50 | def _requirements(deps: list[str]) -> dict[str, Requirement]: 51 | return {_norm_name((req := Requirement(dep)).name): req for dep in deps} 52 | 53 | 54 | def _extra_marker(req: Requirement) -> str | None: 55 | if not req.marker: 56 | return None 57 | try: 58 | return next(marker[2].value for marker in req.marker._markers if getattr(marker[0], "value", None) == "extra") 59 | except StopIteration: 60 | return None 61 | 62 | 63 | def _get_metadata() -> Metadata: 64 | metadata = {} 65 | for pkg in distributions(): 66 | name = _norm_name(pkg.name) # type: ignore[attr-defined,unused-ignore] 67 | metadata[name] = _merge_fields(pkg.metadata) # type: ignore[arg-type] 68 | metadata[name]["spec"] = set() 69 | metadata[name]["extras"] = set() 70 | metadata[name].setdefault("summary", "") 71 | _set_license(metadata[name]) 72 | return metadata 73 | 74 | 75 | def _set_license(metadata: PackageMetadata) -> None: 76 | license_field = metadata.get("license-expression", metadata.get("license", "")) 77 | license_name = license_field if isinstance(license_field, str) else " + ".join(license_field) 78 | check_classifiers = license_name in ("UNKNOWN", "Dual License", "") or license_name.count("\n") 79 | if check_classifiers: 80 | license_names = [] 81 | for classifier in metadata["classifier"]: 82 | if classifier.startswith("License ::"): 83 | license_names.append(classifier.rsplit("::", 1)[1].strip()) 84 | license_name = " + ".join(license_names) 85 | metadata["license"] = license_name or "?" 86 | 87 | 88 | def _get_deps(base_deps: dict[str, Requirement], metadata: Metadata) -> Metadata: 89 | deps = {} 90 | for dep_name, dep_req in base_deps.items(): 91 | if dep_name not in metadata or dep_name == "mkdocs-coverage": 92 | continue 93 | metadata[dep_name]["spec"] |= {str(spec) for spec in dep_req.specifier} # type: ignore[operator] 94 | metadata[dep_name]["extras"] |= dep_req.extras # type: ignore[operator] 95 | deps[dep_name] = metadata[dep_name] 96 | 97 | again = True 98 | while again: 99 | again = False 100 | for pkg_name in metadata: 101 | if pkg_name in deps: 102 | for pkg_dependency in metadata[pkg_name].get("requires-dist", []): 103 | requirement = Requirement(pkg_dependency) 104 | dep_name = _norm_name(requirement.name) 105 | extra_marker = _extra_marker(requirement) 106 | if ( 107 | dep_name in metadata 108 | and dep_name not in deps 109 | and dep_name != project["name"] 110 | and (not extra_marker or extra_marker in deps[pkg_name]["extras"]) 111 | ): 112 | metadata[dep_name]["spec"] |= {str(spec) for spec in requirement.specifier} # type: ignore[operator] 113 | deps[dep_name] = metadata[dep_name] 114 | again = True 115 | 116 | return deps 117 | 118 | 119 | def _render_credits() -> str: 120 | metadata = _get_metadata() 121 | dev_dependencies = _get_deps(_requirements(devdeps), metadata) 122 | prod_dependencies = _get_deps( 123 | _requirements( 124 | chain( # type: ignore[arg-type] 125 | project.get("dependencies", []), 126 | chain(*project.get("optional-dependencies", {}).values()), 127 | ), 128 | ), 129 | metadata, 130 | ) 131 | 132 | template_data = { 133 | "project_name": project_name, 134 | "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"]).lower()), 135 | "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"]).lower()), 136 | "more_credits": "http://pawamoy.github.io/credits/", 137 | } 138 | template_text = dedent( 139 | """ 140 | # Credits 141 | 142 | These projects were used to build *{{ project_name }}*. **Thank you!** 143 | 144 | [Python](https://www.python.org/) | 145 | [uv](https://github.com/astral-sh/uv) | 146 | [copier-uv](https://github.com/pawamoy/copier-uv) 147 | 148 | {% macro dep_line(dep) -%} 149 | [{{ dep.name }}](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec|sort(reverse=True)|join(", ") ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }} 150 | {%- endmacro %} 151 | 152 | {% if prod_dependencies -%} 153 | ### Runtime dependencies 154 | 155 | Project | Summary | Version (accepted) | Version (last resolved) | License 156 | ------- | ------- | ------------------ | ----------------------- | ------- 157 | {% for dep in prod_dependencies -%} 158 | {{ dep_line(dep) }} 159 | {% endfor %} 160 | 161 | {% endif -%} 162 | {% if dev_dependencies -%} 163 | ### Development dependencies 164 | 165 | Project | Summary | Version (accepted) | Version (last resolved) | License 166 | ------- | ------- | ------------------ | ----------------------- | ------- 167 | {% for dep in dev_dependencies -%} 168 | {{ dep_line(dep) }} 169 | {% endfor %} 170 | 171 | {% endif -%} 172 | {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %} 173 | """, 174 | ) 175 | jinja_env = SandboxedEnvironment(undefined=StrictUndefined) 176 | return jinja_env.from_string(template_text).render(**template_data) 177 | 178 | 179 | print(_render_credits()) 180 | -------------------------------------------------------------------------------- /scripts/gen_ref_nav.py: -------------------------------------------------------------------------------- 1 | """Generate the code reference pages and navigation.""" 2 | 3 | from pathlib import Path 4 | 5 | import mkdocs_gen_files 6 | 7 | nav = mkdocs_gen_files.Nav() 8 | mod_symbol = '' 9 | 10 | root = Path(__file__).parent.parent 11 | src = root / "src" 12 | 13 | for path in sorted(src.rglob("*.py")): 14 | module_path = path.relative_to(src).with_suffix("") 15 | doc_path = path.relative_to(src).with_suffix(".md") 16 | full_doc_path = Path("reference", doc_path) 17 | 18 | parts = tuple(module_path.parts) 19 | 20 | if parts[-1] == "__init__": 21 | parts = parts[:-1] 22 | doc_path = doc_path.with_name("index.md") 23 | full_doc_path = full_doc_path.with_name("index.md") 24 | elif parts[-1].startswith("_"): 25 | continue 26 | 27 | nav_parts = [f"{mod_symbol} {part}" for part in parts] 28 | nav[tuple(nav_parts)] = doc_path.as_posix() 29 | 30 | with mkdocs_gen_files.open(full_doc_path, "w") as fd: 31 | ident = ".".join(parts) 32 | fd.write(f"---\ntitle: {ident}\n---\n\n::: {ident}") 33 | 34 | mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path.relative_to(root)) 35 | 36 | with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: 37 | nav_file.writelines(nav.build_literate_nav()) 38 | -------------------------------------------------------------------------------- /scripts/make: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Management commands.""" 3 | 4 | import os 5 | import shutil 6 | import subprocess 7 | import sys 8 | from contextlib import contextmanager 9 | from pathlib import Path 10 | from typing import Any, Iterator 11 | 12 | PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split() 13 | 14 | exe = "" 15 | prefix = "" 16 | 17 | 18 | def shell(cmd: str) -> None: 19 | """Run a shell command.""" 20 | subprocess.run(cmd, shell=True, check=True) # noqa: S602 21 | 22 | 23 | @contextmanager 24 | def environ(**kwargs: str) -> Iterator[None]: 25 | """Temporarily set environment variables.""" 26 | original = dict(os.environ) 27 | os.environ.update(kwargs) 28 | try: 29 | yield 30 | finally: 31 | os.environ.clear() 32 | os.environ.update(original) 33 | 34 | 35 | def uv_install() -> None: 36 | """Install dependencies using uv.""" 37 | uv_opts = "" 38 | if "UV_RESOLUTION" in os.environ: 39 | uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}" 40 | cmd = f"uv pip compile {uv_opts} pyproject.toml devdeps.txt | uv pip install -r -" 41 | shell(cmd) 42 | if "CI" not in os.environ: 43 | shell("uv pip install --no-deps -e .") 44 | else: 45 | shell("uv pip install --no-deps .") 46 | 47 | 48 | def setup() -> None: 49 | """Setup the project.""" 50 | if not shutil.which("uv"): 51 | raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv") 52 | 53 | print("Installing dependencies (default environment)") # noqa: T201 54 | default_venv = Path(".venv") 55 | if not default_venv.exists(): 56 | shell("uv venv --python python") 57 | uv_install() 58 | 59 | if PYTHON_VERSIONS: 60 | for version in PYTHON_VERSIONS: 61 | print(f"\nInstalling dependencies (python{version})") # noqa: T201 62 | venv_path = Path(f".venvs/{version}") 63 | if not venv_path.exists(): 64 | shell(f"uv venv --python {version} {venv_path}") 65 | with environ(VIRTUAL_ENV=str(venv_path.resolve())): 66 | uv_install() 67 | 68 | 69 | def activate(path: str) -> None: 70 | """Activate a virtual environment.""" 71 | global exe, prefix # noqa: PLW0603 72 | 73 | if (bin := Path(path, "bin")).exists(): 74 | activate_script = bin / "activate_this.py" 75 | elif (scripts := Path(path, "Scripts")).exists(): 76 | activate_script = scripts / "activate_this.py" 77 | exe = ".exe" 78 | prefix = f"{path}/Scripts/" 79 | else: 80 | raise ValueError(f"make: activate: Cannot find activation script in {path}") 81 | 82 | if not activate_script.exists(): 83 | raise ValueError(f"make: activate: Cannot find activation script in {path}") 84 | 85 | exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102 86 | 87 | 88 | def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None: 89 | """Run a command in a virtual environment.""" 90 | kwargs = {"check": True, **kwargs} 91 | if version == "default": 92 | activate(".venv") 93 | subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510 94 | else: 95 | activate(f".venvs/{version}") 96 | os.environ["MULTIRUN"] = "1" 97 | subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510 98 | 99 | 100 | def multirun(cmd: str, *args: str, **kwargs: Any) -> None: 101 | """Run a command for all configured Python versions.""" 102 | if PYTHON_VERSIONS: 103 | for version in PYTHON_VERSIONS: 104 | run(version, cmd, *args, **kwargs) 105 | else: 106 | run("default", cmd, *args, **kwargs) 107 | 108 | 109 | def allrun(cmd: str, *args: str, **kwargs: Any) -> None: 110 | """Run a command in all virtual environments.""" 111 | run("default", cmd, *args, **kwargs) 112 | if PYTHON_VERSIONS: 113 | multirun(cmd, *args, **kwargs) 114 | 115 | 116 | def clean() -> None: 117 | """Delete build artifacts and cache files.""" 118 | paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"] 119 | for path in paths_to_clean: 120 | shell(f"rm -rf {path}") 121 | 122 | cache_dirs = [".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"] 123 | for dirpath in Path(".").rglob("*"): 124 | if any(dirpath.match(pattern) for pattern in cache_dirs) and not (dirpath.match(".venv") or dirpath.match(".venvs")): 125 | shutil.rmtree(path, ignore_errors=True) 126 | 127 | 128 | def vscode() -> None: 129 | """Configure VSCode to work on this project.""" 130 | Path(".vscode").mkdir(parents=True, exist_ok=True) 131 | shell("cp -v config/vscode/* .vscode") 132 | 133 | 134 | def main() -> int: 135 | """Main entry point.""" 136 | args = list(sys.argv[1:]) 137 | if not args or args[0] == "help": 138 | if len(args) > 1: 139 | run("default", "duty", "--help", args[1]) 140 | else: 141 | print("Available commands") # noqa: T201 142 | print(" help Print this help. Add task name to print help.") # noqa: T201 143 | print(" setup Setup all virtual environments (install dependencies).") # noqa: T201 144 | print(" run Run a command in the default virtual environment.") # noqa: T201 145 | print(" multirun Run a command for all configured Python versions.") # noqa: T201 146 | print(" allrun Run a command in all virtual environments.") # noqa: T201 147 | print(" 3.x Run a command in the virtual environment for Python 3.x.") # noqa: T201 148 | print(" clean Delete build artifacts and cache files.") # noqa: T201 149 | print(" vscode Configure VSCode to work on this project.") # noqa: T201 150 | try: 151 | run("default", "python", "-V", capture_output=True) 152 | except (subprocess.CalledProcessError, ValueError): 153 | pass 154 | else: 155 | print("\nAvailable tasks") # noqa: T201 156 | run("default", "duty", "--list") 157 | return 0 158 | 159 | while args: 160 | cmd = args.pop(0) 161 | 162 | if cmd == "run": 163 | run("default", *args) 164 | return 0 165 | 166 | if cmd == "multirun": 167 | multirun(*args) 168 | return 0 169 | 170 | if cmd == "allrun": 171 | allrun(*args) 172 | return 0 173 | 174 | if cmd.startswith("3."): 175 | run(cmd, *args) 176 | return 0 177 | 178 | opts = [] 179 | while args and (args[0].startswith("-") or "=" in args[0]): 180 | opts.append(args.pop(0)) 181 | 182 | if cmd == "clean": 183 | clean() 184 | elif cmd == "setup": 185 | setup() 186 | elif cmd == "vscode": 187 | vscode() 188 | elif cmd == "check": 189 | multirun("duty", "check-quality", "check-types", "check-docs") 190 | run("default", "duty", "check-api") 191 | elif cmd in {"check-quality", "check-docs", "check-types", "test"}: 192 | multirun("duty", cmd, *opts) 193 | else: 194 | run("default", "duty", cmd, *opts) 195 | 196 | return 0 197 | 198 | 199 | if __name__ == "__main__": 200 | try: 201 | sys.exit(main()) 202 | except Exception: # noqa: BLE001 203 | sys.exit(1) 204 | -------------------------------------------------------------------------------- /src/mkdocs_coverage/__init__.py: -------------------------------------------------------------------------------- 1 | """MkDocs Coverage Plugin package. 2 | 3 | MkDocs plugin to integrate your coverage HTML report into your site. 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | __all__: list[str] = [] 9 | -------------------------------------------------------------------------------- /src/mkdocs_coverage/debug.py: -------------------------------------------------------------------------------- 1 | """Debugging utilities.""" 2 | 3 | from __future__ import annotations 4 | 5 | import os 6 | import platform 7 | import sys 8 | from dataclasses import dataclass 9 | from importlib import metadata 10 | 11 | 12 | @dataclass 13 | class Variable: 14 | """Dataclass describing an environment variable.""" 15 | 16 | name: str 17 | """Variable name.""" 18 | value: str 19 | """Variable value.""" 20 | 21 | 22 | @dataclass 23 | class Package: 24 | """Dataclass describing a Python package.""" 25 | 26 | name: str 27 | """Package name.""" 28 | version: str 29 | """Package version.""" 30 | 31 | 32 | @dataclass 33 | class Environment: 34 | """Dataclass to store environment information.""" 35 | 36 | interpreter_name: str 37 | """Python interpreter name.""" 38 | interpreter_version: str 39 | """Python interpreter version.""" 40 | interpreter_path: str 41 | """Path to Python executable.""" 42 | platform: str 43 | """Operating System.""" 44 | packages: list[Package] 45 | """Installed packages.""" 46 | variables: list[Variable] 47 | """Environment variables.""" 48 | 49 | 50 | def _interpreter_name_version() -> tuple[str, str]: 51 | if hasattr(sys, "implementation"): 52 | impl = sys.implementation.version 53 | version = f"{impl.major}.{impl.minor}.{impl.micro}" 54 | kind = impl.releaselevel 55 | if kind != "final": 56 | version += kind[0] + str(impl.serial) 57 | return sys.implementation.name, version 58 | return "", "0.0.0" 59 | 60 | 61 | def get_version(dist: str = "mkdocs-coverage") -> str: 62 | """Get version of the given distribution. 63 | 64 | Parameters: 65 | dist: A distribution name. 66 | 67 | Returns: 68 | A version number. 69 | """ 70 | try: 71 | return metadata.version(dist) 72 | except metadata.PackageNotFoundError: 73 | return "0.0.0" 74 | 75 | 76 | def get_debug_info() -> Environment: 77 | """Get debug/environment information. 78 | 79 | Returns: 80 | Environment information. 81 | """ 82 | py_name, py_version = _interpreter_name_version() 83 | packages = ["mkdocs-coverage"] 84 | variables = ["PYTHONPATH", *[var for var in os.environ if var.startswith("MKDOCS_COVERAGE")]] 85 | return Environment( 86 | interpreter_name=py_name, 87 | interpreter_version=py_version, 88 | interpreter_path=sys.executable, 89 | platform=platform.platform(), 90 | variables=[Variable(var, val) for var in variables if (val := os.getenv(var))], 91 | packages=[Package(pkg, get_version(pkg)) for pkg in packages], 92 | ) 93 | 94 | 95 | def print_debug_info() -> None: 96 | """Print debug/environment information.""" 97 | info = get_debug_info() 98 | print(f"- __System__: {info.platform}") 99 | print(f"- __Python__: {info.interpreter_name} {info.interpreter_version} ({info.interpreter_path})") 100 | print("- __Environment variables__:") 101 | for var in info.variables: 102 | print(f" - `{var.name}`: `{var.value}`") 103 | print("- __Installed packages__:") 104 | for pkg in info.packages: 105 | print(f" - `{pkg.name}` v{pkg.version}") 106 | 107 | 108 | if __name__ == "__main__": 109 | print_debug_info() 110 | -------------------------------------------------------------------------------- /src/mkdocs_coverage/loggers.py: -------------------------------------------------------------------------------- 1 | """Logging functions.""" 2 | 3 | from __future__ import annotations 4 | 5 | try: 6 | from mkdocs.plugin import get_plugin_logger 7 | except ImportError: 8 | # TODO: remove once support for MkDocs <1.5 is dropped 9 | import logging 10 | from typing import Any, MutableMapping 11 | 12 | class PrefixedLogger(logging.LoggerAdapter): # noqa: D101 13 | def __init__(self, prefix: str, logger: logging.Logger) -> None: # noqa: D107 14 | super().__init__(logger, {}) 15 | self.prefix = prefix 16 | 17 | def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, Any]: # noqa: D102 18 | return f"{self.prefix}: {msg}", kwargs 19 | 20 | def get_plugin_logger(name: str) -> PrefixedLogger: # noqa: D103 21 | logger = logging.getLogger(f"mkdocs.plugins.{name}") 22 | return PrefixedLogger(name.split(".", 1)[0], logger) 23 | -------------------------------------------------------------------------------- /src/mkdocs_coverage/plugin.py: -------------------------------------------------------------------------------- 1 | """This module contains the `mkdocs_coverage` plugin.""" 2 | 3 | from __future__ import annotations 4 | 5 | import re 6 | import shutil 7 | import textwrap 8 | import warnings 9 | from pathlib import Path 10 | from typing import TYPE_CHECKING, Any 11 | 12 | from mkdocs.config.base import Config 13 | from mkdocs.config.config_options import Optional 14 | from mkdocs.config.config_options import Type as MkType 15 | from mkdocs.plugins import BasePlugin 16 | from mkdocs.structure.files import File, Files 17 | 18 | from mkdocs_coverage.loggers import get_plugin_logger 19 | 20 | if TYPE_CHECKING: 21 | from mkdocs.config.defaults import MkDocsConfig 22 | 23 | log = get_plugin_logger(__name__) 24 | 25 | 26 | class MkDocsCoverageConfig(Config): 27 | """Configuration options for the plugin.""" 28 | 29 | page_name = Optional(MkType(str, default=None)) 30 | page_path = MkType(str, default="coverage") 31 | html_report_dir = MkType(str, default="htmlcov") 32 | 33 | 34 | class MkDocsCoveragePlugin(BasePlugin[MkDocsCoverageConfig]): 35 | """The MkDocs plugin to integrate the coverage HTML report in the site.""" 36 | 37 | def __init__(self) -> None: 38 | """Initialize the plugin.""" 39 | super().__init__() 40 | self.page_path: str = "" 41 | 42 | def on_files(self, files: Files, config: MkDocsConfig, **kwargs: Any) -> Files: # noqa: ARG002 43 | """Add the coverage page to the navigation. 44 | 45 | Hook for the [`on_files` event](https://www.mkdocs.org/user-guide/plugins/#on_files). 46 | This hook is used to add the coverage page to the navigation, using a temporary file. 47 | 48 | Arguments: 49 | files: The files collection. 50 | config: The MkDocs config object. 51 | **kwargs: Additional arguments passed by MkDocs. 52 | 53 | Returns: 54 | The modified files collection. 55 | """ 56 | page_name = self.config.page_name 57 | page_path: str 58 | if page_name is not None: 59 | warnings.warn( 60 | "The 'page_name' configuration option is deprecated and will be removed in a future release. " 61 | "Use the 'page_path' configuration option instead.", 62 | DeprecationWarning, 63 | stacklevel=1, 64 | ) 65 | page_path = page_name 66 | else: 67 | page_path = self.config.page_path 68 | self.page_path = page_path 69 | covindex = "covindex.html" if config.use_directory_urls else f"{page_path}/covindex.html" 70 | 71 | style = textwrap.dedent( 72 | """ 73 | 78 | """, 79 | ) 80 | 81 | iframe = textwrap.dedent( 82 | f""" 83 | 91 | """, 92 | ) 93 | 94 | script = textwrap.dedent( 95 | """ 96 | 107 | 108 | """, 109 | ) 110 | page_contents = style + iframe + script 111 | files.append( 112 | File.generated( 113 | config=config, 114 | src_uri=page_path + ".md", 115 | content=page_contents, 116 | ), 117 | ) 118 | return files 119 | 120 | def on_post_build(self, config: MkDocsConfig, **kwargs: Any) -> None: # noqa: ARG002 121 | """Copy the coverage HTML report into the site directory. 122 | 123 | Hook for the [`on_post_build` event](https://www.mkdocs.org/user-guide/plugins/#on_post_build). 124 | 125 | Rename `index.html` into `covindex.html`. 126 | Replace every occurrence of `index.html` by `covindex.html` in the HTML files. 127 | 128 | Arguments: 129 | config: The MkDocs config object. 130 | **kwargs: Additional arguments passed by MkDocs. 131 | """ 132 | site_dir = Path(config.site_dir) 133 | coverage_dir = site_dir / self.page_path 134 | tmp_index = site_dir / ".coverage-tmp.html" 135 | 136 | if config.use_directory_urls: 137 | shutil.move(str(coverage_dir / "index.html"), tmp_index) 138 | else: 139 | shutil.move(str(coverage_dir.with_suffix(".html")), tmp_index) 140 | 141 | shutil.rmtree(str(coverage_dir), ignore_errors=True) 142 | try: 143 | shutil.copytree(self.config.html_report_dir, str(coverage_dir)) 144 | except FileNotFoundError: 145 | log.warning(f"No such HTML report directory: {self.config.html_report_dir}") 146 | return 147 | 148 | shutil.move(str(coverage_dir / "index.html"), coverage_dir / "covindex.html") 149 | 150 | if config.use_directory_urls: 151 | shutil.move(str(tmp_index), coverage_dir / "index.html") 152 | else: 153 | shutil.move(str(tmp_index), coverage_dir.with_suffix(".html")) 154 | 155 | for html_file in coverage_dir.iterdir(): 156 | if html_file.suffix == ".html" and html_file.name != "index.html": 157 | html_file.write_text(re.sub(r'href="index\.html"', 'href="covindex.html"', html_file.read_text())) 158 | -------------------------------------------------------------------------------- /src/mkdocs_coverage/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pawamoy/mkdocs-coverage/3c6ef1d7614336e83674a50c1f09f62524e28dcd/src/mkdocs_coverage/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests suite for `mkdocs_coverage`.""" 2 | 3 | from pathlib import Path 4 | 5 | TESTS_DIR = Path(__file__).parent 6 | TMP_DIR = TESTS_DIR / "tmp" 7 | FIXTURES_DIR = TESTS_DIR / "fixtures" 8 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for the pytest test suite.""" 2 | -------------------------------------------------------------------------------- /tests/test_plugin.py: -------------------------------------------------------------------------------- 1 | """Tests for the plugin module.""" 2 | 3 | import re 4 | from pathlib import Path 5 | 6 | from mkdocs.commands.build import build 7 | from mkdocs.config.base import load_config 8 | 9 | 10 | def test_plugin() -> None: 11 | """Build our own documentation.""" 12 | config = load_config() 13 | config["plugins"].run_event("startup", command="build", dirty=False) 14 | try: 15 | build(config) 16 | finally: 17 | config["plugins"].run_event("shutdown") 18 | site_coverage_dir = Path(config["site_dir"]) / "coverage" 19 | for html_file in site_coverage_dir.iterdir(): 20 | if html_file.suffix == ".html" and html_file.name != "index.html" and "test" not in html_file.name: 21 | text = html_file.read_text() 22 | assert not re.search("covcovindex", text) 23 | assert not re.search('href="index.html"', text) 24 | --------------------------------------------------------------------------------