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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: makejinja
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Generate entire directory structures using Jinja templates with support for external data and custom plugins.
5
5
  Home-page: https://mirkolenz.github.io/makejinja/
6
6
  License: MIT
@@ -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
- if not config.quiet:
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
- if not config.quiet:
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
- if not config.quiet:
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) == 1
104
- and config.inputs[0].is_file()
105
- and (config.output.suffix != "" or config.output.is_file())
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
- if not config.quiet:
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=True,
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
- {path.name: path.read_text() for path in config.inputs if path.is_file()}
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
- if not config.quiet:
301
- print(f"Load data '{path}'")
323
+ log(f"Load data '{path}'", config)
302
324
 
303
325
  data |= loader(path)
304
326
  else:
305
- if not config.quiet:
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
- if not config.quiet:
359
- print(f"Skip existing dir '{output}'")
379
+ log(f"Skip existing dir '{output}'", config)
360
380
  else:
361
- if not config.quiet:
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
- if not config.quiet:
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
- if not config.quiet:
387
- print(f"Skip empty file '{input}'")
404
+ log(f"Skip empty file '{input}'", config)
388
405
  else:
389
- if not config.quiet:
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
- if not config.quiet:
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.4.0"
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 = "^7.4"
61
- pytest-cov = "^4.1"
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