batstencil 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batstencil-1.0.0/LICENSE +22 -0
- batstencil-1.0.0/PKG-INFO +406 -0
- batstencil-1.0.0/README.md +367 -0
- batstencil-1.0.0/batstencil/__init__.py +5 -0
- batstencil-1.0.0/batstencil/__main__.py +4 -0
- batstencil-1.0.0/batstencil/cli/__init__.py +0 -0
- batstencil-1.0.0/batstencil/cli/bat_stencil/__init__.py +0 -0
- batstencil-1.0.0/batstencil/cli/bat_stencil/_runner.py +88 -0
- batstencil-1.0.0/batstencil/cli/bat_stencil/main.py +80 -0
- batstencil-1.0.0/batstencil/context/__init__.py +4 -0
- batstencil-1.0.0/batstencil/context/_allconf.py +40 -0
- batstencil-1.0.0/batstencil/context/_dict.py +25 -0
- batstencil-1.0.0/batstencil/context/_kwarg.py +10 -0
- batstencil-1.0.0/batstencil/jinjaext/__init__.py +0 -0
- batstencil-1.0.0/batstencil/jinjaext/extensions/__init__.py +4 -0
- batstencil-1.0.0/batstencil/jinjaext/extensions/_embed.py +54 -0
- batstencil-1.0.0/batstencil/jinjaext/extensions/_repeat_for.py +44 -0
- batstencil-1.0.0/batstencil/jinjaext/extensions/_setfilename.py +38 -0
- batstencil-1.0.0/batstencil/jinjaext/extensions/_skip_if.py +24 -0
- batstencil-1.0.0/batstencil/jinjaext/filters/__init__.py +1 -0
- batstencil-1.0.0/batstencil/jinjaext/filters/_base64.py +13 -0
- batstencil-1.0.0/batstencil/renderer/__init__.py +6 -0
- batstencil-1.0.0/batstencil/renderer/_base.py +283 -0
- batstencil-1.0.0/batstencil/renderer/_dir.py +253 -0
- batstencil-1.0.0/batstencil/renderer/_file.py +72 -0
- batstencil-1.0.0/batstencil/renderer/_stdout.py +14 -0
- batstencil-1.0.0/batstencil/renderer/_string.py +22 -0
- batstencil-1.0.0/batstencil/shortcuts/__init__.py +1 -0
- batstencil-1.0.0/batstencil/shortcuts/_render_stencil.py +19 -0
- batstencil-1.0.0/batstencil/stencils.py +6 -0
- batstencil-1.0.0/batstencil/structs/__init__.py +4 -0
- batstencil-1.0.0/batstencil/structs/_aliases.py +7 -0
- batstencil-1.0.0/batstencil/structs/_base.py +2 -0
- batstencil-1.0.0/batstencil/structs/_errors.py +95 -0
- batstencil-1.0.0/batstencil/structs/interfaces/__init__.py +3 -0
- batstencil-1.0.0/batstencil/structs/interfaces/_context.py +24 -0
- batstencil-1.0.0/batstencil/structs/interfaces/_renderer.py +61 -0
- batstencil-1.0.0/batstencil/structs/interfaces/_template.py +24 -0
- batstencil-1.0.0/batstencil/template/__init__.py +5 -0
- batstencil-1.0.0/batstencil/template/_base.py +16 -0
- batstencil-1.0.0/batstencil/template/_dir.py +122 -0
- batstencil-1.0.0/batstencil/template/_file.py +53 -0
- batstencil-1.0.0/batstencil/template/_string.py +16 -0
- batstencil-1.0.0/batstencil/utils/__init__.py +1 -0
- batstencil-1.0.0/batstencil/utils/_guessers.py +63 -0
- batstencil-1.0.0/batstencil.egg-info/PKG-INFO +406 -0
- batstencil-1.0.0/batstencil.egg-info/SOURCES.txt +63 -0
- batstencil-1.0.0/batstencil.egg-info/dependency_links.txt +1 -0
- batstencil-1.0.0/batstencil.egg-info/entry_points.txt +2 -0
- batstencil-1.0.0/batstencil.egg-info/requires.txt +6 -0
- batstencil-1.0.0/batstencil.egg-info/top_level.txt +1 -0
- batstencil-1.0.0/pyproject.toml +67 -0
- batstencil-1.0.0/setup.cfg +4 -0
- batstencil-1.0.0/setup.py +6 -0
- batstencil-1.0.0/tests/test_base64_filter.py +42 -0
- batstencil-1.0.0/tests/test_cli.py +169 -0
- batstencil-1.0.0/tests/test_contexts.py +96 -0
- batstencil-1.0.0/tests/test_directory_renderer.py +551 -0
- batstencil-1.0.0/tests/test_embed.py +58 -0
- batstencil-1.0.0/tests/test_embed_more.py +105 -0
- batstencil-1.0.0/tests/test_file_renderer.py +187 -0
- batstencil-1.0.0/tests/test_filters.py +34 -0
- batstencil-1.0.0/tests/test_guessers.py +153 -0
- batstencil-1.0.0/tests/test_skip_if.py +45 -0
- batstencil-1.0.0/tests/test_some_basic_stuff.py +32 -0
batstencil-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CCP Games
|
|
4
|
+
Copyright (c) 2026 Thordur Matthiasson
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: batstencil
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An AllConf and Jinja2 powered template renderer where the context input can be files and entire directory structures can be rendered (a continuation fork of CCP Games' ccp-stencil).
|
|
5
|
+
Author-email: Thordur Matthiasson <doddi79@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/doddi79/bat-stencil
|
|
8
|
+
Project-URL: Documentation, https://github.com/doddi79/bat-stencil/blob/main/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/doddi79/bat-stencil.git
|
|
10
|
+
Project-URL: Issues, https://github.com/doddi79/bat-stencil/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/doddi79/bat-stencil/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: allconf,template,render,jinja2,scaffolding,bootstrap,tools,utils
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Software Development
|
|
25
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: batutils<3,>=2.2
|
|
34
|
+
Requires-Dist: allconf<5,>=4.0.2
|
|
35
|
+
Requires-Dist: jinja2<4,>=3.1
|
|
36
|
+
Provides-Extra: fidelius
|
|
37
|
+
Requires-Dist: allconf[fidelius]<5,>=4.0.2; extra == "fidelius"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# BatStencil
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/batstencil/)
|
|
43
|
+
|
|
44
|
+
An [AllConf](https://github.com/doddi79/allconf) and Jinja2 powered template
|
|
45
|
+
renderer where the context can come from config files and entire directory
|
|
46
|
+
trees can be rendered.
|
|
47
|
+
|
|
48
|
+
- Context from AllConf files (YAML/JSON with includes, `${__ENV__:...}`,
|
|
49
|
+
`${__PY__:...}` etc.), plain dicts, keyword arguments or command line
|
|
50
|
+
arguments
|
|
51
|
+
- Render a string, a file or a whole directory tree of templates
|
|
52
|
+
- Extra Jinja2 tags for embedding files, skipping output, naming output files
|
|
53
|
+
and repeating files/directories once per item in a list
|
|
54
|
+
- Use it from the command line (`bat-stencil`) or from Python
|
|
55
|
+
|
|
56
|
+
The main use cases are project scaffolding (render a "template project" into a
|
|
57
|
+
new project) and rendering CI/CD manifests (Kubernetes, pipelines etc.) on
|
|
58
|
+
demand.
|
|
59
|
+
|
|
60
|
+
See the **[Public API index](docs/api-index.md)** for a task-oriented reference
|
|
61
|
+
of everything batstencil offers, and the [CHANGELOG](CHANGELOG.md) for what
|
|
62
|
+
changed between versions.
|
|
63
|
+
|
|
64
|
+
## Note
|
|
65
|
+
|
|
66
|
+
This is a fork of [`ccp-stencil`](https://github.com/ccpgames/ccp-stencil)
|
|
67
|
+
(PyPI: `ccpstencil` 0.8.0) by CCP Games, made in order to continue its
|
|
68
|
+
development and maintenance. The package _and_ module are now called
|
|
69
|
+
`batstencil` (starting at version 1.0.0) so it can live side-by-side with the
|
|
70
|
+
original, and the command line tool is `bat-stencil`.
|
|
71
|
+
|
|
72
|
+
The dependencies moved along with it: `ccptools` became
|
|
73
|
+
[`batutils`](https://github.com/doddi79/batutils) and `alviss` became
|
|
74
|
+
[`allconf`](https://github.com/doddi79/allconf) (both also forks of CCP
|
|
75
|
+
packages). Names that carried the old name have deprecated aliases
|
|
76
|
+
(`AlvissContext`, `{% embed ..., alviss=True %}`).
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
Requires Python 3.10+.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install batstencil
|
|
84
|
+
pip install batstencil[fidelius] # optional, for secret lookup via AllConf/Fidelius
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Command line
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
bat-stencil [-i INPUT] [-a KEY=VALUE ...] (-t FILE | -s STRING | -d DIR) [-o OUTPUT]
|
|
91
|
+
[--no-overwrite] [--no-purge] [-v] [--version]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
(`python -m batstencil` works the same way.)
|
|
95
|
+
|
|
96
|
+
| Flag | Meaning |
|
|
97
|
+
|------|---------|
|
|
98
|
+
| `-i`, `--input` | AllConf file (YAML or JSON) to use as the context |
|
|
99
|
+
| `-a`, `--arg` | Extra context value as `key=value`, can be repeated. Dots nest: `-a app.name=foo` sets `app: {name: foo}`. Applied on top of `-i` |
|
|
100
|
+
| `-t`, `--template` | A template file to render |
|
|
101
|
+
| `-s`, `--string-template` | A template given directly on the command line |
|
|
102
|
+
| `-d`, `--directory-template` | A directory of templates to render (requires `-o`) |
|
|
103
|
+
| `-o`, `--output` | Where to write. A path ending in `/` is a directory (the file keeps the template's name, or the one from `{% setfilename %}`), otherwise it's the output file. Without `-o` results go to stdout. For `-d` it's the output directory |
|
|
104
|
+
| `--no-overwrite` | Fail instead of overwriting existing output files (also fails when purging an existing output directory) |
|
|
105
|
+
| `--no-purge` | Don't delete the output directory first when rendering a directory |
|
|
106
|
+
| `-v`, `--verbose` | DEBUG logging (and full tracebacks on errors) |
|
|
107
|
+
| `--version` | Print the version |
|
|
108
|
+
|
|
109
|
+
Exactly one of `-t`, `-s` and `-d` is required. On errors the command prints
|
|
110
|
+
the message to stderr and exits with code 3.
|
|
111
|
+
|
|
112
|
+
Given this `config.yaml`:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
app:
|
|
116
|
+
name: demo
|
|
117
|
+
replicas: 3
|
|
118
|
+
services: [api, worker]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Render a file** (to a directory, keeping the file name):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bat-stencil -i config.yaml -a app.name=shop -t deploy.yaml.j2 -o out/
|
|
125
|
+
# Wrote file: /.../out/deploy.yaml.j2
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Render a string to stdout:**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
bat-stencil -a who=world -a a.b=c -s 'Hello {{ who }} {{ a.b }}!'
|
|
132
|
+
# Hello world c!
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Render a directory** (see [Directory rendering](#directory-rendering)):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
bat-stencil -i config.yaml -d my_template_project -o generated/
|
|
139
|
+
# Wrote file: /.../generated/README.md
|
|
140
|
+
# ...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Python
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from batstencil.stencils import *
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `render_stencil`
|
|
150
|
+
|
|
151
|
+
The shortcut for the common case; it returns the rendered `str` (or `None` if
|
|
152
|
+
the template skipped itself with `{% skip_if %}`):
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
render_stencil('Hello {{ who }}!', {'who': 'world'}) # 'Hello world!'
|
|
156
|
+
render_stencil('deploy.yaml.j2', 'config.yaml') # file template, AllConf file context
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The **template** argument is guessed as follows:
|
|
160
|
+
|
|
161
|
+
- An `ITemplate` object is used as-is
|
|
162
|
+
- A `Path` is a file template
|
|
163
|
+
- A multi-line string is a string template
|
|
164
|
+
- A single-line string is a file template if it names a template that can be
|
|
165
|
+
loaded from the [search paths](#template-search-paths) or is a path to an
|
|
166
|
+
existing file, otherwise it's a string template
|
|
167
|
+
|
|
168
|
+
The **context** argument is guessed as follows:
|
|
169
|
+
|
|
170
|
+
- An `IContext` object is used as-is
|
|
171
|
+
- `None` is an empty context
|
|
172
|
+
- A `dict` (any `Mapping`) is a `DictContext`
|
|
173
|
+
- A `str` or `Path` is an `AllConfContext` (the path of a YAML/JSON file)
|
|
174
|
+
|
|
175
|
+
### Contexts, templates and renderers
|
|
176
|
+
|
|
177
|
+
For anything else (writing to files, rendering directories) combine the
|
|
178
|
+
classes explicitly:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from batstencil.stencils import *
|
|
182
|
+
|
|
183
|
+
renderer = FileRenderer('out/')
|
|
184
|
+
renderer.context = AllConfContext('config.yaml')
|
|
185
|
+
renderer.template = FileTemplate('deploy.yaml.j2')
|
|
186
|
+
path = renderer.render() # absolute path of the file written (None if skipped)
|
|
187
|
+
|
|
188
|
+
# Or all in the constructor
|
|
189
|
+
DirectoryRenderer('generated/',
|
|
190
|
+
context=DictContext({'app': {'name': 'shop'}, 'services': ['api']}),
|
|
191
|
+
template=DirectoryTemplate('my_template_project')).render()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Contexts | |
|
|
195
|
+
|----------|--|
|
|
196
|
+
| `DictContext(dict)` | A plain dict (`update()` and dotted `nested_update('a.b', v)` to modify) |
|
|
197
|
+
| `KeyWordArgumentContext(**kwargs)` | Same, from keyword arguments |
|
|
198
|
+
| `AllConfContext(file)` | Loaded with AllConf. `AlvissContext` is a deprecated alias |
|
|
199
|
+
|
|
200
|
+
| Templates | |
|
|
201
|
+
|-----------|--|
|
|
202
|
+
| `StringTemplate(str)` | A template in a string |
|
|
203
|
+
| `FileTemplate(path)` | A template file. A `str` is first looked up as a template name in the search paths, then as a path |
|
|
204
|
+
| `DirectoryTemplate(dir)` | A directory tree of templates (only for `DirectoryRenderer`) |
|
|
205
|
+
|
|
206
|
+
| Renderers | `render()` returns |
|
|
207
|
+
|-----------|--------------------|
|
|
208
|
+
| `StringRenderer()` | The rendered `str` (or `None` if skipped) |
|
|
209
|
+
| `StdOutRenderer()` | Prints the result, returns `None` |
|
|
210
|
+
| `FileRenderer(output_path, overwrite=True)` | The absolute path written (or `None` if skipped). A path ending in `/` or an existing directory is a directory |
|
|
211
|
+
| `DirectoryRenderer(output_path, overwrite=True, purge=True)` | A list of the absolute paths written |
|
|
212
|
+
|
|
213
|
+
Output files are always written as UTF-8 with `\n` line endings.
|
|
214
|
+
|
|
215
|
+
### Jinja2 environment
|
|
216
|
+
|
|
217
|
+
Templates are rendered in a Jinja2 environment with `trim_blocks`,
|
|
218
|
+
`lstrip_blocks` and `keep_trailing_newline` turned on, and `ChainableUndefined`
|
|
219
|
+
so that missing (even deeply nested) values render as empty strings instead of
|
|
220
|
+
raising errors:
|
|
221
|
+
|
|
222
|
+
```jinja
|
|
223
|
+
{{ nothing.here.at_all }} {# renders as an empty string #}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The context is available as top level variables, plus the
|
|
227
|
+
[`__stencil__`](#the-__stencil__-variable) variable.
|
|
228
|
+
|
|
229
|
+
Note that only the context _file_ is processed by AllConf. Templates are plain
|
|
230
|
+
Jinja2, so `${__ENV__:...}` and friends are not resolved in them (except in
|
|
231
|
+
files pulled in with `{% embed ..., allconf=True %}`).
|
|
232
|
+
|
|
233
|
+
## Template search paths
|
|
234
|
+
|
|
235
|
+
`{% include %}`, `{% extends %}`, `{% import %}`, file template names and
|
|
236
|
+
`{% embed %}` look for files in:
|
|
237
|
+
|
|
238
|
+
1. The current working directory
|
|
239
|
+
2. The directory of the running script (`sys.argv[0]`)
|
|
240
|
+
3. Every path in the `STENCIL_TEMPLATE_PATH` environment variable, separated
|
|
241
|
+
by `;` or the OS path separator (`:` on Linux/macOS)
|
|
242
|
+
|
|
243
|
+
The list is available as `renderer.search_paths`.
|
|
244
|
+
|
|
245
|
+
## Custom tags and filters
|
|
246
|
+
|
|
247
|
+
### `{% embed 'file' %}`
|
|
248
|
+
|
|
249
|
+
Inserts a file "as-is" (it is _not_ rendered as a template). The file is looked
|
|
250
|
+
up relative to the template doing the embedding first, then in the
|
|
251
|
+
[search paths](#template-search-paths); an absolute path is used directly. A
|
|
252
|
+
missing file raises `EmbedFileNotFound`. A newline is added after the embedded
|
|
253
|
+
content.
|
|
254
|
+
|
|
255
|
+
```jinja
|
|
256
|
+
{% embed 'fragment.yaml' %}
|
|
257
|
+
{% embed 'fragment.yaml', indent=4 %}
|
|
258
|
+
{% embed 'fragment.yaml', allconf=True, env=True %}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Argument | Effect |
|
|
262
|
+
|----------|--------|
|
|
263
|
+
| `indent=N` | Indent every line by N spaces (handy for YAML) |
|
|
264
|
+
| `allconf=True` | Render the file with AllConf first (`alviss=True` is a deprecated alias) |
|
|
265
|
+
| `env=True` | With `allconf`: resolve `${__ENV__:...}` |
|
|
266
|
+
| `py=True` | With `allconf`: resolve `${__PY__:...}` |
|
|
267
|
+
| `fidelius=True` | With `allconf`: resolve `${__FID__:...}` (needs `batstencil[fidelius]`) |
|
|
268
|
+
|
|
269
|
+
### `{% skip_if condition %}`
|
|
270
|
+
|
|
271
|
+
Skips the template if the condition is true: nothing is written (the CLI prints
|
|
272
|
+
`Skipped rendering (skip_if)`, `render_stencil` returns `None`). In a
|
|
273
|
+
`__stencil_meta__` file it skips the whole directory.
|
|
274
|
+
|
|
275
|
+
```jinja
|
|
276
|
+
{% skip_if not app.use_docker %}
|
|
277
|
+
FROM python:3.12
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### `{% setfilename %}...{% endsetfilename %}`
|
|
281
|
+
|
|
282
|
+
Sets the name of the output file (or directory, in `__stencil_meta__`) to the
|
|
283
|
+
stripped result of its body. This takes precedence over the template's own name
|
|
284
|
+
and over a file name given in the output path. An empty result logs a warning
|
|
285
|
+
and the default name is used.
|
|
286
|
+
|
|
287
|
+
```jinja
|
|
288
|
+
{% setfilename %}{{ app.name }}.yaml{% endsetfilename %}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### `{% repeat_for item in items %}`
|
|
292
|
+
|
|
293
|
+
Directory rendering only (using it elsewhere raises `RenderError`). Renders the
|
|
294
|
+
file (or, in a `__stencil_meta__` file, the whole directory) once per item, with
|
|
295
|
+
`item` available in the template. Several names unpack each item, e.g.
|
|
296
|
+
`{% repeat_for k, v in some_dict.items() %}`. A missing or empty list renders
|
|
297
|
+
nothing. Every copy needs a unique name, so combine it with `setfilename`
|
|
298
|
+
(otherwise you get a `TemplateRenderError`):
|
|
299
|
+
|
|
300
|
+
```jinja
|
|
301
|
+
{% repeat_for svc in services %}
|
|
302
|
+
{% setfilename %}{{ svc }}.yaml{% endsetfilename %}
|
|
303
|
+
name: {{ svc }}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### `base64` filter
|
|
307
|
+
|
|
308
|
+
Base64 encodes a string (or bytes) to a single line, e.g. for Kubernetes
|
|
309
|
+
Secrets: `password: {{ db.password | base64 }}`.
|
|
310
|
+
|
|
311
|
+
## Directory rendering
|
|
312
|
+
|
|
313
|
+
`DirectoryRenderer` (and `bat-stencil -d`) walks a template directory and
|
|
314
|
+
renders it into the output directory:
|
|
315
|
+
|
|
316
|
+
- Every file is a template, rendered with the same context, and written with the
|
|
317
|
+
same name (unless `{% setfilename %}` says otherwise). `{% skip_if %}` and
|
|
318
|
+
`{% repeat_for %}` work in them too.
|
|
319
|
+
- A file named `__stencil_meta__` inside a **subdirectory** (not allowed in the
|
|
320
|
+
root) is rendered to control that directory: `{% setfilename %}` for its name,
|
|
321
|
+
`{% skip_if %}` to skip it and everything in it, and `{% repeat_for %}` to
|
|
322
|
+
render it once per item. Its text output is ignored and it is never copied.
|
|
323
|
+
- Directories inside a repeated directory inherit its loop variables.
|
|
324
|
+
- Errors in a template raise `TemplateRenderError` naming the failing template
|
|
325
|
+
file (and line).
|
|
326
|
+
- Output paths that would escape the output directory are refused.
|
|
327
|
+
|
|
328
|
+
Example template tree:
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
my_template_project/
|
|
332
|
+
README.md # "# {{ app.name }}"
|
|
333
|
+
docs/
|
|
334
|
+
__stencil_meta__ # {% skip_if not app.docs %}
|
|
335
|
+
index.md
|
|
336
|
+
services/
|
|
337
|
+
svc/
|
|
338
|
+
__stencil_meta__ # {% repeat_for svc in services %}
|
|
339
|
+
# {% setfilename %}{{ svc }}{% endsetfilename %}
|
|
340
|
+
main.py # "# {{ svc }} ({{ __stencil__.loop.index }}/{{ __stencil__.loop.length }})"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
With `services: [api, worker]` and no `app.docs` this renders:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
generated/README.md
|
|
347
|
+
generated/services/api/main.py
|
|
348
|
+
generated/services/worker/main.py
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Purging
|
|
352
|
+
|
|
353
|
+
By default the output directory is **deleted before rendering** so it only
|
|
354
|
+
contains what the template produces. Use `--no-purge` / `purge=False` to render
|
|
355
|
+
into an existing directory and keep what's there. With `--no-overwrite` /
|
|
356
|
+
`overwrite=False`, rendering fails if the output directory exists (when
|
|
357
|
+
purging) or if any file to be written exists (when not purging).
|
|
358
|
+
|
|
359
|
+
As a safety guard the renderer refuses to purge (raising `UnsafePurgeError`)
|
|
360
|
+
the current working directory or any of its parents, the home directory, a file
|
|
361
|
+
system root or a directory containing the template directory.
|
|
362
|
+
|
|
363
|
+
## The `__stencil__` variable
|
|
364
|
+
|
|
365
|
+
Every render has a `__stencil__` variable with info about the rendering:
|
|
366
|
+
|
|
367
|
+
| Key | Value |
|
|
368
|
+
|-----|-------|
|
|
369
|
+
| `version` | The batstencil version |
|
|
370
|
+
| `template_name` | File name of the template (`None` for string templates) |
|
|
371
|
+
| `template_path` | Absolute path of the template file (`None` for string templates) |
|
|
372
|
+
|
|
373
|
+
Directory rendering adds:
|
|
374
|
+
|
|
375
|
+
| Key | Value |
|
|
376
|
+
|-----|-------|
|
|
377
|
+
| `template_root` | Absolute path of the template directory |
|
|
378
|
+
| `template_relative_path` | Path of the current template relative to the root (posix style) |
|
|
379
|
+
| `source_dir` | The current template's directory, relative to the root (`.` for the root) |
|
|
380
|
+
| `output_root` | Absolute path of the output directory |
|
|
381
|
+
| `output_dir` | Current output directory, relative to the output root (posix style) |
|
|
382
|
+
| `loop` | Info on the innermost `repeat_for` loop (or `None`): `index` (from 1), `index0`, `first`, `last`, `length` and `item` |
|
|
383
|
+
| `loops` | A list of all enclosing loops, outermost first |
|
|
384
|
+
|
|
385
|
+
## Errors
|
|
386
|
+
|
|
387
|
+
All batstencil errors derive from `StencilError`:
|
|
388
|
+
|
|
389
|
+
- `TemplateError` / `TemplateNotFoundError` (also a `FileNotFoundError`)
|
|
390
|
+
- `ContextError` (e.g. a malformed `-a` argument)
|
|
391
|
+
- `RenderError`
|
|
392
|
+
- `NoTemplateSetError`, `InvalidContextTypeForRendererError`,
|
|
393
|
+
`InvalidTemplateTypeForRendererError`, `NoOutputFileNameError`
|
|
394
|
+
- `OutputFileExistsError` (also a `FileExistsError`)
|
|
395
|
+
- `EmbedFileNotFound` (also a `FileNotFoundError`)
|
|
396
|
+
- `TemplateRenderError` (has `template_path`; the original error is chained
|
|
397
|
+
as `__cause__`)
|
|
398
|
+
- `UnsafePurgeError`
|
|
399
|
+
|
|
400
|
+
Plain Jinja2 errors (e.g. syntax errors) outside of directory rendering are
|
|
401
|
+
raised as `jinja2.TemplateError`s; the CLI reports both kinds and exits with
|
|
402
|
+
code 3.
|
|
403
|
+
|
|
404
|
+
## License
|
|
405
|
+
|
|
406
|
+
MIT, see [LICENSE](LICENSE).
|