makejinja 2.4.0__tar.gz → 2.6.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.
- {makejinja-2.4.0 → makejinja-2.6.0}/PKG-INFO +1 -1
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/app.py +48 -33
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/config.py +21 -1
- {makejinja-2.4.0 → makejinja-2.6.0}/pyproject.toml +3 -3
- {makejinja-2.4.0 → makejinja-2.6.0}/LICENSE +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/README.md +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/__init__.py +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/__main__.py +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/cli.py +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/plugin.py +0 -0
- {makejinja-2.4.0 → makejinja-2.6.0}/makejinja/py.typed +0 -0
|
@@ -2,6 +2,7 @@ import itertools
|
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
+
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
import tomllib
|
|
7
8
|
from collections import abc
|
|
@@ -20,18 +21,32 @@ from makejinja.plugin import Data, MutableData, PathFilter, Plugin
|
|
|
20
21
|
|
|
21
22
|
__all__ = ["makejinja"]
|
|
22
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
|
+
|
|
23
36
|
|
|
24
37
|
def makejinja(config: Config):
|
|
25
38
|
"""makejinja can be used to automatically generate files from [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/)."""
|
|
26
39
|
|
|
40
|
+
for cmd in config.exec_pre:
|
|
41
|
+
exec(cmd)
|
|
42
|
+
|
|
27
43
|
for path in config.import_paths:
|
|
28
44
|
sys.path.append(str(path.resolve()))
|
|
29
45
|
|
|
30
46
|
data = load_data(config)
|
|
31
47
|
|
|
32
48
|
if config.output.is_dir() and config.clean:
|
|
33
|
-
|
|
34
|
-
print(f"Remove output '{config.output}'")
|
|
49
|
+
log(f"Remove output '{config.output}'", config)
|
|
35
50
|
|
|
36
51
|
shutil.rmtree(config.output)
|
|
37
52
|
|
|
@@ -61,7 +76,7 @@ def makejinja(config: Config):
|
|
|
61
76
|
rendered_dirs: dict[Path, Path] = {}
|
|
62
77
|
|
|
63
78
|
for user_input_path in config.inputs:
|
|
64
|
-
if user_input_path.is_file():
|
|
79
|
+
if user_input_path.is_file() or user_input_path == STDIN_PATH:
|
|
65
80
|
handle_input_file(user_input_path, config, env, rendered_files)
|
|
66
81
|
elif user_input_path.is_dir():
|
|
67
82
|
handle_input_dir(
|
|
@@ -75,6 +90,9 @@ def makejinja(config: Config):
|
|
|
75
90
|
|
|
76
91
|
postprocess_rendered_dirs(config, rendered_dirs)
|
|
77
92
|
|
|
93
|
+
for cmd in config.exec_post:
|
|
94
|
+
exec(cmd)
|
|
95
|
+
|
|
78
96
|
|
|
79
97
|
def postprocess_rendered_dirs(
|
|
80
98
|
config: Config,
|
|
@@ -85,24 +103,24 @@ def postprocess_rendered_dirs(
|
|
|
85
103
|
rendered_dirs.items(), key=lambda x: x[0], reverse=True
|
|
86
104
|
):
|
|
87
105
|
if not config.keep_empty and not any(output_path.iterdir()):
|
|
88
|
-
|
|
89
|
-
print(f"Remove empty dir '{output_path}'")
|
|
90
|
-
|
|
106
|
+
log(f"Remove empty dir '{output_path}'", config)
|
|
91
107
|
shutil.rmtree(output_path)
|
|
92
108
|
|
|
93
109
|
elif config.copy_metadata:
|
|
94
|
-
|
|
95
|
-
print(f"Copy dir metadata '{input_path}' -> '{output_path}'")
|
|
96
|
-
|
|
110
|
+
log(f"Copy dir metadata '{input_path}' -> '{output_path}'", config)
|
|
97
111
|
shutil.copystat(input_path, output_path)
|
|
98
112
|
|
|
99
113
|
|
|
100
114
|
def single_input_output_file(config: Config) -> bool:
|
|
101
115
|
"""Check if the user provided a single input and a single output"""
|
|
102
116
|
return (
|
|
103
|
-
len(config.inputs)
|
|
104
|
-
and config.inputs
|
|
105
|
-
and (
|
|
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
|
+
)
|
|
106
124
|
and not config.output.is_dir()
|
|
107
125
|
)
|
|
108
126
|
|
|
@@ -142,6 +160,8 @@ def handle_input_dir(
|
|
|
142
160
|
for include_pattern in config.include_patterns
|
|
143
161
|
for input_path in sorted(user_input_path.glob(include_pattern))
|
|
144
162
|
)
|
|
163
|
+
# If the user provided a Jinja suffix, enforce it
|
|
164
|
+
enforce_jinja_suffix = bool(config.jinja_suffix)
|
|
145
165
|
|
|
146
166
|
for input_path in input_paths:
|
|
147
167
|
relative_path = input_path.relative_to(user_input_path)
|
|
@@ -154,8 +174,7 @@ def handle_input_dir(
|
|
|
154
174
|
not path_filter(input_path) for path_filter in plugin_path_filters
|
|
155
175
|
)
|
|
156
176
|
if exclude_pattern_match or path_filter_match:
|
|
157
|
-
|
|
158
|
-
print(f"Skip excluded path '{input_path}'")
|
|
177
|
+
log(f"Skip excluded path '{input_path}'", config)
|
|
159
178
|
|
|
160
179
|
elif input_path.is_file() and output_path not in rendered_files:
|
|
161
180
|
render_file(
|
|
@@ -164,7 +183,7 @@ def handle_input_dir(
|
|
|
164
183
|
output_path,
|
|
165
184
|
config,
|
|
166
185
|
env,
|
|
167
|
-
enforce_jinja_suffix
|
|
186
|
+
enforce_jinja_suffix,
|
|
168
187
|
)
|
|
169
188
|
rendered_files[output_path] = input_path
|
|
170
189
|
|
|
@@ -190,7 +209,11 @@ def init_jinja_env(
|
|
|
190
209
|
data: Data,
|
|
191
210
|
) -> Environment:
|
|
192
211
|
file_loader = DictLoader(
|
|
193
|
-
{
|
|
212
|
+
{
|
|
213
|
+
path.name: path.read_text()
|
|
214
|
+
for path in config.inputs
|
|
215
|
+
if path.is_file() or path == STDIN_PATH
|
|
216
|
+
}
|
|
194
217
|
)
|
|
195
218
|
dir_loader = FileSystemLoader([path for path in config.inputs if path.is_dir()])
|
|
196
219
|
loaders: list[BaseLoader] = [file_loader, dir_loader]
|
|
@@ -297,13 +320,11 @@ def load_data(config: Config) -> dict[str, Any]:
|
|
|
297
320
|
|
|
298
321
|
for path in collect_files(config.data):
|
|
299
322
|
if loader := DATA_LOADERS.get(path.suffix):
|
|
300
|
-
|
|
301
|
-
print(f"Load data '{path}'")
|
|
323
|
+
log(f"Load data '{path}'", config)
|
|
302
324
|
|
|
303
325
|
data |= loader(path)
|
|
304
326
|
else:
|
|
305
|
-
|
|
306
|
-
print(f"Skip unsupported data '{path}'")
|
|
327
|
+
log(f"Skip unsupported data '{path}'", config)
|
|
307
328
|
|
|
308
329
|
for key, value in config.data_vars.items():
|
|
309
330
|
dict_nested_set(data, key, value)
|
|
@@ -355,11 +376,9 @@ def load_plugin(
|
|
|
355
376
|
|
|
356
377
|
def render_dir(input: Path, output: Path, config: Config) -> None:
|
|
357
378
|
if output.exists() and not config.force:
|
|
358
|
-
|
|
359
|
-
print(f"Skip existing dir '{output}'")
|
|
379
|
+
log(f"Skip existing dir '{output}'", config)
|
|
360
380
|
else:
|
|
361
|
-
|
|
362
|
-
print(f"Create dir '{input}' -> '{output}'")
|
|
381
|
+
log(f"Create dir '{input}' -> '{output}'", config)
|
|
363
382
|
|
|
364
383
|
output.mkdir(exist_ok=True)
|
|
365
384
|
|
|
@@ -372,9 +391,8 @@ def render_file(
|
|
|
372
391
|
env: Environment,
|
|
373
392
|
enforce_jinja_suffix: bool,
|
|
374
393
|
) -> None:
|
|
375
|
-
if output.exists() and not config.force:
|
|
376
|
-
|
|
377
|
-
print(f"Skip existing file '{output}'")
|
|
394
|
+
if output.exists() and not config.force and output != STDOUT_PATH:
|
|
395
|
+
log(f"Skip existing file '{output}'", config)
|
|
378
396
|
|
|
379
397
|
elif input.suffix == config.jinja_suffix or not enforce_jinja_suffix:
|
|
380
398
|
template = env.get_template(template_name)
|
|
@@ -383,11 +401,9 @@ def render_file(
|
|
|
383
401
|
# Write the rendered template if it has content
|
|
384
402
|
# Prevents empty macro definitions
|
|
385
403
|
if rendered.strip() == "" and not config.keep_empty:
|
|
386
|
-
|
|
387
|
-
print(f"Skip empty file '{input}'")
|
|
404
|
+
log(f"Skip empty file '{input}'", config)
|
|
388
405
|
else:
|
|
389
|
-
|
|
390
|
-
print(f"Render file '{input}' -> '{output}'")
|
|
406
|
+
log(f"Render file '{input}' -> '{output}'", config)
|
|
391
407
|
|
|
392
408
|
with output.open("w") as fp:
|
|
393
409
|
fp.write(rendered)
|
|
@@ -396,7 +412,6 @@ def render_file(
|
|
|
396
412
|
shutil.copystat(input, output)
|
|
397
413
|
|
|
398
414
|
else:
|
|
399
|
-
|
|
400
|
-
print(f"Copy file '{input}' -> '{output}'")
|
|
415
|
+
log(f"Copy file '{input}' -> '{output}'", config)
|
|
401
416
|
|
|
402
417
|
shutil.copy2(input, output)
|
|
@@ -207,13 +207,14 @@ class Config:
|
|
|
207
207
|
Multiple can be provided.
|
|
208
208
|
""",
|
|
209
209
|
)
|
|
210
|
-
jinja_suffix: str = ts.option(
|
|
210
|
+
jinja_suffix: str | None = ts.option(
|
|
211
211
|
default=".jinja",
|
|
212
212
|
help="""
|
|
213
213
|
File ending of Jinja template files.
|
|
214
214
|
All files with this suffix in `inputs` matched by `pattern` are passed to the Jinja renderer.
|
|
215
215
|
This suffix is not enforced for individual files passed to `inputs`.
|
|
216
216
|
**Note:** Should be provided *with* the leading dot.
|
|
217
|
+
If empty, all files are considered to be Jinja templates.
|
|
217
218
|
""",
|
|
218
219
|
)
|
|
219
220
|
keep_jinja_suffix: bool = ts.option(
|
|
@@ -322,6 +323,18 @@ class Config:
|
|
|
322
323
|
"""
|
|
323
324
|
),
|
|
324
325
|
)
|
|
326
|
+
exec_pre: tuple[str, ...] = ts.option(
|
|
327
|
+
default=tuple(),
|
|
328
|
+
help="""
|
|
329
|
+
Shell commands to execute before rendering.
|
|
330
|
+
""",
|
|
331
|
+
)
|
|
332
|
+
exec_post: tuple[str, ...] = ts.option(
|
|
333
|
+
default=tuple(),
|
|
334
|
+
help="""
|
|
335
|
+
Shell commands to execute after rendering.
|
|
336
|
+
""",
|
|
337
|
+
)
|
|
325
338
|
clean: bool = ts.option(
|
|
326
339
|
default=False,
|
|
327
340
|
click={"param_decls": ("--clean", "-c")},
|
|
@@ -402,5 +415,12 @@ OPTION_GROUPS = {
|
|
|
402
415
|
"--prefix-line-comment",
|
|
403
416
|
],
|
|
404
417
|
},
|
|
418
|
+
{
|
|
419
|
+
"name": "Shell Hooks",
|
|
420
|
+
"options": [
|
|
421
|
+
"--exec-pre",
|
|
422
|
+
"--exec-post",
|
|
423
|
+
],
|
|
424
|
+
},
|
|
405
425
|
]
|
|
406
426
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "makejinja"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.6.0"
|
|
4
4
|
description = "Generate entire directory structures using Jinja templates with support for external data and custom plugins."
|
|
5
5
|
authors = ["Mirko Lenz <mirko@mirkolenz.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -57,8 +57,8 @@ typed-settings = { version = ">=23.1, <25", extras = [
|
|
|
57
57
|
immutables = "^0.20"
|
|
58
58
|
|
|
59
59
|
[tool.poetry.group.dev.dependencies]
|
|
60
|
-
pytest = "^
|
|
61
|
-
pytest-cov = "^
|
|
60
|
+
pytest = "^8.0.0"
|
|
61
|
+
pytest-cov = "^5.0.0"
|
|
62
62
|
|
|
63
63
|
[tool.poetry.group.docs.dependencies]
|
|
64
64
|
pdoc = "^14.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|