├── .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 |
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 |
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 | 
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 |
--------------------------------------------------------------------------------