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.
Files changed (65) hide show
  1. batstencil-1.0.0/LICENSE +22 -0
  2. batstencil-1.0.0/PKG-INFO +406 -0
  3. batstencil-1.0.0/README.md +367 -0
  4. batstencil-1.0.0/batstencil/__init__.py +5 -0
  5. batstencil-1.0.0/batstencil/__main__.py +4 -0
  6. batstencil-1.0.0/batstencil/cli/__init__.py +0 -0
  7. batstencil-1.0.0/batstencil/cli/bat_stencil/__init__.py +0 -0
  8. batstencil-1.0.0/batstencil/cli/bat_stencil/_runner.py +88 -0
  9. batstencil-1.0.0/batstencil/cli/bat_stencil/main.py +80 -0
  10. batstencil-1.0.0/batstencil/context/__init__.py +4 -0
  11. batstencil-1.0.0/batstencil/context/_allconf.py +40 -0
  12. batstencil-1.0.0/batstencil/context/_dict.py +25 -0
  13. batstencil-1.0.0/batstencil/context/_kwarg.py +10 -0
  14. batstencil-1.0.0/batstencil/jinjaext/__init__.py +0 -0
  15. batstencil-1.0.0/batstencil/jinjaext/extensions/__init__.py +4 -0
  16. batstencil-1.0.0/batstencil/jinjaext/extensions/_embed.py +54 -0
  17. batstencil-1.0.0/batstencil/jinjaext/extensions/_repeat_for.py +44 -0
  18. batstencil-1.0.0/batstencil/jinjaext/extensions/_setfilename.py +38 -0
  19. batstencil-1.0.0/batstencil/jinjaext/extensions/_skip_if.py +24 -0
  20. batstencil-1.0.0/batstencil/jinjaext/filters/__init__.py +1 -0
  21. batstencil-1.0.0/batstencil/jinjaext/filters/_base64.py +13 -0
  22. batstencil-1.0.0/batstencil/renderer/__init__.py +6 -0
  23. batstencil-1.0.0/batstencil/renderer/_base.py +283 -0
  24. batstencil-1.0.0/batstencil/renderer/_dir.py +253 -0
  25. batstencil-1.0.0/batstencil/renderer/_file.py +72 -0
  26. batstencil-1.0.0/batstencil/renderer/_stdout.py +14 -0
  27. batstencil-1.0.0/batstencil/renderer/_string.py +22 -0
  28. batstencil-1.0.0/batstencil/shortcuts/__init__.py +1 -0
  29. batstencil-1.0.0/batstencil/shortcuts/_render_stencil.py +19 -0
  30. batstencil-1.0.0/batstencil/stencils.py +6 -0
  31. batstencil-1.0.0/batstencil/structs/__init__.py +4 -0
  32. batstencil-1.0.0/batstencil/structs/_aliases.py +7 -0
  33. batstencil-1.0.0/batstencil/structs/_base.py +2 -0
  34. batstencil-1.0.0/batstencil/structs/_errors.py +95 -0
  35. batstencil-1.0.0/batstencil/structs/interfaces/__init__.py +3 -0
  36. batstencil-1.0.0/batstencil/structs/interfaces/_context.py +24 -0
  37. batstencil-1.0.0/batstencil/structs/interfaces/_renderer.py +61 -0
  38. batstencil-1.0.0/batstencil/structs/interfaces/_template.py +24 -0
  39. batstencil-1.0.0/batstencil/template/__init__.py +5 -0
  40. batstencil-1.0.0/batstencil/template/_base.py +16 -0
  41. batstencil-1.0.0/batstencil/template/_dir.py +122 -0
  42. batstencil-1.0.0/batstencil/template/_file.py +53 -0
  43. batstencil-1.0.0/batstencil/template/_string.py +16 -0
  44. batstencil-1.0.0/batstencil/utils/__init__.py +1 -0
  45. batstencil-1.0.0/batstencil/utils/_guessers.py +63 -0
  46. batstencil-1.0.0/batstencil.egg-info/PKG-INFO +406 -0
  47. batstencil-1.0.0/batstencil.egg-info/SOURCES.txt +63 -0
  48. batstencil-1.0.0/batstencil.egg-info/dependency_links.txt +1 -0
  49. batstencil-1.0.0/batstencil.egg-info/entry_points.txt +2 -0
  50. batstencil-1.0.0/batstencil.egg-info/requires.txt +6 -0
  51. batstencil-1.0.0/batstencil.egg-info/top_level.txt +1 -0
  52. batstencil-1.0.0/pyproject.toml +67 -0
  53. batstencil-1.0.0/setup.cfg +4 -0
  54. batstencil-1.0.0/setup.py +6 -0
  55. batstencil-1.0.0/tests/test_base64_filter.py +42 -0
  56. batstencil-1.0.0/tests/test_cli.py +169 -0
  57. batstencil-1.0.0/tests/test_contexts.py +96 -0
  58. batstencil-1.0.0/tests/test_directory_renderer.py +551 -0
  59. batstencil-1.0.0/tests/test_embed.py +58 -0
  60. batstencil-1.0.0/tests/test_embed_more.py +105 -0
  61. batstencil-1.0.0/tests/test_file_renderer.py +187 -0
  62. batstencil-1.0.0/tests/test_filters.py +34 -0
  63. batstencil-1.0.0/tests/test_guessers.py +153 -0
  64. batstencil-1.0.0/tests/test_skip_if.py +45 -0
  65. batstencil-1.0.0/tests/test_some_basic_stuff.py +32 -0
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/batstencil)](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).