Coverage for src / template / __init__.py: 90%
87 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-21 16:36 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-21 16:36 +0000
1import copy
2import json
3import os
4import sys
6import yaml
8from .. import constants as c
9from . import schema
10from .methods import directory, git
13class Template:
14 def __init__(self, **kwargs):
15 self.path = kwargs.get("template_path")
16 self.filename = kwargs.get("template_filename", c.TEMPLATE_FILENAME)
17 self.branch = kwargs.get("branch")
18 self.workpath = kwargs.get("workpath")
19 self.includes = None
20 self.data = None
22 def __repr__(self):
23 # no cover: start
24 return json.dumps(
25 {
26 "path": self.path,
27 "filename": self.filename,
28 "branch": self.branch,
29 "workpath": self.workpath,
30 },
31 )
32 # no cover: stop
34 @property
35 def actions(self):
36 if self.data is None: 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true
37 return []
38 # pylint: disable=unsubscriptable-object
39 return self.data["actions"]
41 @property
42 def questions(self):
43 if self.data is None: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true
44 return {}
45 # pylint: disable=unsubscriptable-object
46 return self.data["questions"]
48 @property
49 def children(self):
50 """
51 Return only the includes for this template
52 """
53 if not self.data: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true
54 return
56 # By reversing the list, we'll be building the list
57 # of questions in the right order
58 items = reversed(self.data.get("includes", []))
59 for item in items:
60 path = item["include"]
61 filename = item.get("filename")
62 branch = item.get("branch")
63 workpath = item.get("workpath")
65 yield Template(template_path=path, template_filename=filename, branch=branch, workpath=workpath)
68def find(tpl):
69 for func in [directory.find, git.find]: 69 ↛ 73line 69 didn't jump to line 73 because the loop on line 69 didn't complete
70 tpl.workpath = func(tpl)
71 if tpl.workpath:
72 return tpl
73 sys.exit(f'error: no "{tpl.filename}" file found in "{tpl.path}"')
76def _load(tpl):
77 path = os.path.join(tpl.workpath, tpl.filename)
78 try:
79 with open(path, encoding="UTF-8") as fd:
80 data = yaml.safe_load(fd) or {}
81 except Exception as err:
82 sys.exit(f"error: failed to open '{tpl.workpath}': {err}")
83 return data
86def _reduce(data):
87 # Reduce to relevant sections.
88 # This allows a user to add whatever he wants to a scaffold.yml file
89 retval = {
90 "actions": data.get("actions", []),
91 "answers": data.get("answers", {}),
92 "includes": data.get("includes", []),
93 "jinja2": data.get("jinja2", {}),
94 "questions": data.get("questions", []),
95 }
96 return retval
99def _validate(data):
100 # TODO: merge with answers into utils
101 yaml_schema = yaml.safe_load(schema.SCHEMA)
102 validator = schema.LocalValidator(yaml_schema)
104 if not validator.validate(data):
105 locations = str(json.dumps(validator.errors, indent=2))
106 raise SystemExit(f"error: YAML schema validation error. Location:\n{locations}") from None
108 return validator.normalized(data)
111def load(tpl, root=None):
112 if root is None:
113 root = tpl
115 data = _load(tpl)
116 data = _reduce(data)
117 data = _validate(data)
118 tpl.data = data
120 # Keep a list of all encountered children
121 if root.includes is None:
122 root.includes = []
123 root.includes += [tpl]
125 # In order to load the questions, we need to recurse into
126 # all files and build the questions list by priority, the
127 # inclusion order
128 return _recurse(tpl, root)
131def _recurse(tpl, root):
132 for other in tpl.children:
133 other = find(other)
134 other = load(other, root)
135 tpl = _merge(tpl, other)
136 return tpl
139def _merge(tpl, other):
140 # Merge questions.
141 # Since the "name" must be unique, we use it as a key
142 # other has priority on self
143 result = {i["name"]: i for i in other.data["questions"]}
144 for question in tpl.data["questions"]:
145 result[question["name"]] = question
146 tpl.data["questions"] = [v for k, v in result.items()]
148 # Sort by order if present
149 tpl.data["questions"] = sorted(tpl.data["questions"], key=lambda item: item.get("order", 0))
151 return tpl