Coverage for src / template / schema.py: 88%
35 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
1# vim: foldmarker=[[,]] foldmethod=marker
2from ..answers import schema as answers
4# https://docs.python-cerberus.org/en/stable/validation-rules.html
5SCHEMA = (
6 answers.SCHEMA
7 + r"""
8actions: # [[
9 type: list
10 default: []
11 schema:
12 type: dict
13 default: {}
14 nullable: true
15 schema:
16 # actions.hidden
17 # --------------
18 action:
19 type: string
20 required: True
21 allowed: ["move", "remove", "remove-trailing-newline"]
23 # actions.dst
24 # -----------
25 dst:
26 type: string
27 excludes: ["path"]
29 # actions.else
30 # ------------
31 else:
32 type: string
33 allowed: ["remove"]
35 # actions.if
36 # ----------
37 if:
38 type: [string, boolean]
39 required: True
41 # actions.order
42 # -------------
43 order:
44 type: integer
45 default: 0
47 # actions.path
48 # ------------
49 path:
50 type: [string, list]
51 excludes: ["dst", "src"]
53 # actions.src
54 # -----------
55 src:
56 type: string
57 excludes: ["path"]
58# ]]
60includes: # [[
61 type: list
62 default: []
63 schema:
64 type: dict
65 default: {}
66 nullable: true
68 schema:
69 # includes.branch
70 # --------------
71 branch:
72 type: string
73 nullable: True
75 # includes.include
76 # ---------------
77 include:
78 type: string
79 required: true
80 check_with: include_minlength
82 # includes.filename
83 # ---------------
84 filename:
85 type: string
86 default: "scaffold.yml"
87# ]]
89jinja2: # [[
90 type: dict
91 default: {}
92 schema:
93 # lstrip_blocks
94 lstrip_blocks:
95 type: boolean
96 default: false
98 # trim_blocks
99 trim_blocks:
100 type: boolean
101 default: false
102# ]]
104questions: # [[
105 type: list
106 default: []
107 schema:
108 type: dict
109 default: {}
110 nullable: true
111 schema:
113 # questions.description
114 # ---------------------
115 description:
116 type: string
118 # questions.hidden
119 # ----------------
120 hidden:
121 type: [string, boolean]
122 default: false
124 # questions.if
125 # ------------
126 if:
127 type: [string, boolean]
129 # questions.name
130 # --------------
131 name:
132 type: string
133 regex: '^\S+$'
134 required: true
135 minlength: 1
137 # questions.order
138 # ---------------
139 order:
140 type: integer
141 default: 0
143 # questions.schema
144 # ----------------
145 schema:
146 type: dict
147 nullable: true
148 coerce: asdict
149 check_with: schema_rules
150 schema:
152 # questions.schema.allowed
153 # ------------------------
154 allowed:
155 type: list
156 schema:
157 type: [string, integer]
159 # questions.schema.default
160 # ------------------------
161 default:
162 type: [string, boolean, integer]
164 # questions.schema.nullable
165 # -------------------------
166 nullable:
167 type: boolean
168 default: False
170 # questions.schema.max_length
171 # ---------------------------
172 max_length:
173 type: integer
175 # questions.schema.max_value
176 # ---------------------------
177 max_value:
178 type: integer
180 # questions.schema.min_length
181 # ---------------------------
182 min_length:
183 type: integer
184 min: 1
186 # questions.schema.min_value
187 # ---------------------------
188 min_value:
189 type: integer
191 # questions.schema.type
192 # ---------------------
193 type:
194 type: string
195 default: "string"
196 allowed: ["string", "integer", "boolean"]
197# ]]
198"""
199)
202class LocalValidator(answers.LocalValidator):
203 def _normalize_coerce_asdict(self, value):
204 if value is None: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true
205 return {}
206 return value
208 def _normalize_coerce_tolist(self, value):
209 if isinstance(value, str):
210 return [value]
211 return value
213 def _check_with_include_minlength(self, field, value):
214 if value is None or len(value) == 0:
215 self._error(field, "Length of an 'include' path must be non-zero")
217 def _check_with_schema_rules(self, field, schema):
218 if schema.get("type") in ["boolean", "string"] and any(k in schema for k in ["min_value", "max_value"]):
219 self._error(
220 field,
221 'neither "min_value" nor "max_value" can be specified if "type" is either boolean or string',
222 )
223 return
224 if schema.get("type") in ["boolean", "integer"] and any(k in schema for k in ["min_length", "max_length"]):
225 # fmt: off
226 self._error(field, 'neither "min_length" nor "max_length" can be specified if "type" is either boolean or integer')
227 return
228 # fmt: on
229 if "allowed" in schema and schema.get("type") == "boolean":
230 self._error(field, '"allowed" can not be specified if "type" is boolean')
231 return
232 if "allowed" in schema and any(k in schema for k in ["min_length", "max_length", "min_value", "max_value"]):
233 # fmt: off
234 self._error(field, '"allowed" can not be specified when one of "min_length", "max_length", "min_value", or "max_value" is also specified')
235 return
236 # fmt: on
237 if "min_value" in schema and "max_value" in schema:
238 if int(schema["min_value"]) > int(schema["max_value"]):
239 self._error(field, '"min_value" must be inferior to "max_value"')
240 return
241 if "min_length" in schema and "max_length" in schema:
242 if schema["min_length"] > schema["max_length"]:
243 self._error(field, '"min_length" must be inferior to "max_length"')
244 return
247# def _check_with_file_rules(self, field, value):
248# if "else" in value and "if" not in value:
249# self._error(field, '"if" required if a "files" element contains an "else"')
250# return
251# if "move" in [value.get("action", ""), value.get("else", "")] and "dest" not in value:
252# self._error(field, '"dest" required if a "files" element contains an "action" or "else"')
253# return