├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── autofix.yml │ ├── check.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── demo.scenario └── logo.png ├── default.nix ├── flake.lock ├── flake.nix ├── pyproject.toml ├── renovate.json ├── src └── makejinja │ ├── __init__.py │ ├── __main__.py │ ├── app.py │ ├── cli.py │ ├── config.py │ ├── plugin.py │ └── py.typed ├── tests ├── __init__.py ├── data │ ├── README.md │ ├── config │ │ ├── data.toml │ │ └── data.yaml │ ├── input1 │ │ ├── empty-folder │ │ │ └── empty-folder.yaml.jinja │ │ ├── extra-file.yaml │ │ ├── not-empty.yaml.jinja │ │ ├── secret.yaml │ │ └── ui-lovelace.yaml.jinja │ ├── input2 │ │ ├── empty.yaml.jinja │ │ ├── include.yaml.partial │ │ ├── not-empty.yaml.jinja │ │ ├── ui-lovelace.yaml │ │ └── views │ │ │ └── home.yaml.jinja │ ├── makejinja.toml │ ├── output │ │ ├── extra-file.yaml │ │ ├── not-empty.yaml │ │ ├── ui-lovelace.yaml │ │ └── views │ │ │ └── home.yaml │ └── plugin.py └── test_makejinja.py └── uv.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: mirkolenz 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: mirkolenz 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci 2 | on: 3 | pull_request: 4 | push: 5 | branches: [main, beta] 6 | jobs: 7 | autofix: 8 | if: ${{ github.repository_owner == 'wi2trier' }} 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: DeterminateSystems/nix-installer-action@v17 15 | with: 16 | extra-conf: | 17 | accept-flake-config = true 18 | - uses: cachix/cachix-action@v16 19 | with: 20 | name: mirkolenz 21 | authToken: ${{ secrets.CACHIX_TOKEN }} 22 | - run: nix fmt 23 | - uses: autofix-ci/action@v1.3.1 24 | with: 25 | commit-message: "chore: reformat code" 26 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | on: 3 | pull_request: 4 | workflow_call: 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: DeterminateSystems/nix-installer-action@v17 13 | with: 14 | extra-conf: | 15 | accept-flake-config = true 16 | - uses: cachix/cachix-action@v16 17 | with: 18 | name: mirkolenz 19 | authToken: ${{ secrets.CACHIX_TOKEN }} 20 | - run: nix flake check --show-trace 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: [main, beta] 5 | jobs: 6 | check: 7 | uses: ./.github/workflows/check.yml 8 | release: 9 | if: ${{ github.repository_owner == 'mirkolenz' }} 10 | runs-on: ubuntu-latest 11 | needs: check 12 | environment: 13 | name: release 14 | url: https://github.com/mirkolenz/makejinja/releases/tag/${{ steps.semanticrelease.outputs.git-tag }} 15 | permissions: 16 | contents: write 17 | outputs: 18 | version: ${{ steps.semanticrelease.outputs.version }} 19 | released: ${{ steps.semanticrelease.outputs.released }} 20 | git-head: ${{ steps.semanticrelease.outputs.git-head }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: DeterminateSystems/nix-installer-action@v17 24 | with: 25 | extra-conf: | 26 | accept-flake-config = true 27 | - uses: cachix/cachix-action@v16 28 | with: 29 | name: mirkolenz 30 | authToken: ${{ secrets.CACHIX_TOKEN }} 31 | - run: nix profile install .#release-env 32 | - uses: cihelper/action-semanticrelease-uv@v1 33 | id: semanticrelease 34 | with: 35 | uv-publish: false 36 | - uses: actions/upload-artifact@v4 37 | if: ${{ steps.semanticrelease.outputs.released == 'true' }} 38 | with: 39 | name: uv-build 40 | path: ./dist 41 | deploy-docker: 42 | runs-on: ubuntu-latest 43 | needs: release 44 | if: ${{ needs.release.outputs.released == 'true' }} 45 | permissions: 46 | contents: read 47 | packages: write 48 | environment: 49 | name: release 50 | url: https://ghcr.io/mirkolenz/makejinja 51 | steps: 52 | - uses: actions/checkout@v4 53 | with: 54 | ref: ${{ needs.release.outputs.git-head }} 55 | - uses: docker/setup-qemu-action@v3 56 | with: 57 | platforms: arm64 58 | - uses: DeterminateSystems/nix-installer-action@v17 59 | with: 60 | extra-conf: | 61 | extra-platforms = aarch64-linux 62 | accept-flake-config = true 63 | - uses: cachix/cachix-action@v16 64 | with: 65 | name: mirkolenz 66 | authToken: ${{ secrets.CACHIX_TOKEN }} 67 | - run: nix run .#docker-manifest --impure 68 | env: 69 | VERSION: ${{ needs.release.outputs.version }} 70 | GH_TOKEN: ${{ github.token }} 71 | deploy-pypi: 72 | runs-on: ubuntu-latest 73 | needs: release 74 | if: ${{ needs.release.outputs.released == 'true' }} 75 | permissions: 76 | id-token: write 77 | environment: 78 | name: release 79 | url: https://pypi.org/project/makejinja/${{needs.release.outputs.version}}/ 80 | steps: 81 | - uses: actions/download-artifact@v4 82 | with: 83 | name: uv-build 84 | path: ./dist 85 | - uses: pypa/gh-action-pypi-publish@release/v1 86 | build-docs: 87 | runs-on: ubuntu-latest 88 | needs: release 89 | if: ${{ needs.release.outputs.released == 'true' }} 90 | permissions: 91 | contents: read 92 | pages: read 93 | environment: github-pages 94 | steps: 95 | - uses: actions/checkout@v4 96 | with: 97 | ref: ${{ needs.release.outputs.git-head }} 98 | - uses: actions/configure-pages@v5 99 | - uses: DeterminateSystems/nix-installer-action@v17 100 | with: 101 | extra-conf: | 102 | accept-flake-config = true 103 | - uses: cachix/cachix-action@v16 104 | with: 105 | name: mirkolenz 106 | authToken: ${{ secrets.CACHIX_TOKEN }} 107 | - run: nix build .#docs 108 | - uses: actions/upload-pages-artifact@v3 109 | with: 110 | path: ./result 111 | deploy-docs: 112 | runs-on: ubuntu-latest 113 | needs: build-docs 114 | environment: 115 | name: github-pages 116 | url: ${{ steps.deploy.outputs.page_url }} 117 | permissions: 118 | pages: write 119 | id-token: write 120 | steps: 121 | - uses: actions/deploy-pages@v4 122 | id: deploy 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,python 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Python ### 38 | # Byte-compiled / optimized / DLL files 39 | __pycache__/ 40 | *.py[cod] 41 | *$py.class 42 | 43 | # C extensions 44 | *.so 45 | 46 | # Distribution / packaging 47 | .Python 48 | build/ 49 | develop-eggs/ 50 | dist/ 51 | downloads/ 52 | eggs/ 53 | .eggs/ 54 | lib/ 55 | lib64/ 56 | parts/ 57 | sdist/ 58 | var/ 59 | wheels/ 60 | share/python-wheels/ 61 | *.egg-info/ 62 | .installed.cfg 63 | *.egg 64 | MANIFEST 65 | 66 | # PyInstaller 67 | # Usually these files are written by a python script from a template 68 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 69 | *.manifest 70 | *.spec 71 | 72 | # Installer logs 73 | pip-log.txt 74 | pip-delete-this-directory.txt 75 | 76 | # Unit test / coverage reports 77 | htmlcov/ 78 | .tox/ 79 | .nox/ 80 | .coverage 81 | .coverage.* 82 | .cache 83 | nosetests.xml 84 | coverage.xml 85 | *.cover 86 | *.py,cover 87 | .hypothesis/ 88 | .pytest_cache/ 89 | cover/ 90 | 91 | # Translations 92 | *.mo 93 | *.pot 94 | 95 | # Django stuff: 96 | *.log 97 | local_settings.py 98 | db.sqlite3 99 | db.sqlite3-journal 100 | 101 | # Flask stuff: 102 | instance/ 103 | .webassets-cache 104 | 105 | # Scrapy stuff: 106 | .scrapy 107 | 108 | # Sphinx documentation 109 | docs/_build/ 110 | 111 | # PyBuilder 112 | .pybuilder/ 113 | target/ 114 | 115 | # Jupyter Notebook 116 | .ipynb_checkpoints 117 | 118 | # IPython 119 | profile_default/ 120 | ipython_config.py 121 | 122 | # pyenv 123 | # For a library or package, you might want to ignore these files since the code is 124 | # intended to run in multiple environments; otherwise, check them in: 125 | # .python-version 126 | 127 | # pipenv 128 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 129 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 130 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 131 | # install all needed dependencies. 132 | #Pipfile.lock 133 | 134 | # poetry 135 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 136 | # This is especially recommended for binary packages to ensure reproducibility, and is more 137 | # commonly ignored for libraries. 138 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 139 | #poetry.lock 140 | 141 | # pdm 142 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 143 | #pdm.lock 144 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 145 | # in version control. 146 | # https://pdm.fming.dev/#use-with-ide 147 | .pdm.toml 148 | 149 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 150 | __pypackages__/ 151 | 152 | # Celery stuff 153 | celerybeat-schedule 154 | celerybeat.pid 155 | 156 | # SageMath parsed files 157 | *.sage.py 158 | 159 | # Environments 160 | .env 161 | .venv 162 | env/ 163 | venv/ 164 | ENV/ 165 | env.bak/ 166 | venv.bak/ 167 | 168 | # Spyder project settings 169 | .spyderproject 170 | .spyproject 171 | 172 | # Rope project settings 173 | .ropeproject 174 | 175 | # mkdocs documentation 176 | /site 177 | 178 | # mypy 179 | .mypy_cache/ 180 | .dmypy.json 181 | dmypy.json 182 | 183 | # Pyre type checker 184 | .pyre/ 185 | 186 | # pytype static type analyzer 187 | .pytype/ 188 | 189 | # Cython debug symbols 190 | cython_debug/ 191 | 192 | # PyCharm 193 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 194 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 195 | # and can be added to the global gitignore or merged into this file. For a more nuclear 196 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 197 | #.idea/ 198 | 199 | ### Python Patch ### 200 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 201 | poetry.toml 202 | 203 | ### VisualStudioCode ### 204 | .vscode/* 205 | !.vscode/settings.json 206 | !.vscode/tasks.json 207 | !.vscode/launch.json 208 | !.vscode/extensions.json 209 | !.vscode/*.code-snippets 210 | 211 | # Local History for Visual Studio Code 212 | .history/ 213 | 214 | # Built Visual Studio Code Extensions 215 | *.vsix 216 | 217 | ### VisualStudioCode Patch ### 218 | # Ignore all local history of files 219 | .history 220 | .ionide 221 | 222 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,python 223 | 224 | # Custom rules (everything added below won't be overridden by 'Generate .gitignore File' if you use 'Update' option) 225 | 226 | /.pre-commit-config.yaml 227 | .vscode/ 228 | /result 229 | /assets/*.gif 230 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.7.2](https://github.com/mirkolenz/makejinja/compare/v2.7.1...v2.7.2) (2024-11-17) 4 | 5 | ### Bug Fixes 6 | 7 | * update paths to get docs working again ([a27f7c7](https://github.com/mirkolenz/makejinja/commit/a27f7c74259b61f0bf2b3574e7656e640d33a955)) 8 | 9 | ## [2.7.1](https://github.com/mirkolenz/makejinja/compare/v2.7.0...v2.7.1) (2024-11-17) 10 | 11 | ### Bug Fixes 12 | 13 | * update metadata and add build-system to pyproject.toml ([df11d96](https://github.com/mirkolenz/makejinja/commit/df11d96e31bf1136dc203180228b83e0dec4088e)) 14 | 15 | ## [2.7.0](https://github.com/mirkolenz/makejinja/compare/v2.6.2...v2.7.0) (2024-11-14) 16 | 17 | ### Features 18 | 19 | * move from poetry to uv ([0ac9325](https://github.com/mirkolenz/makejinja/commit/0ac93253fafd8c823a9a8c6d7cb83bb137799de9)) 20 | 21 | ## [2.6.2](https://github.com/mirkolenz/makejinja/compare/v2.6.1...v2.6.2) (2024-07-28) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * warn about recursive exclude patterns ([22a0918](https://github.com/mirkolenz/makejinja/commit/22a09189451f9004166ef87095ed2b82c1036b36)) 27 | 28 | ## [2.6.1](https://github.com/mirkolenz/makejinja/compare/v2.6.0...v2.6.1) (2024-06-26) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **deps:** bump pdoc due to security issue ([9c7df0c](https://github.com/mirkolenz/makejinja/commit/9c7df0c95fe0748bfbb8b53ee300f99f452dbb48)) 34 | 35 | ## [2.6.0](https://github.com/mirkolenz/makejinja/compare/v2.5.0...v2.6.0) (2024-05-11) 36 | 37 | 38 | ### Features 39 | 40 | * allow rendering all files by providing empty jinja suffix ([697b0ce](https://github.com/mirkolenz/makejinja/commit/697b0cea6c43d8aa27453211e40efbc766529204)) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * add metadata to nix derivation ([31ebb23](https://github.com/mirkolenz/makejinja/commit/31ebb23e45145ca841127e467e586d30619082e1)) 46 | 47 | ## [2.5.0](https://github.com/mirkolenz/makejinja/compare/v2.4.0...v2.5.0) (2024-01-21) 48 | 49 | 50 | ### Features 51 | 52 | * add exec-pre and exec-post ([#96](https://github.com/mirkolenz/makejinja/issues/96)) ([c808400](https://github.com/mirkolenz/makejinja/commit/c808400b9dd668e81b1fefc68ac1ac9f401c3e27)) 53 | * allow stdin/stdout on unix systems ([#58](https://github.com/mirkolenz/makejinja/issues/58)) ([7cd8e94](https://github.com/mirkolenz/makejinja/commit/7cd8e946f8c94431bcecad609ed03afbcfc1d59f)) 54 | 55 | ## [2.4.0](https://github.com/mirkolenz/makejinja/compare/v2.3.5...v2.4.0) (2024-01-21) 56 | 57 | 58 | ### Features 59 | 60 | * add exclusion functions to loader ([#102](https://github.com/mirkolenz/makejinja/issues/102)) ([1ad61f3](https://github.com/mirkolenz/makejinja/commit/1ad61f3024cc4787e0bb91052c20207bec9d9c53)) 61 | * deprecate loaders, add plugins ([2c291bb](https://github.com/mirkolenz/makejinja/commit/2c291bb17e986c915d3dd64115fbc01935f1d25f)) 62 | * keep trailing newlines by default ([a8436a8](https://github.com/mirkolenz/makejinja/commit/a8436a8bb54ca0416e34a7119c9160dc837b884f)) 63 | * pass config to custom loaders ([61ae423](https://github.com/mirkolenz/makejinja/commit/61ae423b883ed8721ff7d7e1e858bd06e1cef31c)) 64 | * replace loader exclusions with path filters ([7cba6c8](https://github.com/mirkolenz/makejinja/commit/7cba6c838940d2178c082d6bc8192412d518e111)) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * update plugin exports ([2dfa8a0](https://github.com/mirkolenz/makejinja/commit/2dfa8a023bf07a290026d7ccad5d4ef42e812b92)) 70 | * use correct negation for path filters ([2992496](https://github.com/mirkolenz/makejinja/commit/299249612528c17bc19f299657534b44360316e6)) 71 | 72 | ## [2.3.5](https://github.com/mirkolenz/makejinja/compare/v2.3.4...v2.3.5) (2024-01-16) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * apply config.force to directories as well ([#98](https://github.com/mirkolenz/makejinja/issues/98)) ([04b2521](https://github.com/mirkolenz/makejinja/commit/04b2521b1840e97bfccfc4f58b8eb3202f2038b3)) 78 | 79 | ## [2.3.4](https://github.com/mirkolenz/makejinja/compare/v2.3.3...v2.3.4) (2024-01-15) 80 | 81 | 82 | ### Bug Fixes 83 | 84 | * remove empty directories if --no-keep-empty ([a586e8f](https://github.com/mirkolenz/makejinja/commit/a586e8f6026d814b5a393bfa004ccf77f19eea9a)) 85 | 86 | ## [2.3.3](https://github.com/mirkolenz/makejinja/compare/v2.3.2...v2.3.3) (2024-01-14) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * properly parse data-vars ([670f8bd](https://github.com/mirkolenz/makejinja/commit/670f8bd459c7fa44be03ad44cd0874723ac676b9)) 92 | 93 | ## [2.3.2](https://github.com/mirkolenz/makejinja/compare/v2.3.1...v2.3.2) (2023-11-08) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * trigger release ([a331675](https://github.com/mirkolenz/makejinja/commit/a33167527ff887e6d77c40459b1ccb92b6ece964)) 99 | 100 | ## [2.3.1](https://github.com/mirkolenz/makejinja/compare/v2.3.0...v2.3.1) (2023-11-08) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * update links in readme ([8dfe58e](https://github.com/mirkolenz/makejinja/commit/8dfe58e2c58872d70a5b93d0b3a34d473a3382a8)) 106 | 107 | ## [2.3.0](https://github.com/mirkolenz/makejinja/compare/v2.2.0...v2.3.0) (2023-11-06) 108 | 109 | 110 | ### Features 111 | 112 | * add flag to pass key-value options via cli ([c59e76f](https://github.com/mirkolenz/makejinja/commit/c59e76fa966cdf2cfca9b39ab5ea15cddc15e030)) 113 | * add force option to enable overwriting ([a9adedc](https://github.com/mirkolenz/makejinja/commit/a9adedcee0ddbcad3fd5e6ab0db55e31c7622f08)) 114 | * add quiet option to silence output ([78b6b44](https://github.com/mirkolenz/makejinja/commit/78b6b448d91973f0619807098cf6216fca4414fd)) 115 | * add shorthand values for cli options ([dd033c2](https://github.com/mirkolenz/makejinja/commit/dd033c2e841d5ab5e780722ba338e4e8ffbe04ee)) 116 | * allow files to be passed as inputs ([b78fa4b](https://github.com/mirkolenz/makejinja/commit/b78fa4bc63c1cecf2db69782df1476189f8d50f5)) 117 | * allow output to be a file in certain cases ([c85763f](https://github.com/mirkolenz/makejinja/commit/c85763f95394f378b9b72a76b8179565dbc62858)) 118 | * optimize data handling (update globals) ([1016fe9](https://github.com/mirkolenz/makejinja/commit/1016fe94d510b7cf4dae99760ab923800a85ce10)) 119 | * pass os.environ to globals ([c9f646c](https://github.com/mirkolenz/makejinja/commit/c9f646c6571b95b5dbe6b3746c1b48a03db1744f)) 120 | * require using a flag to remove the output directory ([f0b288d](https://github.com/mirkolenz/makejinja/commit/f0b288d3fc15fcf23455f3b8434a0a2c87aed5a9)) 121 | * update loader to better support globals ([c38b564](https://github.com/mirkolenz/makejinja/commit/c38b564b4ccbd4543975e18fa4e0f1cc1eec3d10)) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * add shorthand options for version/help ([e4f1cf0](https://github.com/mirkolenz/makejinja/commit/e4f1cf01c24ab121a9dfa6c01869db1f278c835d)) 127 | * do not enforce jinja suffix for input files ([152d220](https://github.com/mirkolenz/makejinja/commit/152d220b4b8d274c6ea638731ed50adfa65ba92c)) 128 | * pass immutable data to loader ([1351f2c](https://github.com/mirkolenz/makejinja/commit/1351f2c4a39af7b082fbd8c0c0e7cc804569aa6a)) 129 | * rename clean-output to clean ([523556c](https://github.com/mirkolenz/makejinja/commit/523556cc3d63afa9008037db2990a37e222bff7f)) 130 | * update cli option groups ([380d0db](https://github.com/mirkolenz/makejinja/commit/380d0db3218ba3d41460f68bb6e2aa1f672b168e)) 131 | 132 | ## [2.2.0](https://github.com/mirkolenz/makejinja/compare/v2.1.4...v2.2.0) (2023-11-03) 133 | 134 | 135 | ### Features 136 | 137 | * allow customization of undefined behavior ([fd84618](https://github.com/mirkolenz/makejinja/commit/fd846189f4e9702c5f08dc344abb3dff062b1a5d)) 138 | 139 | ## [2.1.4](https://github.com/mirkolenz/makejinja/compare/v2.1.3...v2.1.4) (2023-11-03) 140 | 141 | 142 | ### Bug Fixes 143 | 144 | * update description of project ([2f4e405](https://github.com/mirkolenz/makejinja/commit/2f4e40563350d4eb12912db9bdb0e19b4231a791)) 145 | 146 | ## [2.1.3](https://github.com/mirkolenz/makejinja/compare/v2.1.2...v2.1.3) (2023-11-03) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * use correct ref for building docs ([b7e30f7](https://github.com/mirkolenz/makejinja/commit/b7e30f773c2bd18e29333ba654ee7a3cdc85e07d)) 152 | 153 | ## [2.1.2](https://github.com/mirkolenz/makejinja/compare/v2.1.1...v2.1.2) (2023-11-03) 154 | 155 | 156 | ### Bug Fixes 157 | 158 | * trigger release ([fd6579d](https://github.com/mirkolenz/makejinja/commit/fd6579d7650889843f10699999bce2cef4c18f53)) 159 | 160 | ## [2.1.1](https://github.com/mirkolenz/makejinja/compare/v2.1.0...v2.1.1) (2023-10-31) 161 | 162 | 163 | ### Bug Fixes 164 | 165 | * expose cli module in main init ([ed429a0](https://github.com/mirkolenz/makejinja/commit/ed429a0b61814bbd35a7ab36014e556c65dec597)) 166 | 167 | ## [2.1.0](https://github.com/mirkolenz/makejinja/compare/v2.0.2...v2.1.0) (2023-10-30) 168 | 169 | 170 | ### Features 171 | 172 | * add data handler for json files ([d18f514](https://github.com/mirkolenz/makejinja/commit/d18f514a0722fbbea3886d538deec45470207d68)) 173 | 174 | 175 | ### Bug Fixes 176 | 177 | * require at least python 3.11 and drop tomli ([ebdeb64](https://github.com/mirkolenz/makejinja/commit/ebdeb64c765eefcb250c541c46167059af0c154e)) 178 | 179 | ## [2.0.2](https://github.com/mirkolenz/makejinja/compare/v2.0.1...v2.0.2) (2023-10-26) 180 | 181 | 182 | ### Bug Fixes 183 | 184 | * use poetry2nix again after upstream fixes ([3b7dac0](https://github.com/mirkolenz/makejinja/commit/3b7dac03faf7418d0eb78cd5ed3f3f970235b0cb)) 185 | 186 | ## [2.0.1](https://github.com/mirkolenz/makejinja/compare/v2.0.0...v2.0.1) (2023-09-30) 187 | 188 | 189 | ### Bug Fixes 190 | 191 | * bump deps ([193f396](https://github.com/mirkolenz/makejinja/commit/193f396a233d77cf1390622819990563f3055162)) 192 | * remove default command from docker image ([c562377](https://github.com/mirkolenz/makejinja/commit/c5623773947d602f7892bef5c8723a5f03da5c4e)) 193 | 194 | ## [2.0.0](https://github.com/mirkolenz/makejinja/compare/v1.1.5...v2.0.0) (2023-06-18) 195 | 196 | 197 | ### ⚠ BREAKING CHANGES 198 | 199 | * The configuration file has been renamed from `.makejinja.toml` to `makejinja.toml`. Please rename your files accordingly. 200 | * The parameter `input_pattern` has been changed to `include_patterns` which now accepts a list of patterns. 201 | * In this version, `input` has been removed an replaced with `inputs` (allowing to use multiple input folders). We also included a new option `exclude_patterns` to ignore files that would be matched by `input_pattern`. The option `copy_tree` is superseded by the new `copy_metadata` which is compatible with multiple inputs and preserves attributes for rendered files as well. Please adjust your config accordingly, otherwise `makejinja` will break! 202 | 203 | ### Features 204 | 205 | * add version option ([0585114](https://github.com/mirkolenz/makejinja/commit/058511497517724d6e37bd8e4054a16641476366)) 206 | * completely rewrite file handling ([#20](https://github.com/mirkolenz/makejinja/issues/20)) ([97d6a51](https://github.com/mirkolenz/makejinja/commit/97d6a51d268689bd50fa1b4a9a70c099db42bda4)) 207 | * remove leading dot from config file ([4742165](https://github.com/mirkolenz/makejinja/commit/4742165ed4e18c67543f5c46411989d752e867f9)) 208 | * rename input_pattern to include_patterns ([21e3e85](https://github.com/mirkolenz/makejinja/commit/21e3e85d91cb0c2c2426bd36137998e36c5140ef)) 209 | 210 | 211 | ### Bug Fixes 212 | 213 | * add multi-arch docker images ([6024633](https://github.com/mirkolenz/makejinja/commit/6024633e84a895eeb9cb2db0860cfa0bd77b7954)) 214 | * apply exclude patterns to files and folders ([5d80747](https://github.com/mirkolenz/makejinja/commit/5d807474276b107047422f1430bbb890f9cb9d7d)) 215 | * provide aarch64 docker image ([e983268](https://github.com/mirkolenz/makejinja/commit/e983268df920741921384d4ea4f75f92f79f524e)) 216 | * remove aarch64 docker image due to bugs ([0af11a2](https://github.com/mirkolenz/makejinja/commit/0af11a24c24dc7200253f9864126bda14a9ebf29)) 217 | * update nix flake ([05b9575](https://github.com/mirkolenz/makejinja/commit/05b95756e2682f4acad69f7ebbbcfd75eb945a02)) 218 | 219 | ## [2.0.0-beta.8](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2023-06-13) 220 | 221 | 222 | ### ⚠ BREAKING CHANGES 223 | 224 | * The configuration file has been renamed from `.makejinja.toml` to `makejinja.toml`. Please rename your files accordingly. 225 | * The parameter `input_pattern` has been changed to `include_patterns` which now accepts a list of patterns. 226 | 227 | ### Features 228 | 229 | * remove leading dot from config file ([4742165](https://github.com/mirkolenz/makejinja/commit/4742165ed4e18c67543f5c46411989d752e867f9)) 230 | * rename input_pattern to include_patterns ([21e3e85](https://github.com/mirkolenz/makejinja/commit/21e3e85d91cb0c2c2426bd36137998e36c5140ef)) 231 | 232 | ## [2.0.0-beta.7](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2023-06-13) 233 | 234 | 235 | ### Bug Fixes 236 | 237 | * add multi-arch docker images ([6024633](https://github.com/mirkolenz/makejinja/commit/6024633e84a895eeb9cb2db0860cfa0bd77b7954)) 238 | 239 | ## [2.0.0-beta.6](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2023-06-01) 240 | 241 | 242 | ### Bug Fixes 243 | 244 | * apply exclude patterns to files and folders ([5d80747](https://github.com/mirkolenz/makejinja/commit/5d807474276b107047422f1430bbb890f9cb9d7d)) 245 | 246 | ## [2.0.0-beta.5](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2023-05-25) 247 | 248 | 249 | ### Bug Fixes 250 | 251 | * remove aarch64 docker image due to bugs ([0af11a2](https://github.com/mirkolenz/makejinja/commit/0af11a24c24dc7200253f9864126bda14a9ebf29)) 252 | 253 | ## [2.0.0-beta.4](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2023-05-25) 254 | 255 | 256 | ### Bug Fixes 257 | 258 | * update nix flake ([05b9575](https://github.com/mirkolenz/makejinja/commit/05b95756e2682f4acad69f7ebbbcfd75eb945a02)) 259 | 260 | ## [2.0.0-beta.3](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2023-05-25) 261 | 262 | 263 | ### Bug Fixes 264 | 265 | * provide aarch64 docker image ([e983268](https://github.com/mirkolenz/makejinja/commit/e983268df920741921384d4ea4f75f92f79f524e)) 266 | 267 | ## [2.0.0-beta.2](https://github.com/mirkolenz/makejinja/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2023-05-24) 268 | 269 | 270 | ### Features 271 | 272 | * add version option ([0585114](https://github.com/mirkolenz/makejinja/commit/058511497517724d6e37bd8e4054a16641476366)) 273 | 274 | ## [2.0.0-beta.1](https://github.com/mirkolenz/makejinja/compare/v1.1.5...v2.0.0-beta.1) (2023-05-18) 275 | 276 | 277 | ### ⚠ BREAKING CHANGES 278 | 279 | * In this version, `input` has been removed an replaced with `inputs` (allowing to use multiple input folders). We also included a new option `exclude_patterns` to ignore files that would be matched by `input_pattern`. The option `copy_tree` is superseded by the new `copy_metadata` which is compatible with multiple inputs and preserves attributes for rendered files as well. Please adjust your config accordingly, otherwise `makejinja` will break! 280 | 281 | ### Features 282 | 283 | * completely rewrite file handling ([#20](https://github.com/mirkolenz/makejinja/issues/20)) ([97d6a51](https://github.com/mirkolenz/makejinja/commit/97d6a51d268689bd50fa1b4a9a70c099db42bda4)) 284 | 285 | ## [1.1.5](https://github.com/mirkolenz/makejinja/compare/v1.1.4...v1.1.5) (2023-05-17) 286 | 287 | 288 | ### Bug Fixes 289 | 290 | * trigger ci build ([acd7609](https://github.com/mirkolenz/makejinja/commit/acd7609c4cdfaf546e4d28eee14642a8e9f580e5)) 291 | * try to fix docker image pushing ([6634507](https://github.com/mirkolenz/makejinja/commit/663450742507ba308082d4f7e17b4e71c0f4ee23)) 292 | * update readme ([2445d25](https://github.com/mirkolenz/makejinja/commit/2445d254cc6cde9953ebe8055a1c97640f01527e)) 293 | * use impure nix run for pushing docker ([634f699](https://github.com/mirkolenz/makejinja/commit/634f699d3b68060d1265c47e044f993977028257)) 294 | 295 | ## [1.1.5-beta.4](https://github.com/mirkolenz/makejinja/compare/v1.1.5-beta.3...v1.1.5-beta.4) (2023-05-09) 296 | 297 | 298 | ### Bug Fixes 299 | 300 | * update readme ([2445d25](https://github.com/mirkolenz/makejinja/commit/2445d254cc6cde9953ebe8055a1c97640f01527e)) 301 | 302 | ## [1.1.5-beta.3](https://github.com/mirkolenz/makejinja/compare/v1.1.5-beta.2...v1.1.5-beta.3) (2023-05-09) 303 | 304 | 305 | ### Bug Fixes 306 | 307 | * use impure nix run for pushing docker ([634f699](https://github.com/mirkolenz/makejinja/commit/634f699d3b68060d1265c47e044f993977028257)) 308 | 309 | ## [1.1.5-beta.2](https://github.com/mirkolenz/makejinja/compare/v1.1.5-beta.1...v1.1.5-beta.2) (2023-05-09) 310 | 311 | 312 | ### Bug Fixes 313 | 314 | * try to fix docker image pushing ([6634507](https://github.com/mirkolenz/makejinja/commit/663450742507ba308082d4f7e17b4e71c0f4ee23)) 315 | 316 | ## [1.1.5-beta.1](https://github.com/mirkolenz/makejinja/compare/v1.1.4...v1.1.5-beta.1) (2023-05-08) 317 | 318 | 319 | ### Bug Fixes 320 | 321 | * trigger ci build ([acd7609](https://github.com/mirkolenz/makejinja/commit/acd7609c4cdfaf546e4d28eee14642a8e9f580e5)) 322 | 323 | ## [1.1.4](https://github.com/mirkolenz/makejinja/compare/v1.1.3...v1.1.4) (2023-04-30) 324 | 325 | 326 | ### Bug Fixes 327 | 328 | * trigger ci build ([f529ff0](https://github.com/mirkolenz/makejinja/commit/f529ff0f323941dc0bafb2366c768f1a316ae293)) 329 | 330 | ## [1.1.3](https://github.com/mirkolenz/makejinja/compare/v1.1.2...v1.1.3) (2023-04-30) 331 | 332 | 333 | ### Bug Fixes 334 | 335 | * help message was missing from cli ([34b626e](https://github.com/mirkolenz/makejinja/commit/34b626e52ff32ee6ce2dbba8351877441d1c9903)) 336 | 337 | ## [1.1.2](https://github.com/mirkolenz/makejinja/compare/v1.1.1...v1.1.2) (2023-02-14) 338 | 339 | 340 | ### Bug Fixes 341 | 342 | * **loader:** remove protocol to enable subclassing ([db55ae3](https://github.com/mirkolenz/makejinja/commit/db55ae36478ddd7899ad6fc0395f3f84e796e637)) 343 | 344 | ## [1.1.1](https://github.com/mirkolenz/makejinja/compare/v1.1.0...v1.1.1) (2023-02-14) 345 | 346 | 347 | ### Bug Fixes 348 | 349 | * use protocol instead of abc for loader class ([d72bec1](https://github.com/mirkolenz/makejinja/commit/d72bec10bf555d9aca53e712195171253ee3f003)) 350 | 351 | ## [1.1.0](https://github.com/mirkolenz/makejinja/compare/v1.0.1...v1.1.0) (2023-02-06) 352 | 353 | 354 | ### Features 355 | 356 | * enable programmatic usage of the library ([ddc744b](https://github.com/mirkolenz/makejinja/commit/ddc744bd4427c6d7480f6c45b10b6ab329e24b90)) 357 | 358 | 359 | ### Bug Fixes 360 | 361 | * add all annotations to config/loader ([6070e5a](https://github.com/mirkolenz/makejinja/commit/6070e5aca09adc07998dfa7240544badfd116331)) 362 | * add py.typed file ([3756882](https://github.com/mirkolenz/makejinja/commit/3756882401b6e2402715b5ddaf484a8b3a3c5ecc)) 363 | * modularize app, improve loader construction ([a8da7fa](https://github.com/mirkolenz/makejinja/commit/a8da7fac03a08ba23ca9a7debc9c183fc7688ce6)) 364 | 365 | ## [1.0.1](https://github.com/mirkolenz/makejinja/compare/v1.0.0...v1.0.1) (2023-02-03) 366 | 367 | 368 | ### Bug Fixes 369 | 370 | * **docker:** use entrypoint for proper cli usage ([fcebe4d](https://github.com/mirkolenz/makejinja/commit/fcebe4de622bbbc654ee2799a94affb515a4ab30)) 371 | 372 | ## [1.0.0](https://github.com/mirkolenz/makejinja/compare/v0.7.5...v1.0.0) (2023-01-25) 373 | 374 | 375 | ### ⚠ BREAKING CHANGES 376 | 377 | * use jinja methods to import custom loaders 378 | * enhance support for custom loaders 379 | * rename input/output options 380 | * enhance custom code & remove cli options 381 | * switch from typer to click & typed-settings 382 | * Massive performance boost over python-simpleconf. The CLI options changed: env-vars are no longer supported and we only handle files ending in `yaml` or `yml`. 383 | 384 | ### Features 385 | 386 | * add checks to verify correct file handling ([5d5d5fd](https://github.com/mirkolenz/makejinja/commit/5d5d5fdd3473efebf41fbad83891786f9e902688)) 387 | * add initial support to load custom code ([9404ecc](https://github.com/mirkolenz/makejinja/commit/9404eccca2db01858242d2f445b814311188ba07)) 388 | * add options to change jinja delimiters ([edd1caa](https://github.com/mirkolenz/makejinja/commit/edd1caac1b1cd22d14d0bd59aa33061934b1a25b)) 389 | * add python data loader ([2a0b817](https://github.com/mirkolenz/makejinja/commit/2a0b8170f68e8e6a3658ff3c1bd79e7eeab4841b)) 390 | * collect modules in subfolders ([ebfa242](https://github.com/mirkolenz/makejinja/commit/ebfa24230ca8056ad2ed2194f69530c6ff93a80b)) 391 | * enhance custom code & remove cli options ([a8b0b64](https://github.com/mirkolenz/makejinja/commit/a8b0b641304583377975d9960d0677596ad88709)) 392 | * enhance support for custom loaders ([46c8eb1](https://github.com/mirkolenz/makejinja/commit/46c8eb1eda830f36f1d0d657adfe28046a0b82fe)) 393 | * pass jinja options to env constructor ([f39fe32](https://github.com/mirkolenz/makejinja/commit/f39fe32c61ef100241b58b14e9d53ba11ab20356)) 394 | * rename input/output options ([2592c19](https://github.com/mirkolenz/makejinja/commit/2592c196fce2fd872e76c86d902f3322d6c5d02c)) 395 | * switch from typer to click & typed-settings ([3e9d09d](https://github.com/mirkolenz/makejinja/commit/3e9d09d53c1a68fb47a40c25b088809198f30e10)) 396 | * switch to pure yaml config parsing ([ac22a0d](https://github.com/mirkolenz/makejinja/commit/ac22a0df5e1a6bd48bda457e797b271aa9b9aae5)) 397 | * use jinja methods to import custom loaders ([901f37a](https://github.com/mirkolenz/makejinja/commit/901f37a35e9287fc1f0a98c9f3ccc23cafd3cbc5)) 398 | 399 | 400 | ### Bug Fixes 401 | 402 | * add missing main package file ([b436dda](https://github.com/mirkolenz/makejinja/commit/b436dda408e04b510d3bd6185e29dd257029aa84)) 403 | * improve cli output ([1280fa7](https://github.com/mirkolenz/makejinja/commit/1280fa71c83af483419c6e0c58f3e5c4757c5c3c)) 404 | * improve options ([e81d727](https://github.com/mirkolenz/makejinja/commit/e81d727469d012579ec04fb1e61d28076ffe7a7e)) 405 | * improve types ([475e2a5](https://github.com/mirkolenz/makejinja/commit/475e2a54220998c5b1022f1b89228d42b04ccc91)) 406 | * make custom import paths more robust ([7424729](https://github.com/mirkolenz/makejinja/commit/7424729cdba1b168193fec72b9d0639c16962107)) 407 | * properly set pythonpath for module resolution ([6beb0b0](https://github.com/mirkolenz/makejinja/commit/6beb0b0a8bd4a7649dffd5f734805ae951c58841)) 408 | * remove wrong flag decls from click params ([5d98f08](https://github.com/mirkolenz/makejinja/commit/5d98f08752b264b94d9091755e3ad1ca515496c0)) 409 | * update typed-settings and remove type casts ([e42309d](https://github.com/mirkolenz/makejinja/commit/e42309de1020c4cd0463ec4948933b83caad9438)) 410 | 411 | ## [1.0.0-beta.12](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2023-01-15) 412 | 413 | 414 | ### Bug Fixes 415 | 416 | * make custom import paths more robust ([7424729](https://github.com/mirkolenz/makejinja/commit/7424729cdba1b168193fec72b9d0639c16962107)) 417 | 418 | ## [1.0.0-beta.11](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2023-01-15) 419 | 420 | 421 | ### Bug Fixes 422 | 423 | * properly set pythonpath for module resolution ([6beb0b0](https://github.com/mirkolenz/makejinja/commit/6beb0b0a8bd4a7649dffd5f734805ae951c58841)) 424 | 425 | ## [1.0.0-beta.10](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2023-01-15) 426 | 427 | 428 | ### ⚠ BREAKING CHANGES 429 | 430 | * use jinja methods to import custom loaders 431 | 432 | ### Features 433 | 434 | * use jinja methods to import custom loaders ([901f37a](https://github.com/mirkolenz/makejinja/commit/901f37a35e9287fc1f0a98c9f3ccc23cafd3cbc5)) 435 | 436 | 437 | ### Bug Fixes 438 | 439 | * improve types ([475e2a5](https://github.com/mirkolenz/makejinja/commit/475e2a54220998c5b1022f1b89228d42b04ccc91)) 440 | 441 | ## [1.0.0-beta.9](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2023-01-15) 442 | 443 | 444 | ### ⚠ BREAKING CHANGES 445 | 446 | * enhance support for custom loaders 447 | 448 | ### Features 449 | 450 | * enhance support for custom loaders ([46c8eb1](https://github.com/mirkolenz/makejinja/commit/46c8eb1eda830f36f1d0d657adfe28046a0b82fe)) 451 | 452 | ## [1.0.0-beta.8](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2023-01-15) 453 | 454 | 455 | ### ⚠ BREAKING CHANGES 456 | 457 | * rename input/output options 458 | 459 | ### Features 460 | 461 | * collect modules in subfolders ([ebfa242](https://github.com/mirkolenz/makejinja/commit/ebfa24230ca8056ad2ed2194f69530c6ff93a80b)) 462 | * pass jinja options to env constructor ([f39fe32](https://github.com/mirkolenz/makejinja/commit/f39fe32c61ef100241b58b14e9d53ba11ab20356)) 463 | * rename input/output options ([2592c19](https://github.com/mirkolenz/makejinja/commit/2592c196fce2fd872e76c86d902f3322d6c5d02c)) 464 | 465 | 466 | ### Bug Fixes 467 | 468 | * improve options ([e81d727](https://github.com/mirkolenz/makejinja/commit/e81d727469d012579ec04fb1e61d28076ffe7a7e)) 469 | 470 | ## [1.0.0-beta.7](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2023-01-14) 471 | 472 | 473 | ### ⚠ BREAKING CHANGES 474 | 475 | * enhance custom code & remove cli options 476 | 477 | ### Features 478 | 479 | * add initial support to load custom code ([9404ecc](https://github.com/mirkolenz/makejinja/commit/9404eccca2db01858242d2f445b814311188ba07)) 480 | * add python data loader ([2a0b817](https://github.com/mirkolenz/makejinja/commit/2a0b8170f68e8e6a3658ff3c1bd79e7eeab4841b)) 481 | * enhance custom code & remove cli options ([a8b0b64](https://github.com/mirkolenz/makejinja/commit/a8b0b641304583377975d9960d0677596ad88709)) 482 | 483 | ## [1.0.0-beta.6](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2023-01-14) 484 | 485 | 486 | ### Bug Fixes 487 | 488 | * add missing main package file ([b436dda](https://github.com/mirkolenz/makejinja/commit/b436dda408e04b510d3bd6185e29dd257029aa84)) 489 | * update typed-settings and remove type casts ([e42309d](https://github.com/mirkolenz/makejinja/commit/e42309de1020c4cd0463ec4948933b83caad9438)) 490 | 491 | ## [1.0.0-beta.5](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2023-01-05) 492 | 493 | 494 | ### Bug Fixes 495 | 496 | * remove wrong flag decls from click params ([5d98f08](https://github.com/mirkolenz/makejinja/commit/5d98f08752b264b94d9091755e3ad1ca515496c0)) 497 | 498 | ## [1.0.0-beta.4](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2023-01-03) 499 | 500 | 501 | ### ⚠ BREAKING CHANGES 502 | 503 | * switch from typer to click & typed-settings 504 | 505 | ### Features 506 | 507 | * switch from typer to click & typed-settings ([3e9d09d](https://github.com/mirkolenz/makejinja/commit/3e9d09d53c1a68fb47a40c25b088809198f30e10)) 508 | 509 | ## [1.0.0-beta.3](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2023-01-02) 510 | 511 | 512 | ### Bug Fixes 513 | 514 | * **deps:** update dependency rich to v13 ([#11](https://github.com/mirkolenz/makejinja/issues/11)) ([86b15d7](https://github.com/mirkolenz/makejinja/commit/86b15d7325c9cc4e50f69cad6c3fd5628a242817)) 515 | 516 | ## [0.7.5](https://github.com/mirkolenz/makejinja/compare/v0.7.4...v0.7.5) (2022-12-30) 517 | 518 | 519 | ### Bug Fixes 520 | 521 | * **deps:** update dependency rich to v13 ([#11](https://github.com/mirkolenz/makejinja/issues/11)) ([86b15d7](https://github.com/mirkolenz/makejinja/commit/86b15d7325c9cc4e50f69cad6c3fd5628a242817)) 522 | 523 | ## [1.0.0-beta.2](https://github.com/mirkolenz/makejinja/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2022-12-28) 524 | 525 | 526 | ### Features 527 | 528 | * add options to change jinja delimiters ([edd1caa](https://github.com/mirkolenz/makejinja/commit/edd1caac1b1cd22d14d0bd59aa33061934b1a25b)) 529 | 530 | ## [1.0.0-beta.1](https://github.com/mirkolenz/makejinja/compare/v0.7.4...v1.0.0-beta.1) (2022-12-26) 531 | 532 | 533 | ### ⚠ BREAKING CHANGES 534 | 535 | * Massive performance boost over python-simpleconf. The CLI options changed: env-vars are no longer supported and we only handle files ending in `yaml` or `yml`. 536 | 537 | ### Features 538 | 539 | * add checks to verify correct file handling ([5d5d5fd](https://github.com/mirkolenz/makejinja/commit/5d5d5fdd3473efebf41fbad83891786f9e902688)) 540 | * switch to pure yaml config parsing ([ac22a0d](https://github.com/mirkolenz/makejinja/commit/ac22a0df5e1a6bd48bda457e797b271aa9b9aae5)) 541 | 542 | 543 | ### Bug Fixes 544 | 545 | * improve cli output ([1280fa7](https://github.com/mirkolenz/makejinja/commit/1280fa71c83af483419c6e0c58f3e5c4757c5c3c)) 546 | 547 | ## [0.7.4](https://github.com/mirkolenz/makejinja/compare/v0.7.3...v0.7.4) (2022-12-18) 548 | 549 | 550 | ### Bug Fixes 551 | 552 | * bump version ([2a80893](https://github.com/mirkolenz/makejinja/commit/2a808933a75cfdb5af5e2e4b6c1b982304ce1a9d)) 553 | 554 | ## [0.7.3](https://github.com/mirkolenz/makejinja/compare/v0.7.2...v0.7.3) (2022-12-18) 555 | 556 | 557 | ### Bug Fixes 558 | 559 | * bump version ([ab04f19](https://github.com/mirkolenz/makejinja/commit/ab04f19714dfaaa2a44f7f37cb726744b184dd7b)) 560 | 561 | ## [0.7.2](https://github.com/mirkolenz/makejinja/compare/v0.7.1...v0.7.2) (2022-12-18) 562 | 563 | 564 | ### Bug Fixes 565 | 566 | * bump version ([0a6611a](https://github.com/mirkolenz/makejinja/commit/0a6611ab6699891acbacb2fbd8488aeec6cc3122)) 567 | 568 | ## [0.7.1](https://github.com/mirkolenz/makejinja/compare/v0.7.0...v0.7.1) (2022-12-18) 569 | 570 | 571 | ### Bug Fixes 572 | 573 | * wrong loading of env vars data ([4bd764b](https://github.com/mirkolenz/makejinja/commit/4bd764b85b09985ce1990ac90147f084394d3a9f)) 574 | 575 | ## [0.7.0](https://github.com/mirkolenz/makejinja/compare/v0.6.0...v0.7.0) (2022-12-17) 576 | 577 | 578 | ### Features 579 | 580 | * add documentation to cli ([b001d04](https://github.com/mirkolenz/makejinja/commit/b001d04c0a622b3012e7a6d587be171d22331d12)) 581 | 582 | 583 | ### Bug Fixes 584 | 585 | * improve command output ([36df06f](https://github.com/mirkolenz/makejinja/commit/36df06fecc14b443a452e2f2e49107870fb517d9)) 586 | * process env vars after files ([31cb946](https://github.com/mirkolenz/makejinja/commit/31cb946b5ad47beed2788e53d4b39c50fe7da256)) 587 | * sort files in iterdir ([5be3db1](https://github.com/mirkolenz/makejinja/commit/5be3db18898fe868d45fea6cfdab6ba3fe6bbbf3)) 588 | 589 | ## [0.6.0](https://github.com/mirkolenz/makejinja/compare/v0.5.1...v0.6.0) (2022-12-15) 590 | 591 | 592 | ### Features 593 | 594 | * update cli param names ([c819a51](https://github.com/mirkolenz/makejinja/commit/c819a51d309803fb8e6a56d9ba6d52334b79bda0)) 595 | 596 | 597 | ### Documentation 598 | 599 | * update readme ([dd3eec7](https://github.com/mirkolenz/makejinja/commit/dd3eec77ffc96f1cc544013c4ada4e4663bbe7b7)) 600 | 601 | ## [0.5.1](https://github.com/mirkolenz/makejinja/compare/v0.5.0...v0.5.1) (2022-12-14) 602 | 603 | 604 | ### Bug Fixes 605 | 606 | * enable file-based loading of globals & filters ([cf9f331](https://github.com/mirkolenz/makejinja/commit/cf9f331f81c13cc8d2834f5c748776d7d332fd4d)) 607 | 608 | ## [0.5.0](https://github.com/mirkolenz/makejinja/compare/v0.4.1...v0.5.0) (2022-12-14) 609 | 610 | 611 | ### Features 612 | 613 | * allow customization of globals and filters ([d86bd5a](https://github.com/mirkolenz/makejinja/commit/d86bd5a195b0e8ace992f28e13bb0c13f4bcea42)) 614 | 615 | ## [0.4.1](https://github.com/mirkolenz/makejinja/compare/v0.4.0...v0.4.1) (2022-12-14) 616 | 617 | 618 | ### Bug Fixes 619 | 620 | * handle empty templates with newlines ([97123b6](https://github.com/mirkolenz/makejinja/commit/97123b6f20ac608edd42962b4f031ef967c8e5df)) 621 | 622 | ## [0.4.0](https://github.com/mirkolenz/makejinja/compare/v0.3.0...v0.4.0) (2022-12-14) 623 | 624 | 625 | ### Features 626 | 627 | * add skip-entry cli param ([7d79fa9](https://github.com/mirkolenz/makejinja/commit/7d79fa95c2411aced7d7085d5d385b8f594cbd55)) 628 | 629 | ## [0.3.0](https://github.com/mirkolenz/makejinja/compare/v0.2.1...v0.3.0) (2022-12-14) 630 | 631 | 632 | ### Features 633 | 634 | * add global function to select a language ([b26836d](https://github.com/mirkolenz/makejinja/commit/b26836df42f87af42a5145cd2ddfd3e61f8e5dd9)) 635 | 636 | ## [0.2.1](https://github.com/mirkolenz/makejinja/compare/v0.2.0...v0.2.1) (2022-12-11) 637 | 638 | 639 | ### Bug Fixes 640 | 641 | * improve compatibility with python 3.9 ([30919e8](https://github.com/mirkolenz/makejinja/commit/30919e83e11fbc368b8d97d498dab7ae2e766671)) 642 | 643 | ## v0.2.0 (2022-12-11) 644 | 645 | ### Feature 646 | 647 | - Add option to remove jinja suffix after rendering ([`d1ec7d6`](https://github.com/mirkolenz/makejinja/commit/d1ec7d6079ec3cf2e124a708dfe5688284add192)) 648 | 649 | ### Documentation 650 | 651 | - Fix changelog ([`cfab1b4`](https://github.com/mirkolenz/makejinja/commit/cfab1b436036caae98a11798b98adc857f8fa189)) 652 | 653 | ## [v0.1.1](https://github.com/mirkolenz/makejinja/compare/0.1.0...0.1.1) (2022-12-10) 654 | 655 | ### Bug Fixes 656 | 657 | - change script name to makejinja ([df14627](https://github.com/mirkolenz/makejinja/commit/df14627056c40e62adc489ac4c766b796e59f34f)) 658 | 659 | ## v0.1.0 (2022-12-10) 660 | 661 | - Initial release 662 | -------------------------------------------------------------------------------- /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, religion, or sexual identity 10 | 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 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of 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 35 | address, 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 | mirko@mirkolenz.com. 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 86 | of 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 93 | permanent 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 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mirko Lenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

makejinja

3 | 4 |

5 | makejinja logo 6 |

7 | 8 |

9 | PyPI | 10 | Docker | 11 | Docs | 12 | Example | 13 | Jinja reference 14 |

15 | 16 |

17 | Generate entire directory structures using Jinja templates with support for external data and custom plugins. 18 |

19 | 20 |

21 | makejinja demonstration 22 |

23 | 24 | --- 25 | 26 | 27 | 28 | makejinja can be used to automatically generate files from [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates). 29 | It is conceptually similar to [Ansible templates](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/template_module.html) since both are built on top of Jinja. 30 | However, makejinja is a standalone tool that can be used without Ansible and offers some advanced features like custom plugins. 31 | A use case for this tool is generating config files for [Home Assistant](https://www.home-assistant.io/): 32 | Using the same language that the built-in templates use, you can greatly simplify your configuration. 33 | An [example for Home Assistant](https://github.com/mirkolenz/makejinja/tree/main/tests/data) can be found in the tests directory. 34 | 35 | ## Highlights 36 | 37 | - Recursively render nested directories containing template files to a common output directory. 38 | - Load data files containing variables to use in your Jinja templates from YAML, TOML, and Python files. 39 | - Write custom [plugins](https://mirkolenz.github.io/makejinja/makejinja/plugin.html#Plugin) to extend the functionality of makejinja. 40 | - Adjust all Jinja options (e.g., whitespace behavior and delimiters) to your needs via CLI flags or a config file. 41 | 42 | ## Installation 43 | 44 | The tool is written in Python and can be installed via pip, nix, and docker. 45 | It can be used as a CLI tool or as a Python library. 46 | 47 | ### PIP 48 | 49 | makejinja is available on [PyPI](https://pypi.org/project/makejinja/) and can be installed via `pip`: 50 | 51 | ```shell 52 | pip install makejinja 53 | makejinja -i ./input -o ./output 54 | ``` 55 | 56 | ### Nix 57 | 58 | If you use the `nix` package manager, you can add this repository as an input to your flake and use `makejinja.packages.${system}.default` or apply the overlay `makejinja.overlays.default`. 59 | You can also run it directly as follows: 60 | 61 | ```shell 62 | nix run github:mirkolenz/makejinja -- -i ./input -o ./output 63 | ``` 64 | 65 | ### Docker 66 | 67 | We automatically publish an image to the [GitHub Container Registry](https://ghcr.io/mirkolenz/makejinja). 68 | To use it, mount a directory to the container and pass the options as the command: 69 | 70 | ```shell 71 | docker run --rm -v $(pwd)/data:/data ghcr.io/mirkolenz/makejinja:latest -i /data/input -o /data/output 72 | ``` 73 | 74 | ## Usage in Terminal / Command Line 75 | 76 | In its default configuration, makejinja searches the input directory recursively for files ending in `.jinja`. 77 | It then renders these files and writes them to the output directory, preserving the directory structure. 78 | Our [documentation](https://mirkolenz.github.io/makejinja/makejinja/cli.html) contains a detailed description of all options and can also be accessed via `makejinja --help`. 79 | -------------------------------------------------------------------------------- /assets/demo.scenario: -------------------------------------------------------------------------------- 1 | $ cat demo.txt.jinja 2 | {% for word in ["lorem", "ipsum", "dolor"] %} 3 | Word {{loop.index}}: {{word | capitalize}} 4 | {% endfor %} 5 | 6 | $ makejinja -i demo.txt.jinja -o . 7 | Render file 'demo.txt.jinja' -> 'demo.txt' 8 | 9 | $ cat demo.txt 10 | Word 1: Lorem 11 | Word 2: Ipsum 12 | Word 3: Dolor 13 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirkolenz/makejinja/b41e64f6e44aede7a47a5d37fd7a0e00337e5d46/assets/logo.png -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | callPackage, 5 | fetchFromGitHub, 6 | python3, 7 | jetbrains-mono, 8 | asciinema-scenario, 9 | asciinema-agg, 10 | uv2nix, 11 | pyproject-nix, 12 | pyproject-build-systems, 13 | }: 14 | let 15 | pdocRepo = fetchFromGitHub { 16 | owner = "mitmproxy"; 17 | repo = "pdoc"; 18 | tag = "v15.0.1"; 19 | hash = "sha256-HDrDGnK557EWbBQtsvDzTst3oV0NjLRm4ilXaxd6/j8="; 20 | }; 21 | workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; 22 | pyprojectOverlay = workspace.mkPyprojectOverlay { 23 | sourcePreference = "wheel"; 24 | }; 25 | pyprojectOverrides = final: prev: { 26 | makejinja = prev.makejinja.overrideAttrs (old: { 27 | meta = (old.meta or { }) // { 28 | mainProgram = "makejinja"; 29 | maintainers = with lib.maintainers; [ mirkolenz ]; 30 | license = lib.licenses.mit; 31 | homepage = "https://github.com/mirkolenz/makejinja"; 32 | description = "Generate entire directory structures using Jinja templates with support for external data and custom plugins."; 33 | platforms = with lib.platforms; darwin ++ linux; 34 | }; 35 | passthru = lib.recursiveUpdate (old.passthru or { }) { 36 | tests.pytest = stdenv.mkDerivation { 37 | name = "${final.makejinja.name}-pytest"; 38 | inherit (final.makejinja) src; 39 | nativeBuildInputs = [ 40 | (final.mkVirtualEnv "makejinja-test-env" { 41 | makejinja = [ "test" ]; 42 | }) 43 | ]; 44 | dontConfigure = true; 45 | buildPhase = '' 46 | runHook preBuild 47 | pytest --cov-report=html 48 | runHook postBuild 49 | ''; 50 | installPhase = '' 51 | runHook preInstall 52 | mv htmlcov $out 53 | runHook postInstall 54 | ''; 55 | }; 56 | docs = stdenv.mkDerivation { 57 | name = "${final.makejinja.name}-docs"; 58 | inherit (final.makejinja) src; 59 | nativeBuildInputs = [ 60 | (final.mkVirtualEnv "makejinja-docs-env" { 61 | makejinja = [ "docs" ]; 62 | }) 63 | asciinema-scenario 64 | asciinema-agg 65 | ]; 66 | dontConfigure = true; 67 | buildPhase = '' 68 | runHook preBuild 69 | 70 | { 71 | echo '```txt' 72 | COLUMNS=120 makejinja --help 73 | echo '```' 74 | } > ./manpage.md 75 | 76 | asciinema-scenario ./assets/demo.scenario > ./assets/demo.cast 77 | agg \ 78 | --font-dir "${jetbrains-mono}/share/fonts/truetype" \ 79 | --font-family "JetBrains Mono" \ 80 | --theme monokai \ 81 | ./assets/demo.cast ./assets/demo.gif 82 | 83 | pdoc \ 84 | -d google \ 85 | -t ${pdocRepo}/examples/dark-mode \ 86 | --math \ 87 | --logo https://raw.githubusercontent.com/mirkolenz/makejinja/main/assets/logo.png \ 88 | -o "$out" \ 89 | ./src/makejinja 90 | 91 | runHook postBuild 92 | ''; 93 | installPhase = '' 94 | runHook preInstall 95 | 96 | mkdir -p "$out/assets" 97 | cp -rf ./assets/{*.png,*.gif} "$out/assets/" 98 | 99 | runHook postInstall 100 | ''; 101 | }; 102 | }; 103 | }); 104 | }; 105 | baseSet = callPackage pyproject-nix.build.packages { 106 | python = python3; 107 | }; 108 | in 109 | { 110 | inherit workspace; 111 | inherit (callPackage pyproject-nix.build.util { }) mkApplication; 112 | pythonSet = baseSet.overrideScope ( 113 | lib.composeManyExtensions [ 114 | pyproject-build-systems.overlays.wheel 115 | pyprojectOverlay 116 | pyprojectOverrides 117 | ] 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1743550720, 9 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-parts_2": { 22 | "inputs": { 23 | "nixpkgs-lib": "nixpkgs-lib_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1736143030, 27 | "narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=", 28 | "owner": "hercules-ci", 29 | "repo": "flake-parts", 30 | "rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "hercules-ci", 35 | "repo": "flake-parts", 36 | "type": "github" 37 | } 38 | }, 39 | "flocken": { 40 | "inputs": { 41 | "flake-parts": "flake-parts_2", 42 | "nixpkgs": [ 43 | "nixpkgs" 44 | ], 45 | "systems": "systems" 46 | }, 47 | "locked": { 48 | "lastModified": 1737581094, 49 | "narHash": "sha256-MSjyNy4zENfngnSdXQ6ef/wwACB0jfDyhy0qkI67F9A=", 50 | "owner": "mirkolenz", 51 | "repo": "flocken", 52 | "rev": "97921a2650cb3de20c2a5ee591b00a6d5099fc40", 53 | "type": "github" 54 | }, 55 | "original": { 56 | "owner": "mirkolenz", 57 | "ref": "v2", 58 | "repo": "flocken", 59 | "type": "github" 60 | } 61 | }, 62 | "nixpkgs": { 63 | "locked": { 64 | "lastModified": 1746332716, 65 | "narHash": "sha256-VBmKSkmw9PYBCEGhBKzORjx+nwNZkPZyHcUHE21A/ws=", 66 | "owner": "nixos", 67 | "repo": "nixpkgs", 68 | "rev": "6b1c028bce9c89e9824cde040d6986d428296055", 69 | "type": "github" 70 | }, 71 | "original": { 72 | "owner": "nixos", 73 | "ref": "nixpkgs-unstable", 74 | "repo": "nixpkgs", 75 | "type": "github" 76 | } 77 | }, 78 | "nixpkgs-lib": { 79 | "locked": { 80 | "lastModified": 1743296961, 81 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 82 | "owner": "nix-community", 83 | "repo": "nixpkgs.lib", 84 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 85 | "type": "github" 86 | }, 87 | "original": { 88 | "owner": "nix-community", 89 | "repo": "nixpkgs.lib", 90 | "type": "github" 91 | } 92 | }, 93 | "nixpkgs-lib_2": { 94 | "locked": { 95 | "lastModified": 1735774519, 96 | "narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=", 97 | "type": "tarball", 98 | "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" 99 | }, 100 | "original": { 101 | "type": "tarball", 102 | "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" 103 | } 104 | }, 105 | "pyproject-build-systems": { 106 | "inputs": { 107 | "nixpkgs": [ 108 | "nixpkgs" 109 | ], 110 | "pyproject-nix": [ 111 | "pyproject-nix" 112 | ], 113 | "uv2nix": [ 114 | "uv2nix" 115 | ] 116 | }, 117 | "locked": { 118 | "lastModified": 1744599653, 119 | "narHash": "sha256-nysSwVVjG4hKoOjhjvE6U5lIKA8sEr1d1QzEfZsannU=", 120 | "owner": "pyproject-nix", 121 | "repo": "build-system-pkgs", 122 | "rev": "7dba6dbc73120e15b558754c26024f6c93015dd7", 123 | "type": "github" 124 | }, 125 | "original": { 126 | "owner": "pyproject-nix", 127 | "repo": "build-system-pkgs", 128 | "type": "github" 129 | } 130 | }, 131 | "pyproject-nix": { 132 | "inputs": { 133 | "nixpkgs": [ 134 | "nixpkgs" 135 | ] 136 | }, 137 | "locked": { 138 | "lastModified": 1746146146, 139 | "narHash": "sha256-60+mzI2lbgn+G8F5mz+cmkDvHFn4s5oqcOna1SzYy74=", 140 | "owner": "pyproject-nix", 141 | "repo": "pyproject.nix", 142 | "rev": "3e9623bdd86a3c545e82b7f97cfdba5f07232d9a", 143 | "type": "github" 144 | }, 145 | "original": { 146 | "owner": "pyproject-nix", 147 | "repo": "pyproject.nix", 148 | "type": "github" 149 | } 150 | }, 151 | "root": { 152 | "inputs": { 153 | "flake-parts": "flake-parts", 154 | "flocken": "flocken", 155 | "nixpkgs": "nixpkgs", 156 | "pyproject-build-systems": "pyproject-build-systems", 157 | "pyproject-nix": "pyproject-nix", 158 | "systems": "systems_2", 159 | "treefmt-nix": "treefmt-nix", 160 | "uv2nix": "uv2nix" 161 | } 162 | }, 163 | "systems": { 164 | "locked": { 165 | "lastModified": 1681028828, 166 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 167 | "owner": "nix-systems", 168 | "repo": "default", 169 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 170 | "type": "github" 171 | }, 172 | "original": { 173 | "owner": "nix-systems", 174 | "repo": "default", 175 | "type": "github" 176 | } 177 | }, 178 | "systems_2": { 179 | "locked": { 180 | "lastModified": 1681028828, 181 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 182 | "owner": "nix-systems", 183 | "repo": "default", 184 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 185 | "type": "github" 186 | }, 187 | "original": { 188 | "owner": "nix-systems", 189 | "repo": "default", 190 | "type": "github" 191 | } 192 | }, 193 | "treefmt-nix": { 194 | "inputs": { 195 | "nixpkgs": [ 196 | "nixpkgs" 197 | ] 198 | }, 199 | "locked": { 200 | "lastModified": 1746216483, 201 | "narHash": "sha256-4h3s1L/kKqt3gMDcVfN8/4v2jqHrgLIe4qok4ApH5x4=", 202 | "owner": "numtide", 203 | "repo": "treefmt-nix", 204 | "rev": "29ec5026372e0dec56f890e50dbe4f45930320fd", 205 | "type": "github" 206 | }, 207 | "original": { 208 | "owner": "numtide", 209 | "repo": "treefmt-nix", 210 | "type": "github" 211 | } 212 | }, 213 | "uv2nix": { 214 | "inputs": { 215 | "nixpkgs": [ 216 | "nixpkgs" 217 | ], 218 | "pyproject-nix": [ 219 | "pyproject-nix" 220 | ] 221 | }, 222 | "locked": { 223 | "lastModified": 1746416492, 224 | "narHash": "sha256-mBhC2T9DW7KrbFt9bKMKZS1z4bHQRu5i0s+A9bszErA=", 225 | "owner": "pyproject-nix", 226 | "repo": "uv2nix", 227 | "rev": "e1738524915ff769ec8e2a6d982006368cf08a6f", 228 | "type": "github" 229 | }, 230 | "original": { 231 | "owner": "pyproject-nix", 232 | "repo": "uv2nix", 233 | "type": "github" 234 | } 235 | } 236 | }, 237 | "root": "root", 238 | "version": 7 239 | } 240 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | flake-parts.url = "github:hercules-ci/flake-parts"; 5 | systems.url = "github:nix-systems/default"; 6 | flocken = { 7 | url = "github:mirkolenz/flocken/v2"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | treefmt-nix = { 11 | url = "github:numtide/treefmt-nix"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | pyproject-nix = { 15 | url = "github:pyproject-nix/pyproject.nix"; 16 | inputs.nixpkgs.follows = "nixpkgs"; 17 | }; 18 | uv2nix = { 19 | url = "github:pyproject-nix/uv2nix"; 20 | inputs.pyproject-nix.follows = "pyproject-nix"; 21 | inputs.nixpkgs.follows = "nixpkgs"; 22 | }; 23 | pyproject-build-systems = { 24 | url = "github:pyproject-nix/build-system-pkgs"; 25 | inputs.pyproject-nix.follows = "pyproject-nix"; 26 | inputs.uv2nix.follows = "uv2nix"; 27 | inputs.nixpkgs.follows = "nixpkgs"; 28 | }; 29 | }; 30 | nixConfig = { 31 | extra-substituters = [ 32 | "https://nix-community.cachix.org" 33 | "https://mirkolenz.cachix.org" 34 | "https://pyproject-nix.cachix.org" 35 | ]; 36 | extra-trusted-public-keys = [ 37 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 38 | "mirkolenz.cachix.org-1:R0dgCJ93t33K/gncNbKgUdJzwgsYVXeExRsZNz5jpho=" 39 | "pyproject-nix.cachix.org-1:UNzugsOlQIu2iOz0VyZNBQm2JSrL/kwxeCcFGw+jMe0=" 40 | ]; 41 | }; 42 | outputs = 43 | inputs@{ 44 | self, 45 | nixpkgs, 46 | flake-parts, 47 | systems, 48 | flocken, 49 | uv2nix, 50 | ... 51 | }: 52 | flake-parts.lib.mkFlake { inherit inputs; } { 53 | systems = import systems; 54 | imports = [ 55 | inputs.flake-parts.flakeModules.easyOverlay 56 | inputs.treefmt-nix.flakeModule 57 | ]; 58 | perSystem = 59 | { 60 | pkgs, 61 | system, 62 | lib, 63 | config, 64 | ... 65 | }: 66 | let 67 | inherit 68 | (pkgs.callPackage ./default.nix { 69 | inherit (inputs) uv2nix pyproject-nix pyproject-build-systems; 70 | }) 71 | pythonSet 72 | workspace 73 | mkApplication 74 | ; 75 | in 76 | { 77 | _module.args.pkgs = import nixpkgs { 78 | inherit system; 79 | overlays = lib.singleton ( 80 | final: prev: { 81 | python3 = final.python312; 82 | uv = uv2nix.packages.${system}.uv-bin; 83 | } 84 | ); 85 | }; 86 | overlayAttrs = { 87 | inherit (config.packages) makejinja; 88 | }; 89 | checks = pythonSet.makejinja.passthru.tests // { 90 | inherit (pythonSet.makejinja.passthru) docs; 91 | }; 92 | treefmt = { 93 | projectRootFile = "flake.nix"; 94 | programs = { 95 | ruff-check.enable = true; 96 | ruff-format.enable = true; 97 | nixfmt.enable = true; 98 | }; 99 | }; 100 | packages = { 101 | inherit (pythonSet.makejinja.passthru) docs; 102 | default = config.packages.makejinja; 103 | makejinja = mkApplication { 104 | venv = pythonSet.mkVirtualEnv "makejinja-env" workspace.deps.optionals; 105 | package = pythonSet.makejinja; 106 | }; 107 | docker = pkgs.dockerTools.streamLayeredImage { 108 | name = "makejinja"; 109 | tag = "latest"; 110 | created = "now"; 111 | config.Entrypoint = [ (lib.getExe config.packages.makejinja) ]; 112 | }; 113 | release-env = pkgs.buildEnv { 114 | name = "release-env"; 115 | paths = with pkgs; [ 116 | uv 117 | python3 118 | ]; 119 | }; 120 | }; 121 | legacyPackages.docker-manifest = flocken.legacyPackages.${system}.mkDockerManifest { 122 | github = { 123 | enable = true; 124 | token = "$GH_TOKEN"; 125 | }; 126 | version = builtins.getEnv "VERSION"; 127 | imageStreams = with self.packages; [ 128 | x86_64-linux.docker 129 | aarch64-linux.docker 130 | ]; 131 | }; 132 | devShells.default = pkgs.mkShell { 133 | packages = with pkgs; [ 134 | uv 135 | config.treefmt.build.wrapper 136 | ]; 137 | UV_PYTHON = lib.getExe pkgs.python3; 138 | shellHook = '' 139 | uv sync --all-extras --locked 140 | ''; 141 | }; 142 | }; 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "makejinja" 3 | version = "2.7.2" 4 | description = "Generate entire directory structures using Jinja templates with support for external data and custom plugins" 5 | authors = [{ name = "Mirko Lenz", email = "mirko@mirkolenz.com" }] 6 | readme = "README.md" 7 | keywords = [ 8 | "jinja2", 9 | "home-assistant", 10 | "hassio", 11 | "dashboard", 12 | "lovelace", 13 | "template", 14 | "generator", 15 | "cli", 16 | "tool", 17 | "library", 18 | ] 19 | classifiers = [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Environment :: Console", 22 | "Framework :: Pytest", 23 | "Intended Audience :: Developers", 24 | "Intended Audience :: End Users/Desktop", 25 | "Intended Audience :: System Administrators", 26 | "License :: OSI Approved :: MIT License", 27 | "Natural Language :: English", 28 | "Operating System :: OS Independent", 29 | "Programming Language :: Python :: 3.11", 30 | "Programming Language :: Python :: 3.12", 31 | "Programming Language :: Python :: 3.13", 32 | "Programming Language :: Python :: 3", 33 | "Topic :: File Formats", 34 | "Topic :: Home Automation", 35 | "Topic :: Software Development :: Code Generators", 36 | "Topic :: Software Development :: Libraries :: Python Modules", 37 | "Topic :: System :: Software Distribution", 38 | "Topic :: System :: Systems Administration", 39 | "Topic :: Text Processing :: Markup", 40 | "Topic :: Utilities", 41 | "Typing :: Typed", 42 | ] 43 | requires-python = ">=3.11" 44 | dependencies = [ 45 | "jinja2>=3,<4", 46 | "pyyaml>=6,<7", 47 | "rich-click>=1,<2", 48 | "typed-settings[attrs,cattrs,click]>=23,<25", 49 | "immutables>=0.20,<1", 50 | ] 51 | 52 | [project.urls] 53 | Repository = "https://github.com/mirkolenz/makejinja" 54 | Homepage = "https://mirkolenz.github.io/makejinja/" 55 | Documentation = "https://mirkolenz.github.io/makejinja/makejinja/cli.html" 56 | Issues = "https://github.com/mirkolenz/makejinja/issues" 57 | Changelog = "https://github.com/mirkolenz/makejinja/releases" 58 | 59 | [project.scripts] 60 | makejinja = "makejinja.cli:makejinja_cli" 61 | 62 | [dependency-groups] 63 | test = ["pytest>=8,<9", "pytest-cov>=6,<7"] 64 | docs = ["pdoc>=15,<16"] 65 | 66 | [build-system] 67 | requires = ["setuptools>=61"] 68 | build-backend = "setuptools.build_meta" 69 | 70 | [tool.uv] 71 | default-groups = ["test", "docs"] 72 | 73 | [tool.pytest.ini_options] 74 | addopts = "--cov makejinja --cov-report term-missing" 75 | 76 | [tool.ruff] 77 | target-version = "py311" 78 | select = ["E", "W", "F", "B", "UP"] 79 | ignore = ["E501"] 80 | 81 | [tool.ruff.pydocstyle] 82 | convention = "google" 83 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>mirkolenz/renovate-preset"], 4 | "lockFileMaintenance": { 5 | "enabled": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/makejinja/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | **[🌟 GitHub Project 🌟](https://github.com/mirkolenz/makejinja)** 3 | 4 | ![makejinja demonstration](../../assets/demo.gif) 5 | 6 | .. include:: ../../README.md 7 | :start-after: 8 | 9 | ## Usage as a Library 10 | 11 | While mainly intended to be used as a command line tool, makejinja can also be from Python directly. 12 | """ 13 | 14 | from . import cli, config, plugin 15 | from .app import makejinja 16 | 17 | loader = plugin 18 | 19 | __all__ = ["makejinja", "config", "plugin", "loader", "cli"] 20 | -------------------------------------------------------------------------------- /src/makejinja/__main__.py: -------------------------------------------------------------------------------- 1 | from .cli import makejinja_cli 2 | 3 | makejinja_cli() 4 | -------------------------------------------------------------------------------- /src/makejinja/app.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | import os 4 | import shutil 5 | import subprocess 6 | import sys 7 | import tomllib 8 | from collections import abc 9 | from inspect import signature 10 | from pathlib import Path 11 | from typing import Any 12 | 13 | import yaml 14 | from jinja2 import BaseLoader, ChoiceLoader, DictLoader, Environment, FileSystemLoader 15 | from jinja2.environment import load_extensions 16 | from jinja2.utils import import_string 17 | from rich import print 18 | 19 | from makejinja.config import Config 20 | from makejinja.plugin import Data, MutableData, PathFilter, Plugin 21 | 22 | __all__ = ["makejinja"] 23 | 24 | STDOUT_PATH = Path("/dev/stdout").resolve() 25 | STDIN_PATH = Path("/dev/stdin").resolve() 26 | 27 | 28 | def log(message: str, config: Config) -> None: 29 | if not config.quiet and config.output != STDOUT_PATH: 30 | print(message) 31 | 32 | 33 | def exec(cmd: str) -> None: 34 | subprocess.run(cmd, shell=True, check=True) 35 | 36 | 37 | def makejinja(config: Config): 38 | """makejinja can be used to automatically generate files from [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/).""" 39 | 40 | for cmd in config.exec_pre: 41 | exec(cmd) 42 | 43 | for path in config.import_paths: 44 | sys.path.append(str(path.resolve())) 45 | 46 | data = load_data(config) 47 | 48 | if config.output.is_dir() and config.clean: 49 | log(f"Remove output '{config.output}'", config) 50 | 51 | shutil.rmtree(config.output) 52 | 53 | if not single_input_output_file(config): 54 | config.output.mkdir(exist_ok=True, parents=True) 55 | 56 | env = init_jinja_env(config, data) 57 | plugins: list[Plugin] = [] 58 | 59 | for plugin_name in itertools.chain(config.plugins, config.loaders): 60 | plugins.append(load_plugin(plugin_name, env, data, config)) 61 | 62 | plugin_path_filters: list[PathFilter] = [] 63 | 64 | for plugin in plugins: 65 | if hasattr(plugin, "path_filters"): 66 | plugin_path_filters.extend(plugin.path_filters()) 67 | 68 | # Save rendered files to avoid duplicate work 69 | # Even if two files are in two separate dirs, they will have the same template name (i.e., relative path) 70 | # and thus only the first one will be rendered every time 71 | # Key: output_path, Value: input_path 72 | rendered_files: dict[Path, Path] = {} 73 | 74 | # Save rendered dirs to later copy metadata 75 | # Key: output_path, Value: input_path 76 | rendered_dirs: dict[Path, Path] = {} 77 | 78 | for user_input_path in config.inputs: 79 | if user_input_path.is_file() or user_input_path == STDIN_PATH: 80 | handle_input_file(user_input_path, config, env, rendered_files) 81 | elif user_input_path.is_dir(): 82 | handle_input_dir( 83 | user_input_path, 84 | config, 85 | env, 86 | rendered_files, 87 | rendered_dirs, 88 | plugin_path_filters, 89 | ) 90 | 91 | postprocess_rendered_dirs(config, rendered_dirs) 92 | 93 | for cmd in config.exec_post: 94 | exec(cmd) 95 | 96 | 97 | def postprocess_rendered_dirs( 98 | config: Config, 99 | rendered_dirs: abc.Mapping[Path, Path], 100 | ) -> None: 101 | # Start with the deepest directory and work our way up, otherwise the statistics could be modified after copying 102 | for output_path, input_path in sorted( 103 | rendered_dirs.items(), key=lambda x: x[0], reverse=True 104 | ): 105 | if not config.keep_empty and not any(output_path.iterdir()): 106 | log(f"Remove empty dir '{output_path}'", config) 107 | shutil.rmtree(output_path) 108 | 109 | elif config.copy_metadata: 110 | log(f"Copy dir metadata '{input_path}' -> '{output_path}'", config) 111 | shutil.copystat(input_path, output_path) 112 | 113 | 114 | def single_input_output_file(config: Config) -> bool: 115 | """Check if the user provided a single input and a single output""" 116 | return ( 117 | len(config.inputs) <= 1 118 | and not any(path.is_dir() for path in config.inputs) 119 | and ( 120 | config.output == STDOUT_PATH 121 | or config.output.suffix != "" 122 | or config.output.is_file() 123 | ) 124 | and not config.output.is_dir() 125 | ) 126 | 127 | 128 | def handle_input_file( 129 | input_path: Path, 130 | config: Config, 131 | env: Environment, 132 | rendered_files: abc.MutableMapping[Path, Path], 133 | ) -> None: 134 | relative_path = Path(input_path.name) 135 | output_path = generate_output_path(config, relative_path) 136 | 137 | if output_path not in rendered_files: 138 | render_file( 139 | input_path, 140 | str(relative_path), 141 | output_path, 142 | config, 143 | env, 144 | enforce_jinja_suffix=False, 145 | ) 146 | 147 | rendered_files[output_path] = input_path 148 | 149 | 150 | def handle_input_dir( 151 | user_input_path: Path, 152 | config: Config, 153 | env: Environment, 154 | rendered_files: abc.MutableMapping[Path, Path], 155 | rendered_dirs: abc.MutableMapping[Path, Path], 156 | plugin_path_filters: abc.Sequence[abc.Callable[[Path], bool]], 157 | ) -> None: 158 | input_paths = ( 159 | input_path 160 | for include_pattern in config.include_patterns 161 | for input_path in sorted(user_input_path.glob(include_pattern)) 162 | ) 163 | # If the user provided a Jinja suffix, enforce it 164 | enforce_jinja_suffix = bool(config.jinja_suffix) 165 | 166 | for input_path in input_paths: 167 | relative_path = input_path.relative_to(user_input_path) 168 | output_path = generate_output_path(config, relative_path) 169 | 170 | exclude_pattern_match = any( 171 | input_path.match(x) for x in config.exclude_patterns 172 | ) 173 | path_filter_match = any( 174 | not path_filter(input_path) for path_filter in plugin_path_filters 175 | ) 176 | if exclude_pattern_match or path_filter_match: 177 | log(f"Skip excluded path '{input_path}'", config) 178 | 179 | elif input_path.is_file() and output_path not in rendered_files: 180 | render_file( 181 | input_path, 182 | str(relative_path), 183 | output_path, 184 | config, 185 | env, 186 | enforce_jinja_suffix, 187 | ) 188 | rendered_files[output_path] = input_path 189 | 190 | elif input_path.is_dir() and output_path not in rendered_dirs: 191 | render_dir(input_path, output_path, config) 192 | rendered_dirs[output_path] = input_path 193 | 194 | 195 | def generate_output_path(config: Config, relative_path: Path) -> Path: 196 | if single_input_output_file(config): 197 | return config.output 198 | 199 | output_file = config.output / relative_path 200 | 201 | if relative_path.suffix == config.jinja_suffix and not config.keep_jinja_suffix: 202 | output_file = output_file.with_suffix("") 203 | 204 | return output_file 205 | 206 | 207 | def init_jinja_env( 208 | config: Config, 209 | data: Data, 210 | ) -> Environment: 211 | file_loader = DictLoader( 212 | { 213 | path.name: path.read_text() 214 | for path in config.inputs 215 | if path.is_file() or path == STDIN_PATH 216 | } 217 | ) 218 | dir_loader = FileSystemLoader([path for path in config.inputs if path.is_dir()]) 219 | loaders: list[BaseLoader] = [file_loader, dir_loader] 220 | 221 | env = Environment( 222 | loader=ChoiceLoader(loaders), 223 | extensions=config.extensions, 224 | block_start_string=config.delimiter.block_start, 225 | block_end_string=config.delimiter.block_end, 226 | variable_start_string=config.delimiter.variable_start, 227 | variable_end_string=config.delimiter.variable_end, 228 | comment_start_string=config.delimiter.comment_start, 229 | comment_end_string=config.delimiter.comment_end, 230 | line_statement_prefix=config.prefix.line_statement, 231 | line_comment_prefix=config.prefix.line_comment, 232 | trim_blocks=config.whitespace.trim_blocks, 233 | lstrip_blocks=config.whitespace.lstrip_blocks, 234 | newline_sequence=config.whitespace.newline_sequence, # type: ignore 235 | keep_trailing_newline=config.whitespace.keep_trailing_newline, 236 | optimized=config.internal.optimized, 237 | undefined=config.undefined.value, 238 | finalize=None, 239 | autoescape=config.internal.autoescape, 240 | cache_size=config.internal.cache_size, 241 | auto_reload=config.internal.auto_reload, 242 | bytecode_cache=None, 243 | enable_async=config.internal.enable_async, 244 | ) 245 | 246 | env.globals.update(data) 247 | env.globals["env"] = os.environ 248 | 249 | return env 250 | 251 | 252 | def from_yaml(path: Path) -> dict[str, Any]: 253 | data = {} 254 | 255 | with path.open("rb") as fp: 256 | for doc in yaml.safe_load_all(fp): 257 | data |= doc 258 | 259 | return data 260 | 261 | 262 | def from_toml(path: Path) -> dict[str, Any]: 263 | with path.open("rb") as fp: 264 | return tomllib.load(fp) 265 | 266 | 267 | def from_json(path: Path) -> dict[str, Any]: 268 | with path.open("rb") as fp: 269 | return json.load(fp) 270 | 271 | 272 | DATA_LOADERS: dict[str, abc.Callable[[Path], dict[str, Any]]] = { 273 | ".yaml": from_yaml, 274 | ".yml": from_yaml, 275 | ".toml": from_toml, 276 | ".json": from_json, 277 | } 278 | 279 | 280 | def collect_files(paths: abc.Iterable[Path], pattern: str = "**/*") -> list[Path]: 281 | files = [] 282 | 283 | for path in paths: 284 | if path.is_dir(): 285 | files.extend( 286 | file 287 | for file in sorted(path.glob(pattern)) 288 | if not file.name.startswith(".") and file.is_file() 289 | ) 290 | elif path.is_file(): 291 | files.append(path) 292 | 293 | return files 294 | 295 | 296 | def dict_nested_set(data: MutableData, dotted_key: str, value: Any) -> None: 297 | """Given `foo`, 'key1.key2.key3', 'something', set foo['key1']['key2']['key3'] = 'something' 298 | 299 | Source: https://stackoverflow.com/a/57561744 300 | """ 301 | 302 | # Start off pointing at the original dictionary that was passed in. 303 | here = data 304 | 305 | # Turn the string of key names into a list of strings. 306 | keys = dotted_key.split(".") 307 | 308 | # For every key *before* the last one, we concentrate on navigating through the dictionary. 309 | for key in keys[:-1]: 310 | # Try to find here[key]. If it doesn't exist, create it with an empty dictionary. Then, 311 | # update our `here` pointer to refer to the thing we just found (or created). 312 | here = here.setdefault(key, {}) 313 | 314 | # Finally, set the final key to the given value 315 | here[keys[-1]] = value 316 | 317 | 318 | def load_data(config: Config) -> dict[str, Any]: 319 | data: dict[str, Any] = {} 320 | 321 | for path in collect_files(config.data): 322 | if loader := DATA_LOADERS.get(path.suffix): 323 | log(f"Load data '{path}'", config) 324 | 325 | data |= loader(path) 326 | else: 327 | log(f"Skip unsupported data '{path}'", config) 328 | 329 | for key, value in config.data_vars.items(): 330 | dict_nested_set(data, key, value) 331 | 332 | return data 333 | 334 | 335 | def load_plugin( 336 | plugin_name: str, env: Environment, data: Data, config: Config 337 | ) -> Plugin: 338 | cls: type[Plugin] = import_string(plugin_name) 339 | sig_params = signature(cls).parameters 340 | params: dict[str, Any] = {} 341 | 342 | if sig_params.get("env"): 343 | params["env"] = env 344 | if sig_params.get("environment"): 345 | params["environment"] = env 346 | if sig_params.get("data"): 347 | params["data"] = data 348 | if sig_params.get("config"): 349 | params["config"] = config 350 | 351 | plugin = cls(**params) 352 | 353 | if hasattr(plugin, "globals"): 354 | env.globals.update({func.__name__: func for func in plugin.globals()}) 355 | 356 | if hasattr(plugin, "functions"): 357 | env.globals.update({func.__name__: func for func in plugin.functions()}) 358 | 359 | if hasattr(plugin, "data"): 360 | env.globals.update(plugin.data()) 361 | 362 | if hasattr(plugin, "extensions"): 363 | load_extensions(env, plugin.extensions()) 364 | 365 | if hasattr(plugin, "filters"): 366 | env.filters.update({func.__name__: func for func in plugin.filters()}) 367 | 368 | if hasattr(plugin, "tests"): 369 | env.tests.update({func.__name__: func for func in plugin.tests()}) 370 | 371 | if hasattr(plugin, "policies"): 372 | env.policies.update(plugin.policies()) 373 | 374 | return plugin 375 | 376 | 377 | def render_dir(input: Path, output: Path, config: Config) -> None: 378 | if output.exists() and not config.force: 379 | log(f"Skip existing dir '{output}'", config) 380 | else: 381 | log(f"Create dir '{input}' -> '{output}'", config) 382 | 383 | output.mkdir(exist_ok=True) 384 | 385 | 386 | def render_file( 387 | input: Path, 388 | template_name: str, 389 | output: Path, 390 | config: Config, 391 | env: Environment, 392 | enforce_jinja_suffix: bool, 393 | ) -> None: 394 | if output.exists() and not config.force and output != STDOUT_PATH: 395 | log(f"Skip existing file '{output}'", config) 396 | 397 | elif input.suffix == config.jinja_suffix or not enforce_jinja_suffix: 398 | template = env.get_template(template_name) 399 | rendered = template.render() 400 | 401 | # Write the rendered template if it has content 402 | # Prevents empty macro definitions 403 | if rendered.strip() == "" and not config.keep_empty: 404 | log(f"Skip empty file '{input}'", config) 405 | else: 406 | log(f"Render file '{input}' -> '{output}'", config) 407 | 408 | with output.open("w") as fp: 409 | fp.write(rendered) 410 | 411 | if config.copy_metadata: 412 | shutil.copystat(input, output) 413 | 414 | else: 415 | log(f"Copy file '{input}' -> '{output}'", config) 416 | 417 | shutil.copy2(input, output) 418 | -------------------------------------------------------------------------------- /src/makejinja/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. include:: ../../manpage.md 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | import rich_click as click 8 | import typed_settings as ts 9 | 10 | from makejinja.config import OPTION_GROUPS, Config 11 | 12 | from .app import makejinja 13 | 14 | __all__: list[str] = [] 15 | 16 | click.rich_click.USE_MARKDOWN = True 17 | click.rich_click.OPTION_GROUPS = OPTION_GROUPS 18 | 19 | _ts_loaders = ts.default_loaders( 20 | appname="makejinja", config_files=(Path("makejinja.toml"),) 21 | ) 22 | 23 | 24 | @click.command("makejinja", context_settings={"help_option_names": ("--help", "-h")}) 25 | @click.version_option(None, "--version", "-v") 26 | @ts.click_options(Config, _ts_loaders) 27 | def makejinja_cli(config: Config): 28 | """makejinja can be used to automatically generate files from [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/). 29 | 30 | Instead of passing CLI options, you can also write them to a file called `makejinja.toml` in your working directory. 31 | **Note**: In this file, options may be named differently. 32 | Please refer to the file [`makejinja/config.py`](https://github.com/mirkolenz/makejinja/blob/main/makejinja/config.py) to see their actual names. 33 | You will also find an example here: [`makejinja/tests/data/makejinja.toml`](https://github.com/mirkolenz/makejinja/blob/main/tests/data/makejinja.toml). 34 | To override its location, you can set the environment variable `MAKEJINJA_SETTINGS` to the path of your config file. 35 | """ 36 | 37 | makejinja(config) 38 | 39 | 40 | if __name__ == "__main__": 41 | makejinja_cli() 42 | -------------------------------------------------------------------------------- /src/makejinja/config.py: -------------------------------------------------------------------------------- 1 | from collections import abc 2 | from enum import Enum 3 | from pathlib import Path 4 | 5 | import immutables 6 | import rich_click as click 7 | import typed_settings as ts 8 | from jinja2 import ( 9 | ChainableUndefined, 10 | DebugUndefined, 11 | StrictUndefined, 12 | ) 13 | from jinja2 import ( 14 | Undefined as DefaultUndefined, 15 | ) 16 | from jinja2.defaults import ( 17 | BLOCK_END_STRING, 18 | BLOCK_START_STRING, 19 | COMMENT_END_STRING, 20 | COMMENT_START_STRING, 21 | LINE_COMMENT_PREFIX, 22 | LINE_STATEMENT_PREFIX, 23 | NEWLINE_SEQUENCE, 24 | VARIABLE_END_STRING, 25 | VARIABLE_START_STRING, 26 | ) 27 | 28 | __all__ = ["Config", "Delimiter", "Internal", "Prefix", "Whitespace", "Undefined"] 29 | frozendict = immutables.Map 30 | 31 | 32 | class Undefined(Enum): 33 | """How to handle undefined variables.""" 34 | 35 | default = DefaultUndefined 36 | chainable = ChainableUndefined 37 | debug = DebugUndefined 38 | strict = StrictUndefined 39 | 40 | 41 | def _exclude_patterns_validator(instance, attribute, value) -> None: 42 | if any("**" in pattern for pattern in value): 43 | # raise ValueError("The recursive wildcard `**` is not supported by `exclude_patterns`.") 44 | print( 45 | "The recursive wildcard `**` is not supported by `exclude_patterns` (it acts like non-recursive `*`).", 46 | ) 47 | 48 | 49 | @ts.settings(frozen=True) 50 | class Delimiter: 51 | block_start: str = ts.option( 52 | default=BLOCK_START_STRING, help="The string marking the beginning of a block." 53 | ) 54 | block_end: str = ts.option( 55 | default=BLOCK_END_STRING, help="The string marking the end of a block." 56 | ) 57 | variable_start: str = ts.option( 58 | default=VARIABLE_START_STRING, 59 | help="The string marking the beginning of a print statement.", 60 | ) 61 | variable_end: str = ts.option( 62 | default=VARIABLE_END_STRING, 63 | help="The string marking the end of a print statement.", 64 | ) 65 | comment_start: str = ts.option( 66 | default=COMMENT_START_STRING, 67 | help="The string marking the beginning of a comment.", 68 | ) 69 | comment_end: str = ts.option( 70 | default=COMMENT_END_STRING, help="The string marking the end of a comment." 71 | ) 72 | 73 | 74 | @ts.settings(frozen=True) 75 | class Prefix: 76 | line_statement: str | None = ts.option( 77 | default=LINE_STATEMENT_PREFIX, 78 | help=( 79 | "If given and a string, this will be used as prefix for line based" 80 | " statements." 81 | ), 82 | ) 83 | line_comment: str | None = ts.option( 84 | default=LINE_COMMENT_PREFIX, 85 | help=( 86 | "If given and a string, this will be used as prefix for line based" 87 | " comments." 88 | ), 89 | ) 90 | 91 | 92 | @ts.settings(frozen=True) 93 | class Internal: 94 | optimized: bool = ts.option( 95 | default=True, 96 | click={"hidden": True}, 97 | help=( 98 | "Should the" 99 | " [optimizer](https://github.com/Pfern/jinja2/blob/master/jinja2/optimizer.py)" 100 | " be enabled?" 101 | ), 102 | ) 103 | autoescape: bool = ts.option( 104 | default=False, 105 | click={"param_decls": "--internal-autoescape", "hidden": True}, 106 | help=""" 107 | If set to `True` the XML/HTML autoescaping feature is enabled by default. 108 | For more details about autoescaping see `markupsafe.Markup`. 109 | """, 110 | ) 111 | cache_size: int = ts.option( 112 | default=0, 113 | click={"hidden": True}, 114 | help=""" 115 | The size of the cache. 116 | If the cache size is set to a positive number like `400`, 117 | it means that if more than 400 templates are loaded the loader will clean out the least recently used template. 118 | If the cache size is set to `0`, templates are recompiled all the time. 119 | If the cache size is `-1` the cache will not be cleaned. 120 | """, 121 | ) 122 | auto_reload: bool = ts.option( 123 | default=False, 124 | click={"hidden": True}, 125 | help=""" 126 | Some loaders load templates from locations where the template sources may change (ie: file system or database). 127 | If `auto_reload` is set to `True`, every time a template is requested, the loader checks if the source changed and if yes, 128 | it will reload the template. For higher performance it's possible to disable that. 129 | """, 130 | ) 131 | enable_async: bool = ts.option( 132 | default=False, 133 | click={"param_decls": "--internal-enable-async", "hidden": True}, 134 | help=""" 135 | If set to true this enables async template execution which allows using async functions and generators. 136 | """, 137 | ) 138 | 139 | 140 | @ts.settings(frozen=True) 141 | class Whitespace: 142 | trim_blocks: bool = ts.option( 143 | default=True, 144 | click={"param_decls": "--trim-blocks/--no-trim-blocks"}, 145 | help=""" 146 | If this is set to `True`, the first newline after a block is removed (block, not variable tag!). 147 | """, 148 | ) 149 | lstrip_blocks: bool = ts.option( 150 | default=True, 151 | click={"param_decls": "--lstrip-blocks/--no-lstrip-blocks"}, 152 | help=""" 153 | If this is set to `True`, leading spaces and tabs are stripped from the start of a line to a block. 154 | """, 155 | ) 156 | newline_sequence: str = ts.option( 157 | default=NEWLINE_SEQUENCE, 158 | click={"param_decls": "--newline-sequence"}, 159 | help=""" 160 | The sequence that starts a newline. 161 | The default is tailored for UNIX-like systems (Linux/macOS). 162 | """, 163 | ) 164 | keep_trailing_newline: bool = ts.option( 165 | default=True, 166 | click={"param_decls": "--keep-trailing-newline/--strip-trailing-newline"}, 167 | help=""" 168 | Preserve the trailing newline when rendering templates. 169 | The default is `False`, which causes a single newline, if present, to be stripped from the end of the template. 170 | """, 171 | ) 172 | 173 | 174 | @ts.settings(frozen=True) 175 | class Config: 176 | inputs: tuple[Path, ...] = ts.option( 177 | click={ 178 | "type": click.Path(exists=True, path_type=Path), 179 | "param_decls": ("--input", "-i"), 180 | }, 181 | help=""" 182 | Path to a directory containing template files or a single template file. 183 | It is passed to Jinja's [FileSystemLoader](https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.FileSystemLoader) when creating the environment. 184 | **Note:** This option may be passed multiple times to pass a list of values. 185 | If a template exists in multiple inputs, the last value with be used. 186 | """, 187 | ) 188 | output: Path = ts.option( 189 | click={ 190 | "type": click.Path(path_type=Path), 191 | "param_decls": ("--output", "-o"), 192 | }, 193 | help=""" 194 | Path to a directory where the rendered templates are stored. 195 | makejinja preserves the relative paths in the process, meaning that you can even use it on nested directories. 196 | """, 197 | ) 198 | include_patterns: tuple[str, ...] = ts.option( 199 | default=("**/*",), 200 | click={"param_decls": ("--include-pattern", "--include", "-I")}, 201 | help=""" 202 | Glob patterns to search for files in `inputs`. 203 | Accepts all pattern supported by [`fnmatch`](https://docs.python.org/3/library/fnmatch.html#module-fnmatch). 204 | If a file is matched by this pattern and does not end with the specified `jinja-suffix`, it is copied over to `output`. 205 | Multiple can be provided. 206 | **Note:** Do not add a special suffix used by your template files here, instead use the `jinja-suffix` option. 207 | """, 208 | ) 209 | exclude_patterns: tuple[str, ...] = ts.option( 210 | default=tuple(), 211 | click={"param_decls": ("--exclude-pattern", "--exclude", "-E")}, 212 | validator=_exclude_patterns_validator, 213 | help=""" 214 | Glob patterns pattern to exclude files matched. 215 | Applied against files discovered through `include_patterns` via `Path.match`. 216 | **Note:** The recursive wildcard `**` is not supported (it acts like non-recursive `*`). 217 | Multiple can be provided. 218 | """, 219 | ) 220 | jinja_suffix: str | None = ts.option( 221 | default=".jinja", 222 | help=""" 223 | File ending of Jinja template files. 224 | All files with this suffix in `inputs` matched by `pattern` are passed to the Jinja renderer. 225 | This suffix is not enforced for individual files passed to `inputs`. 226 | **Note:** Should be provided *with* the leading dot. 227 | If empty, all files are considered to be Jinja templates. 228 | """, 229 | ) 230 | keep_jinja_suffix: bool = ts.option( 231 | default=False, 232 | click={"param_decls": "--keep-jinja-suffix"}, 233 | help=""" 234 | Decide whether the specified `jinja-suffix` is removed from the file name after rendering. 235 | """, 236 | ) 237 | keep_empty: bool = ts.option( 238 | default=False, 239 | click={"param_decls": "--keep-empty"}, 240 | help=""" 241 | Some Jinja template files may be empty after rendering (e.g., if they only contain macros that are imported by other templates). 242 | By default, we do not copy such empty files. 243 | If there is a need to have them available anyway, you can adjust that. 244 | """, 245 | ) 246 | copy_metadata: bool = ts.option( 247 | default=False, 248 | click={"param_decls": ("--copy-metadata", "-m")}, 249 | help=""" 250 | Copy the file metadata (e.g., created/modified/permissions) from the input file using `shutil.copystat` 251 | """, 252 | ) 253 | data: tuple[Path, ...] = ts.option( 254 | default=tuple(), 255 | click={ 256 | "type": click.Path(exists=True, path_type=Path), 257 | "param_decls": ("--data", "-d"), 258 | }, 259 | help=""" 260 | Load variables from yaml/yml/toml/json files for use in your Jinja templates. 261 | The definitions are passed to Jinja as globals. 262 | Can either be a file or a directory containing files. 263 | **Note:** This option may be passed multiple times to pass a list of values. 264 | If multiple files are supplied, beware that previous declarations will be overwritten by newer ones. 265 | """, 266 | ) 267 | data_vars: abc.Mapping[str, str] = ts.option( 268 | default=frozendict(), 269 | click={ 270 | "param_decls": ("--data-var", "-D"), 271 | "help": """ 272 | Load variables from the command line for use in your Jinja templates. 273 | The definitions are applied after loading the data from files. 274 | When using dotted keys (e.g., `foo.bar=42`), the value is converted to a nested dictionary. 275 | Consequently, you can override values loaded from files. 276 | **Note:** This option may be passed multiple times. 277 | """, 278 | }, 279 | ) 280 | loaders: tuple[str, ...] = ts.option( 281 | default=tuple(), 282 | click={ 283 | "param_decls": ("--loader", "-l"), 284 | "hidden": True, 285 | }, 286 | help="Deprecated, use `--plugin` instead.", 287 | ) 288 | plugins: tuple[str, ...] = ts.option( 289 | default=tuple(), 290 | click={ 291 | "param_decls": ("--plugin", "-p"), 292 | }, 293 | help=""" 294 | Use custom Python code to adjust the used Jinja environment to your needs. 295 | The specified Python file should export a **class** containing a subset of the following functions: 296 | `filters`, `globals`, `data`, and `extensions`. 297 | In addition, you may add an `__init__` function that receives two positional arguments: 298 | the created Jinja environment and the data parsed from the files supplied to makejinja's `data` option. 299 | This allows you to apply arbitrary logic to makejinja. 300 | An import path can be specified either in dotted notation (`your.custom.Plugin`) 301 | or with a colon as object delimiter (`your.custom:Plugin`). 302 | **Note:** This option may be passed multiple times to pass a list of values. 303 | """, 304 | ) 305 | import_paths: tuple[Path, ...] = ts.option( 306 | default=(Path("."),), 307 | click={ 308 | "type": click.Path(exists=True, file_okay=False, path_type=Path), 309 | "param_decls": "--import-path", 310 | "show_default": "current working directory", 311 | }, 312 | help=""" 313 | In order to load plugins or Jinja extensions, the PYTHONPATH variable needs to be patched. 314 | The default value works for most use cases, but you may load other paths as well. 315 | """, 316 | ) 317 | extensions: tuple[str, ...] = ts.option( 318 | default=tuple(), 319 | click={"param_decls": ("--extension", "-e")}, 320 | help=""" 321 | List of Jinja extensions to use as strings of import paths. 322 | An overview of the built-in ones can be found on the [project website](https://jinja.palletsprojects.com/en/3.1.x/extensions/). 323 | **Note:** This option may be passed multiple times to pass a list of values. 324 | """, 325 | ) 326 | undefined: Undefined = ts.option( 327 | default=Undefined.default, 328 | help=( 329 | """ 330 | Whenever the template engine is unable to look up a name or access an attribute one of those objects is created and returned. 331 | Some operations on undefined values are then allowed, others fail. 332 | The closest to regular Python behavior is `strict` which disallows all operations beside testing if it is an undefined object. 333 | """ 334 | ), 335 | ) 336 | exec_pre: tuple[str, ...] = ts.option( 337 | default=tuple(), 338 | help=""" 339 | Shell commands to execute before rendering. 340 | """, 341 | ) 342 | exec_post: tuple[str, ...] = ts.option( 343 | default=tuple(), 344 | help=""" 345 | Shell commands to execute after rendering. 346 | """, 347 | ) 348 | clean: bool = ts.option( 349 | default=False, 350 | click={"param_decls": ("--clean", "-c")}, 351 | help=""" 352 | Whether to remove the output directory if it exists. 353 | """, 354 | ) 355 | force: bool = ts.option( 356 | default=False, 357 | click={"param_decls": ("--force", "-f")}, 358 | help=""" 359 | Whether to overwrite existing files in the output directory. 360 | """, 361 | ) 362 | quiet: bool = ts.option( 363 | default=False, 364 | click={"param_decls": ("--quiet", "-q")}, 365 | help=""" 366 | Print no information about the rendering process. 367 | """, 368 | ) 369 | delimiter: Delimiter = Delimiter() 370 | prefix: Prefix = Prefix() 371 | whitespace: Whitespace = Whitespace() 372 | internal: Internal = Internal() 373 | 374 | 375 | OPTION_GROUPS = { 376 | "makejinja": [ 377 | { 378 | "name": "Input/Output", 379 | "options": [ 380 | "--input", 381 | "--output", 382 | "--include-pattern", 383 | "--exclude-pattern", 384 | "--jinja-suffix", 385 | "--keep-jinja-suffix", 386 | "--keep-empty", 387 | "--copy-metadata", 388 | ], 389 | }, 390 | { 391 | "name": "Jinja Environment", 392 | "options": [ 393 | "--data", 394 | "--data-var", 395 | "--plugin", 396 | "--import-path", 397 | "--extension", 398 | "--undefined", 399 | ], 400 | }, 401 | { 402 | "name": "Jinja Whitespace", 403 | "options": [ 404 | "--lstrip-blocks", 405 | "--trim-blocks", 406 | "--keep-trailing-newline", 407 | "--newline-sequence", 408 | ], 409 | }, 410 | { 411 | "name": "Jinja Delimiters", 412 | "options": [ 413 | "--delimiter-block-start", 414 | "--delimiter-block-end", 415 | "--delimiter-comment-start", 416 | "--delimiter-comment-end", 417 | "--delimiter-variable-start", 418 | "--delimiter-variable-end", 419 | ], 420 | }, 421 | { 422 | "name": "Jinja Prefixes", 423 | "options": [ 424 | "--prefix-line-statement", 425 | "--prefix-line-comment", 426 | ], 427 | }, 428 | { 429 | "name": "Shell Hooks", 430 | "options": [ 431 | "--exec-pre", 432 | "--exec-post", 433 | ], 434 | }, 435 | ] 436 | } 437 | -------------------------------------------------------------------------------- /src/makejinja/plugin.py: -------------------------------------------------------------------------------- 1 | from collections import abc 2 | from pathlib import Path 3 | from typing import Any, Protocol 4 | 5 | from jinja2 import Environment 6 | from jinja2.ext import Extension 7 | 8 | from makejinja.config import Config 9 | 10 | __all__ = ["Plugin"] 11 | 12 | Extensions = abc.Sequence[type[Extension]] 13 | Filter = abc.Callable[[Any], Any] 14 | Filters = abc.Sequence[Filter] 15 | Function = abc.Callable[..., Any] 16 | Functions = abc.Sequence[Function] 17 | Test = abc.Callable[..., Any] 18 | Tests = abc.Sequence[Test] 19 | Policies = abc.Mapping[str, Any] 20 | MutableData = abc.MutableMapping[str, Any] 21 | Data = abc.Mapping[str, Any] 22 | PathFilter = abc.Callable[[Path], bool] 23 | PathFilters = abc.Sequence[PathFilter] 24 | 25 | 26 | class Plugin(Protocol): 27 | """Extend the functionality of makejinja with a plugin implementing a subset of this protocol.""" 28 | 29 | def __init__(self, *, env: Environment, data: Data, config: Config) -> None: 30 | pass 31 | 32 | def functions(self) -> Functions: 33 | return [] 34 | 35 | def data(self) -> Data: 36 | return {} 37 | 38 | def filters(self) -> Filters: 39 | return [] 40 | 41 | def tests(self) -> Tests: 42 | return [] 43 | 44 | def policies(self) -> Policies: 45 | return {} 46 | 47 | def extensions(self) -> Extensions: 48 | return [] 49 | 50 | def path_filters(self) -> PathFilters: 51 | return [] 52 | 53 | # Deprecated: Use functions() and data() instead 54 | def globals(self) -> Functions: 55 | return [] 56 | 57 | 58 | AbstractLoader = Plugin 59 | -------------------------------------------------------------------------------- /src/makejinja/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirkolenz/makejinja/b41e64f6e44aede7a47a5d37fd7a0e00337e5d46/src/makejinja/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirkolenz/makejinja/b41e64f6e44aede7a47a5d37fd7a0e00337e5d46/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/README.md: -------------------------------------------------------------------------------- 1 | # Dashboard Example for Home Assistant 2 | 3 | This directory contains a fully working example for automatically generating a dashboard for Home Assistant. 4 | Assuming you run `makejinja` inside this directory (`tests/data`), it can be run without any arguments as it will load its options from the `makejinja.toml` file. 5 | 6 | **Note:** 7 | We adjust the default Jinja template delimiters so that there are no collisions with the Home Assistant template syntax. 8 | This way, you can even automatically generate correct templates for sensors and other use cases. 9 | 10 | The following files/directories are relevant: 11 | 12 | - `makejinja.toml`: Config file for makejinja invocation. 13 | - `input`: Regular `yaml` config files together with `yaml.jinja` config templates (these are rendered by makejinja). 14 | - `output`: Resulting directory tree after running makejinja with the command shown above. 15 | - `config`: Directory containing a `yaml` file with variables used in our Jinja templates. 16 | - `plugin.py`: Class with custom Jinja filters and global functions to use in our templates. 17 | -------------------------------------------------------------------------------- /tests/data/config/data.toml: -------------------------------------------------------------------------------- 1 | toml-value = "Hello world" 2 | -------------------------------------------------------------------------------- /tests/data/config/data.yaml: -------------------------------------------------------------------------------- 1 | areas: 2 | living_room: 3 | name: 4 | en: Living room 5 | de: Wohnzimmer 6 | icon: mdi:sofa 7 | entities: 8 | climate: 9 | - entity: climate.living_room 10 | name: Thermostat 11 | cover: 12 | - entity: cover.living_room_door 13 | name: 14 | en: Cover door 15 | de: Rollo Tür 16 | - entity: cover.living_room_window 17 | name: 18 | en: Cover window 19 | de: Rollo Fenster 20 | light: 21 | - entity: light.living_room_ceiling 22 | name: 23 | en: Ceiling 24 | de: Decke 25 | - entity: light.living_room_spots 26 | name: Spots 27 | binary_sensor: 28 | - entity: binary_sensor.living_room_door_contact 29 | name: 30 | en: Door 31 | de: Tür 32 | - entity: binary_sensor.living_room_window_contact 33 | name: 34 | en: Window 35 | de: Fenster 36 | media_player: 37 | - entity: media_player.living_room_homepod 38 | name: HomePod 39 | - entity: media_player.living_room_apple_tv 40 | name: Apple TV 41 | kitchen: 42 | name: 43 | en: Kitchen 44 | de: Küche 45 | icon: mdi:chef-hat 46 | entities: 47 | climate: 48 | - entity: climate.kitchen 49 | name: Thermostat 50 | - entity: cover.kitchen_window 51 | name: 52 | en: Cover window 53 | de: Rollo Fenster 54 | light: 55 | - entity: light.kitchen_spots 56 | name: Spots 57 | binary_sensor: 58 | - entity: binary_sensor.kitchen_window_contact 59 | name: 60 | en: Window 61 | de: Fenster 62 | media_player: 63 | - entity: media_player.kitchen_homepod 64 | name: HomePod 65 | -------------------------------------------------------------------------------- /tests/data/input1/empty-folder/empty-folder.yaml.jinja: -------------------------------------------------------------------------------- 1 | <# This file is here to test the --skip-empty feature for directories #> 2 | -------------------------------------------------------------------------------- /tests/data/input1/extra-file.yaml: -------------------------------------------------------------------------------- 1 | title: Overview 2 | views: !include_dir_merge_list views 3 | <% include 'include.yaml.partial' %> 4 | -------------------------------------------------------------------------------- /tests/data/input1/not-empty.yaml.jinja: -------------------------------------------------------------------------------- 1 | # This file is << 'not empty' | upper >> 2 | -------------------------------------------------------------------------------- /tests/data/input1/secret.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /tests/data/input1/ui-lovelace.yaml.jinja: -------------------------------------------------------------------------------- 1 | title: Overview 2 | views: !include_dir_merge_list views 3 | <% include 'include.yaml.partial' %> 4 | -------------------------------------------------------------------------------- /tests/data/input2/empty.yaml.jinja: -------------------------------------------------------------------------------- 1 | <# This file is here to test the --skip-empty feature #> 2 | -------------------------------------------------------------------------------- /tests/data/input2/include.yaml.partial: -------------------------------------------------------------------------------- 1 | icon: mdi:home 2 | -------------------------------------------------------------------------------- /tests/data/input2/not-empty.yaml.jinja: -------------------------------------------------------------------------------- 1 | <# This file is overridden in input_2 to test jinja file overrides #> 2 | -------------------------------------------------------------------------------- /tests/data/input2/ui-lovelace.yaml: -------------------------------------------------------------------------------- 1 | title: Overview 2 | views: !include_dir_merge_list views 3 | -------------------------------------------------------------------------------- /tests/data/input2/views/home.yaml.jinja: -------------------------------------------------------------------------------- 1 | <% set lang = "en" %> 2 | <% for area_id, area in areas.items() %> 3 | - title: << getlang(area.name, lang) >> 4 | path: << area_id | hassurl >> 5 | icon: << area.icon >> 6 | cards: 7 | - type: grid 8 | square: false 9 | columns: 2 10 | cards: 11 | <% for item in area.entities.light %> 12 | - type: tile 13 | entity: << item.entity >> 14 | name: << getlang(item.name, lang) >> 15 | features: 16 | - type: light-brightness 17 | <% endfor %> 18 | <% for item in area.entities.cover %> 19 | - type: tile 20 | entity: << item.entity >> 21 | name: << getlang(item.name, lang) >> 22 | features: 23 | - type: cover-open-close 24 | <% endfor %> 25 | <% for item in area.entities.climate %> 26 | - type: tile 27 | entity: << item.entity >> 28 | name: << getlang(item.name, lang) >> 29 | <% endfor %> 30 | <% for item in area.entities.switch %> 31 | - type: tile 32 | entity: << item.entity >> 33 | name: << getlang(item.name, lang) >> 34 | <% endfor %> 35 | <% for item in area.entities.sensor %> 36 | - type: tile 37 | entity: << item.entity >> 38 | name: << getlang(item.name, lang) >> 39 | <% endfor %> 40 | <% for item in area.entities.binary_sensor %> 41 | - type: tile 42 | entity: << item.entity >> 43 | name: << getlang(item.name, lang) >> 44 | <% endfor %> 45 | <% for item in area.entities.media_player %> 46 | - type: tile 47 | entity: << item.entity >> 48 | name: << getlang(item.name, lang) >> 49 | tap_action: 50 | action: more-info 51 | <% endfor %> 52 | <% for item in area.entities.climate %> 53 | - type: history-graph 54 | title: << getlang(item.name, lang) >> history 55 | show_names: false 56 | entities: 57 | - << item.entity >> 58 | <% endfor %> 59 | <% endfor %> 60 | -------------------------------------------------------------------------------- /tests/data/makejinja.toml: -------------------------------------------------------------------------------- 1 | [makejinja] 2 | inputs = ["./input1", "./input2"] 3 | output = "./output" 4 | data = ["./config"] 5 | plugins = ["plugin:Plugin"] 6 | exclude_patterns = ["*.partial"] 7 | data_vars = { "areas.kitchen.name.en" = "Cuisine" } 8 | 9 | 10 | [makejinja.delimiter] 11 | block_start = "<%" 12 | block_end = "%>" 13 | comment_start = "<#" 14 | comment_end = "#>" 15 | variable_start = "<<" 16 | variable_end = ">>" 17 | -------------------------------------------------------------------------------- /tests/data/output/extra-file.yaml: -------------------------------------------------------------------------------- 1 | title: Overview 2 | views: !include_dir_merge_list views 3 | <% include 'include.yaml.partial' %> 4 | -------------------------------------------------------------------------------- /tests/data/output/not-empty.yaml: -------------------------------------------------------------------------------- 1 | # This file is NOT EMPTY 2 | -------------------------------------------------------------------------------- /tests/data/output/ui-lovelace.yaml: -------------------------------------------------------------------------------- 1 | title: Overview 2 | views: !include_dir_merge_list views 3 | icon: mdi:home 4 | -------------------------------------------------------------------------------- /tests/data/output/views/home.yaml: -------------------------------------------------------------------------------- 1 | - title: Living room 2 | path: living-room 3 | icon: mdi:sofa 4 | cards: 5 | - type: grid 6 | square: false 7 | columns: 2 8 | cards: 9 | - type: tile 10 | entity: light.living_room_ceiling 11 | name: Ceiling 12 | features: 13 | - type: light-brightness 14 | - type: tile 15 | entity: light.living_room_spots 16 | name: Spots 17 | features: 18 | - type: light-brightness 19 | - type: tile 20 | entity: cover.living_room_door 21 | name: Cover door 22 | features: 23 | - type: cover-open-close 24 | - type: tile 25 | entity: cover.living_room_window 26 | name: Cover window 27 | features: 28 | - type: cover-open-close 29 | - type: tile 30 | entity: climate.living_room 31 | name: Thermostat 32 | - type: tile 33 | entity: binary_sensor.living_room_door_contact 34 | name: Door 35 | - type: tile 36 | entity: binary_sensor.living_room_window_contact 37 | name: Window 38 | - type: tile 39 | entity: media_player.living_room_homepod 40 | name: HomePod 41 | tap_action: 42 | action: more-info 43 | - type: tile 44 | entity: media_player.living_room_apple_tv 45 | name: Apple TV 46 | tap_action: 47 | action: more-info 48 | - type: history-graph 49 | title: Thermostat history 50 | show_names: false 51 | entities: 52 | - climate.living_room 53 | - title: Cuisine 54 | path: kitchen 55 | icon: mdi:chef-hat 56 | cards: 57 | - type: grid 58 | square: false 59 | columns: 2 60 | cards: 61 | - type: tile 62 | entity: light.kitchen_spots 63 | name: Spots 64 | features: 65 | - type: light-brightness 66 | - type: tile 67 | entity: climate.kitchen 68 | name: Thermostat 69 | - type: tile 70 | entity: cover.kitchen_window 71 | name: Cover window 72 | - type: tile 73 | entity: binary_sensor.kitchen_window_contact 74 | name: Window 75 | - type: tile 76 | entity: media_player.kitchen_homepod 77 | name: HomePod 78 | tap_action: 79 | action: more-info 80 | - type: history-graph 81 | title: Thermostat history 82 | show_names: false 83 | entities: 84 | - climate.kitchen 85 | - type: history-graph 86 | title: Cover window history 87 | show_names: false 88 | entities: 89 | - cover.kitchen_window 90 | -------------------------------------------------------------------------------- /tests/data/plugin.py: -------------------------------------------------------------------------------- 1 | from collections import abc 2 | from pathlib import Path 3 | from urllib.parse import quote 4 | 5 | import makejinja 6 | 7 | 8 | def hassurl(value: str) -> str: 9 | return quote(value).replace("_", "-") 10 | 11 | 12 | def getlang(value: str | abc.Mapping[str, str], lang: str, default_lang: str = "en"): 13 | if isinstance(value, str): 14 | return value 15 | else: 16 | return value.get(lang, value.get(default_lang, "")) 17 | 18 | 19 | class Plugin(makejinja.plugin.Plugin): 20 | def filters(self) -> makejinja.plugin.Filters: 21 | return [hassurl] 22 | 23 | def functions(self) -> makejinja.plugin.Functions: 24 | return [getlang] 25 | 26 | def path_filters(self) -> makejinja.plugin.PathFilters: 27 | return [self._remove_secrets] 28 | 29 | def _remove_secrets(self, path: Path) -> bool: 30 | if "secret" in path.stem: 31 | return False 32 | 33 | return True 34 | -------------------------------------------------------------------------------- /tests/test_makejinja.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from pathlib import Path 3 | 4 | import pytest 5 | from click.testing import CliRunner 6 | 7 | 8 | @dataclass 9 | class Paths: 10 | input: Path 11 | baseline: Path 12 | output: Path 13 | 14 | def __repr__(self) -> str: 15 | return "makejinja" 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def exec(tmp_path_factory: pytest.TempPathFactory) -> Paths: 20 | assert __package__ is not None 21 | data_path = Path(__package__, "data") 22 | input_path = data_path / "input" 23 | baseline_path = data_path / "output" 24 | output_path = tmp_path_factory.mktemp("data") 25 | 26 | with pytest.MonkeyPatch.context() as m: 27 | m.chdir(data_path) 28 | runner = CliRunner() 29 | 30 | # Need to import it AFTER chdir 31 | from makejinja.cli import makejinja_cli 32 | 33 | runner.invoke( 34 | makejinja_cli, 35 | [ 36 | # Override it here to use our tmp_path 37 | "--output", 38 | str(output_path), 39 | ], 40 | catch_exceptions=False, 41 | color=True, 42 | ) 43 | 44 | return Paths(input_path, baseline_path, output_path) 45 | 46 | 47 | def _dir_content(path: Path) -> set[Path]: 48 | return {item.relative_to(path) for item in path.rglob("*")} 49 | 50 | 51 | def test_dir_content(exec: Paths): 52 | assert _dir_content(exec.baseline) == _dir_content(exec.output) 53 | 54 | 55 | def test_file_content(exec: Paths): 56 | paths = _dir_content(exec.baseline) 57 | 58 | for item in paths: 59 | baseline_path = exec.baseline / item 60 | output_path = exec.output / item 61 | 62 | if baseline_path.is_file() and output_path.is_file(): 63 | baseline = baseline_path.read_text() 64 | output = output_path.read_text() 65 | 66 | assert baseline.strip() == output.strip(), str(item) 67 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.11" 4 | 5 | [[package]] 6 | name = "attrs" 7 | version = "25.3.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, 12 | ] 13 | 14 | [[package]] 15 | name = "cattrs" 16 | version = "24.1.3" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "attrs" }, 20 | ] 21 | sdist = { url = "https://files.pythonhosted.org/packages/29/7b/da4aa2f95afb2f28010453d03d6eedf018f9e085bd001f039e15731aba89/cattrs-24.1.3.tar.gz", hash = "sha256:981a6ef05875b5bb0c7fb68885546186d306f10f0f6718fe9b96c226e68821ff", size = 426684, upload-time = "2025-03-25T15:01:00.325Z" } 22 | wheels = [ 23 | { url = "https://files.pythonhosted.org/packages/3c/ee/d68a3de23867a9156bab7e0a22fb9a0305067ee639032a22982cf7f725e7/cattrs-24.1.3-py3-none-any.whl", hash = "sha256:adf957dddd26840f27ffbd060a6c4dd3b2192c5b7c2c0525ef1bd8131d8a83f5", size = 66462, upload-time = "2025-03-25T15:00:58.663Z" }, 24 | ] 25 | 26 | [[package]] 27 | name = "click" 28 | version = "8.2.1" 29 | source = { registry = "https://pypi.org/simple" } 30 | dependencies = [ 31 | { name = "colorama", marker = "sys_platform == 'win32'" }, 32 | ] 33 | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } 34 | wheels = [ 35 | { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, 36 | ] 37 | 38 | [[package]] 39 | name = "colorama" 40 | version = "0.4.6" 41 | source = { registry = "https://pypi.org/simple" } 42 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 43 | wheels = [ 44 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 45 | ] 46 | 47 | [[package]] 48 | name = "coverage" 49 | version = "7.8.2" 50 | source = { registry = "https://pypi.org/simple" } 51 | sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } 52 | wheels = [ 53 | { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, 54 | { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, 55 | { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, 56 | { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, 57 | { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, 58 | { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, 59 | { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, 60 | { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, 61 | { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, 62 | { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, 63 | { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, 64 | { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, 65 | { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, 66 | { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, 67 | { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" }, 68 | { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" }, 69 | { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" }, 70 | { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" }, 71 | { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" }, 72 | { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, 73 | { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, 74 | { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, 75 | { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" }, 76 | { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" }, 77 | { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" }, 78 | { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" }, 79 | { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" }, 80 | { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" }, 81 | { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" }, 82 | { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" }, 83 | { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" }, 84 | { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" }, 85 | { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" }, 86 | { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" }, 87 | { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" }, 88 | { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" }, 89 | { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" }, 90 | { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" }, 91 | { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" }, 92 | { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" }, 93 | { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" }, 94 | { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" }, 95 | { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" }, 96 | { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" }, 97 | { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, 98 | { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, 99 | ] 100 | 101 | [package.optional-dependencies] 102 | toml = [ 103 | { name = "tomli", marker = "python_full_version <= '3.11'" }, 104 | ] 105 | 106 | [[package]] 107 | name = "immutables" 108 | version = "0.21" 109 | source = { registry = "https://pypi.org/simple" } 110 | sdist = { url = "https://files.pythonhosted.org/packages/69/41/0ccaa6ef9943c0609ec5aa663a3b3e681c1712c1007147b84590cec706a0/immutables-0.21.tar.gz", hash = "sha256:b55ffaf0449790242feb4c56ab799ea7af92801a0a43f9e2f4f8af2ab24dfc4a", size = 89008, upload-time = "2024-10-10T00:55:01.434Z" } 111 | wheels = [ 112 | { url = "https://files.pythonhosted.org/packages/85/39/2d7d54f6cf33f6fcb3f78e985142166d2ec6ff93be761a17dfee31a372cd/immutables-0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5d780c38067047911a2e06a86ba063ba0055618ab5573c8198ef3f368e321303", size = 31227, upload-time = "2024-10-10T00:54:09.884Z" }, 113 | { url = "https://files.pythonhosted.org/packages/ca/4b/260f632d0fb83b8cdabb0c1fc14657a9bdead456f3399c4eb5a0c0697989/immutables-0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9aab9d0f0016f6e0bfe7e4a4cb831ef20063da6468b1bbc71d06ef285781ee9e", size = 31081, upload-time = "2024-10-10T00:54:11.325Z" }, 114 | { url = "https://files.pythonhosted.org/packages/d2/8d/0f4a1d10fac2700874680dad4d1d7b0d0bf31225cb21f3a804d085bfb603/immutables-0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ff83390b05d3372acb9a0c928f6cc20c78e74ca20ed88eb941f84a63b65e444", size = 99334, upload-time = "2024-10-10T00:54:12.229Z" }, 115 | { url = "https://files.pythonhosted.org/packages/ca/fc/20f4a5d5fcd4d17b46027d7d542190f5084f3d74abc6bdc2c0fab4d3deb3/immutables-0.21-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01497713e71509c4481ffccdbe3a47b94969345f4e92f814d6626f7c0a4c304", size = 99511, upload-time = "2024-10-10T00:54:13.202Z" }, 116 | { url = "https://files.pythonhosted.org/packages/7e/52/f929eaee4b247b1b71823d784734e51761685d6e69d7ad000459c3f2b84d/immutables-0.21-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc7844c9fbb5bece5bfdf2bf8ea74d308f42f40b0665fd25c58abf56d7db024a", size = 97474, upload-time = "2024-10-10T00:54:14.961Z" }, 117 | { url = "https://files.pythonhosted.org/packages/85/b1/690aeaa4acc1db13e8356888bc929c8d0c9363c1409ffd8ab74d65d8c49c/immutables-0.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:984106fa4345efd9f96de22e9949fc97bac8598bdebee03c20b2497a88bff3b7", size = 97940, upload-time = "2024-10-10T00:54:15.957Z" }, 118 | { url = "https://files.pythonhosted.org/packages/dd/de/78bd53b69ee99e034c2b69f46063abf600f81417f28de6120d430f523c98/immutables-0.21-cp311-cp311-win32.whl", hash = "sha256:1bdb5200518518601377e4877d5034e7c535e9ea8a9d601ed8b0eedef0c7becd", size = 30503, upload-time = "2024-10-10T00:54:16.891Z" }, 119 | { url = "https://files.pythonhosted.org/packages/1f/20/999ecf398412c9ad174da083c6ffea56d027ef62cf892f695b43210c4721/immutables-0.21-cp311-cp311-win_amd64.whl", hash = "sha256:dd00c34f431c54c95e7b84bfdbdeacb4f039a6a24eb0c1f7aa4b168bb9a6ad0a", size = 34395, upload-time = "2024-10-10T00:54:18.481Z" }, 120 | { url = "https://files.pythonhosted.org/packages/4d/f9/0c46f600702b815182212453f5514c0070ee168b817cdf7c3767554c8489/immutables-0.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1ed262094b755903122c3c3a83ad0e0d5c3ab7887cda12b2fe878769d1ee0d", size = 31885, upload-time = "2024-10-10T00:54:19.406Z" }, 121 | { url = "https://files.pythonhosted.org/packages/29/34/7608d2eab6179aa47e8f59ab0fbd5b3eeb2333d78c9dc2da0de8de4ed322/immutables-0.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce604f81d9d8f26e60b52ebcb56bb5c0462c8ea50fb17868487d15f048a2f13e", size = 31537, upload-time = "2024-10-10T00:54:20.998Z" }, 122 | { url = "https://files.pythonhosted.org/packages/f7/52/cb9e2bb7a69338155ffabbd2f993c968c750dd2d5c6c6eaa6ebb7bfcbdfa/immutables-0.21-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b48b116aaca4500398058b5a87814857a60c4cb09417fecc12d7da0f5639b73d", size = 104270, upload-time = "2024-10-10T00:54:21.912Z" }, 123 | { url = "https://files.pythonhosted.org/packages/0f/a4/25df835a9b9b372a4a869a8a1ac30a32199f2b3f581ad0e249f7e3d19eed/immutables-0.21-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad7c0c74b285cc0e555ec0e97acbdc6f1862fcd16b99abd612df3243732e741", size = 104864, upload-time = "2024-10-10T00:54:22.956Z" }, 124 | { url = "https://files.pythonhosted.org/packages/4a/51/b548fbc657134d658e179ee8d201ae82d9049aba5c3cb2d858ed2ecb7e3f/immutables-0.21-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e44346e2221a5a676c880ca8e0e6429fa24d1a4ae562573f5c04d7f2e759b030", size = 99733, upload-time = "2024-10-10T00:54:23.99Z" }, 125 | { url = "https://files.pythonhosted.org/packages/47/db/d7b1e0e88faf07fe9a88579a86f58078a9a37fff871f4b3dbcf28cad9a12/immutables-0.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b10139b529a460e53fe8be699ebd848c54c8a33ebe67763bcfcc809a475a26f", size = 101698, upload-time = "2024-10-10T00:54:25.734Z" }, 126 | { url = "https://files.pythonhosted.org/packages/69/2d/6fe42a1a053dd8cfb9f45e91d5246522637c7287dc6bd347f67aedf7aedb/immutables-0.21-cp312-cp312-win32.whl", hash = "sha256:fc512d808662614feb17d2d92e98f611d69669a98c7af15910acf1dc72737038", size = 30977, upload-time = "2024-10-10T00:54:27.436Z" }, 127 | { url = "https://files.pythonhosted.org/packages/63/45/d062aca6971e99454ce3ae42a7430037227fee961644ed1f8b6c9b99e0a5/immutables-0.21-cp312-cp312-win_amd64.whl", hash = "sha256:461dcb0f58a131045155e52a2c43de6ec2fe5ba19bdced6858a3abb63cee5111", size = 35088, upload-time = "2024-10-10T00:54:28.388Z" }, 128 | { url = "https://files.pythonhosted.org/packages/5e/db/60da6f5a3c3f64e0b3940c4ad86e1971d9d2eb8b13a179c26eda5ec6a298/immutables-0.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:79674b51aa8dd983f9ac55f7f67b433b1df84a6b4f28ab860588389a5659485b", size = 31922, upload-time = "2024-10-10T00:54:29.305Z" }, 129 | { url = "https://files.pythonhosted.org/packages/9b/89/5420f1d16a652024fcccc9c07d46d4157fcaf33ff37c82412c83fc16ef36/immutables-0.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93c8350f8f7d0d9693f708229d9d0578e6f3b785ce6da4bced1da97137aacfad", size = 31552, upload-time = "2024-10-10T00:54:30.282Z" }, 130 | { url = "https://files.pythonhosted.org/packages/d2/d0/a5fb7c164ddb298ec37537e618b70dfa30c7cae9fac01de374c36489cbc9/immutables-0.21-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:583d2a63e444ce1538cc2bda56ae1f4a1a11473dbc0377c82b516bc7eec3b81e", size = 104334, upload-time = "2024-10-10T00:54:31.284Z" }, 131 | { url = "https://files.pythonhosted.org/packages/f3/a5/5fda0ee4a261a85124011ac0750fec678f00e1b2d4a5502b149a3b4d86d9/immutables-0.21-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b274a52da9b106db55eceb93fc1aea858c4e6f4740189e3548e38613eafc2021", size = 104898, upload-time = "2024-10-10T00:54:32.295Z" }, 132 | { url = "https://files.pythonhosted.org/packages/93/fa/d46bfe92f2c66d35916344176ff87fa839aac9c16849652947e722b7a15f/immutables-0.21-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:338bede057250b33716a3e4892e15df0bf5a5ddbf1d67ead996b3e680b49ef9e", size = 99966, upload-time = "2024-10-10T00:54:34.046Z" }, 133 | { url = "https://files.pythonhosted.org/packages/d7/f5/2a19e2e095f7a39d8d77dcc10669734d2d99773ce00c99bdcfeeb7d714e6/immutables-0.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8781c89583b68f604cf30f0978b722165824c3075888639fde771bf1a3e12dc0", size = 101773, upload-time = "2024-10-10T00:54:35.851Z" }, 134 | { url = "https://files.pythonhosted.org/packages/86/80/5b6ee53f836cf2067ced997efbf2ce20890627f150c3089ea50cf607e783/immutables-0.21-cp313-cp313-win32.whl", hash = "sha256:e97ea83befad873712f283c0cccd630f70cba753e207b4868af28d5b85e9dc54", size = 30988, upload-time = "2024-10-10T00:54:37.618Z" }, 135 | { url = "https://files.pythonhosted.org/packages/ff/07/f623e6da78368fc0b1772f4877afbf60f34c4cc93f1a8f1006507afa21ec/immutables-0.21-cp313-cp313-win_amd64.whl", hash = "sha256:cfcb23bd898f5a4ef88692b42c51f52ca7373a35ba4dcc215060a668639eb5da", size = 35147, upload-time = "2024-10-10T00:54:38.558Z" }, 136 | ] 137 | 138 | [[package]] 139 | name = "iniconfig" 140 | version = "2.1.0" 141 | source = { registry = "https://pypi.org/simple" } 142 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 143 | wheels = [ 144 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 145 | ] 146 | 147 | [[package]] 148 | name = "jinja2" 149 | version = "3.1.6" 150 | source = { registry = "https://pypi.org/simple" } 151 | dependencies = [ 152 | { name = "markupsafe" }, 153 | ] 154 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } 155 | wheels = [ 156 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, 157 | ] 158 | 159 | [[package]] 160 | name = "makejinja" 161 | version = "2.7.2" 162 | source = { editable = "." } 163 | dependencies = [ 164 | { name = "immutables" }, 165 | { name = "jinja2" }, 166 | { name = "pyyaml" }, 167 | { name = "rich-click" }, 168 | { name = "typed-settings", extra = ["attrs", "cattrs", "click"] }, 169 | ] 170 | 171 | [package.dev-dependencies] 172 | docs = [ 173 | { name = "pdoc" }, 174 | ] 175 | test = [ 176 | { name = "pytest" }, 177 | { name = "pytest-cov" }, 178 | ] 179 | 180 | [package.metadata] 181 | requires-dist = [ 182 | { name = "immutables", specifier = ">=0.20,<1" }, 183 | { name = "jinja2", specifier = ">=3,<4" }, 184 | { name = "pyyaml", specifier = ">=6,<7" }, 185 | { name = "rich-click", specifier = ">=1,<2" }, 186 | { name = "typed-settings", extras = ["attrs", "cattrs", "click"], specifier = ">=23,<25" }, 187 | ] 188 | 189 | [package.metadata.requires-dev] 190 | docs = [{ name = "pdoc", specifier = ">=15,<16" }] 191 | test = [ 192 | { name = "pytest", specifier = ">=8,<9" }, 193 | { name = "pytest-cov", specifier = ">=6,<7" }, 194 | ] 195 | 196 | [[package]] 197 | name = "markdown-it-py" 198 | version = "3.0.0" 199 | source = { registry = "https://pypi.org/simple" } 200 | dependencies = [ 201 | { name = "mdurl" }, 202 | ] 203 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 204 | wheels = [ 205 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 206 | ] 207 | 208 | [[package]] 209 | name = "markupsafe" 210 | version = "3.0.2" 211 | source = { registry = "https://pypi.org/simple" } 212 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } 213 | wheels = [ 214 | { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, 215 | { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, 216 | { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, 217 | { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, 218 | { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, 219 | { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, 220 | { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, 221 | { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, 222 | { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, 223 | { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, 224 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, 225 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, 226 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, 227 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, 228 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, 229 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, 230 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, 231 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, 232 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, 233 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, 234 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, 235 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, 236 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, 237 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, 238 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, 239 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, 240 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, 241 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, 242 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, 243 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, 244 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, 245 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, 246 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, 247 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, 248 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, 249 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, 250 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, 251 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, 252 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, 253 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, 254 | ] 255 | 256 | [[package]] 257 | name = "mdurl" 258 | version = "0.1.2" 259 | source = { registry = "https://pypi.org/simple" } 260 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 261 | wheels = [ 262 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 263 | ] 264 | 265 | [[package]] 266 | name = "packaging" 267 | version = "25.0" 268 | source = { registry = "https://pypi.org/simple" } 269 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 270 | wheels = [ 271 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 272 | ] 273 | 274 | [[package]] 275 | name = "pdoc" 276 | version = "15.0.3" 277 | source = { registry = "https://pypi.org/simple" } 278 | dependencies = [ 279 | { name = "jinja2" }, 280 | { name = "markupsafe" }, 281 | { name = "pygments" }, 282 | ] 283 | sdist = { url = "https://files.pythonhosted.org/packages/9f/e9/66ab0fc39276a1818dea6302858ec9558964d8d9f1c90dd1facfe395d216/pdoc-15.0.3.tar.gz", hash = "sha256:6482d8ebbd40185fea5e6aec2f1592f4be92e93cf6bf70b9e2a00378bbaf3252", size = 155384, upload-time = "2025-04-21T12:55:47.761Z" } 284 | wheels = [ 285 | { url = "https://files.pythonhosted.org/packages/dc/37/bc3189471c63c84e15f7dc42d4b712747e9662ffbcfacfc4b6a93e6c3bc6/pdoc-15.0.3-py3-none-any.whl", hash = "sha256:686c921ef2622f166de5f73b7241935a4ddac79c8d10dbfa43def8c1fca86550", size = 145950, upload-time = "2025-04-21T12:55:46.178Z" }, 286 | ] 287 | 288 | [[package]] 289 | name = "pluggy" 290 | version = "1.6.0" 291 | source = { registry = "https://pypi.org/simple" } 292 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } 293 | wheels = [ 294 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, 295 | ] 296 | 297 | [[package]] 298 | name = "pygments" 299 | version = "2.19.1" 300 | source = { registry = "https://pypi.org/simple" } 301 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 302 | wheels = [ 303 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 304 | ] 305 | 306 | [[package]] 307 | name = "pytest" 308 | version = "8.3.5" 309 | source = { registry = "https://pypi.org/simple" } 310 | dependencies = [ 311 | { name = "colorama", marker = "sys_platform == 'win32'" }, 312 | { name = "iniconfig" }, 313 | { name = "packaging" }, 314 | { name = "pluggy" }, 315 | ] 316 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } 317 | wheels = [ 318 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, 319 | ] 320 | 321 | [[package]] 322 | name = "pytest-cov" 323 | version = "6.1.1" 324 | source = { registry = "https://pypi.org/simple" } 325 | dependencies = [ 326 | { name = "coverage", extra = ["toml"] }, 327 | { name = "pytest" }, 328 | ] 329 | sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } 330 | wheels = [ 331 | { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, 332 | ] 333 | 334 | [[package]] 335 | name = "pyyaml" 336 | version = "6.0.2" 337 | source = { registry = "https://pypi.org/simple" } 338 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } 339 | wheels = [ 340 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, 341 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, 342 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, 343 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, 344 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, 345 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, 346 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, 347 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, 348 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, 349 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, 350 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, 351 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, 352 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, 353 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, 354 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, 355 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, 356 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, 357 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, 358 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, 359 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, 360 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, 361 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, 362 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, 363 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, 364 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, 365 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, 366 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, 367 | ] 368 | 369 | [[package]] 370 | name = "rich" 371 | version = "14.0.0" 372 | source = { registry = "https://pypi.org/simple" } 373 | dependencies = [ 374 | { name = "markdown-it-py" }, 375 | { name = "pygments" }, 376 | ] 377 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 378 | wheels = [ 379 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 380 | ] 381 | 382 | [[package]] 383 | name = "rich-click" 384 | version = "1.8.9" 385 | source = { registry = "https://pypi.org/simple" } 386 | dependencies = [ 387 | { name = "click" }, 388 | { name = "rich" }, 389 | { name = "typing-extensions" }, 390 | ] 391 | sdist = { url = "https://files.pythonhosted.org/packages/b7/a8/dcc0a8ec9e91d76ecad9413a84b6d3a3310c6111cfe012d75ed385c78d96/rich_click-1.8.9.tar.gz", hash = "sha256:fd98c0ab9ddc1cf9c0b7463f68daf28b4d0033a74214ceb02f761b3ff2af3136", size = 39378, upload-time = "2025-05-19T21:33:05.569Z" } 392 | wheels = [ 393 | { url = "https://files.pythonhosted.org/packages/b6/c2/9fce4c8a9587c4e90500114d742fe8ef0fd92d7bad29d136bb9941add271/rich_click-1.8.9-py3-none-any.whl", hash = "sha256:c3fa81ed8a671a10de65a9e20abf642cfdac6fdb882db1ef465ee33919fbcfe2", size = 36082, upload-time = "2025-05-19T21:33:04.195Z" }, 394 | ] 395 | 396 | [[package]] 397 | name = "tomli" 398 | version = "2.2.1" 399 | source = { registry = "https://pypi.org/simple" } 400 | sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } 401 | wheels = [ 402 | { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, 403 | { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, 404 | { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, 405 | { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, 406 | { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, 407 | { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, 408 | { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, 409 | { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, 410 | { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, 411 | { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, 412 | { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, 413 | { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, 414 | { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, 415 | { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, 416 | { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, 417 | { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, 418 | { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, 419 | { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, 420 | { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, 421 | { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, 422 | { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, 423 | { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, 424 | { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, 425 | { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, 426 | { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, 427 | { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, 428 | { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, 429 | { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, 430 | { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, 431 | { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, 432 | { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, 433 | ] 434 | 435 | [[package]] 436 | name = "typed-settings" 437 | version = "24.6.0" 438 | source = { registry = "https://pypi.org/simple" } 439 | sdist = { url = "https://files.pythonhosted.org/packages/c0/91/f39cff1a6eb43ba5d8e7eb810405b0a7abed7b7033f7725a09796c625547/typed_settings-24.6.0.tar.gz", hash = "sha256:9a5595de33f80452a2038e018bc45508fffc238a8752f08f5c03dc8e7bc0d1e2", size = 29469194, upload-time = "2024-11-07T09:46:28.562Z" } 440 | wheels = [ 441 | { url = "https://files.pythonhosted.org/packages/d6/65/659c1bebd863edc9184715fd1cf2475926cead71d83535d980577bfa2439/typed_settings-24.6.0-py3-none-any.whl", hash = "sha256:22221598a821395700beaafd8eab0e1bc596b394767aa37e0926c5f74d0b6a62", size = 58394, upload-time = "2024-11-07T09:46:25.079Z" }, 442 | ] 443 | 444 | [package.optional-dependencies] 445 | attrs = [ 446 | { name = "attrs" }, 447 | ] 448 | cattrs = [ 449 | { name = "cattrs" }, 450 | ] 451 | click = [ 452 | { name = "click" }, 453 | ] 454 | 455 | [[package]] 456 | name = "typing-extensions" 457 | version = "4.13.2" 458 | source = { registry = "https://pypi.org/simple" } 459 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } 460 | wheels = [ 461 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, 462 | ] 463 | --------------------------------------------------------------------------------