Skip to content

Commit 47aa464

Browse files
committed
Add some basic variable validation
Validate the `name`, `python_package` and `pypi_package_name`. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent b7ae9ee commit 47aa464

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Cookiecutter pre-generation hooks.
5+
6+
This module contains the pre-generation hooks for the cookiecutter template. It
7+
validates the cookiecutter variables and prints an error message and exits with a
8+
non-zero exit code if any of them are invalid.
9+
"""
10+
11+
import collections
12+
import json
13+
import re
14+
import sys
15+
from typing import Any
16+
17+
NAME_REGEX = re.compile(r"^[a-zA-Z][_a-zA-Z0-9]+(-[_a-zA-Z][_a-zA-Z0-9]+)*$")
18+
PYTHON_PACKAGE_REGEX = re.compile(r"^[a-zA-Z][_a-zA-Z0-9]+(\.[_a-zA-Z][_a-zA-Z0-9]+)*$")
19+
PYPI_PACKAGE_REGEX = NAME_REGEX
20+
21+
22+
def to_named_tuple(dictionary: dict[Any, Any], /) -> Any:
23+
"""Convert a dictionary to a named tuple.
24+
25+
Args:
26+
dictionary: The dictionary to convert.
27+
28+
Returns:
29+
The named tuple with the same keys and values as the dictionary.
30+
"""
31+
filtered = {k: v for k, v in dictionary.items() if not k.startswith("_")}
32+
return collections.namedtuple("Cookiecutter", filtered.keys())(*filtered.values())
33+
34+
35+
cookiecutter = to_named_tuple(json.loads(r"""{{cookiecutter | tojson}}"""))
36+
37+
38+
def main() -> None:
39+
"""Validate the cookiecutter variables.
40+
41+
This function validates the cookiecutter variables and prints an error message and
42+
exits with a non-zero exit code if any of them are invalid.
43+
"""
44+
errors: dict[str, list[str]] = {}
45+
46+
def add_error(key: str, message: str) -> None:
47+
"""Add an error to the error dictionary.
48+
49+
Args:
50+
key: The key of the error.
51+
message: The error message.
52+
"""
53+
errors.setdefault(key, []).append(message)
54+
55+
if not NAME_REGEX.match(cookiecutter.name):
56+
add_error("name", f"Invalid project name (must match {NAME_REGEX.pattern})")
57+
58+
if not PYTHON_PACKAGE_REGEX.match(cookiecutter.python_package):
59+
add_error(
60+
"python_package",
61+
f"Invalid package name (must match {PYTHON_PACKAGE_REGEX.pattern})",
62+
)
63+
64+
if not PYPI_PACKAGE_REGEX.match(cookiecutter.pypi_package_name):
65+
add_error(
66+
"pypi_package_name",
67+
f"Invalid package name (must match {PYPI_PACKAGE_REGEX.pattern})",
68+
)
69+
70+
if errors:
71+
print("The following errors were found:", file=sys.stderr)
72+
for key, messages in errors.items():
73+
print(f" {key}:", file=sys.stderr)
74+
for message in messages:
75+
print(f" - {message}", file=sys.stderr)
76+
sys.exit(1)
77+
78+
79+
if __name__ == "__main__":
80+
main()

0 commit comments

Comments
 (0)