-
Notifications
You must be signed in to change notification settings - Fork 55
/
need_constraints.py
106 lines (87 loc) · 4.63 KB
/
need_constraints.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from typing import Dict
import jinja2
from sphinx.application import Sphinx
from sphinx_needs.api.exceptions import NeedsConstraintFailed, NeedsConstraintNotAllowed
from sphinx_needs.config import NeedsSphinxConfig
from sphinx_needs.data import SphinxNeedsData
from sphinx_needs.filter_common import filter_single_need
from sphinx_needs.logging import get_logger
logger = get_logger(__name__)
def process_constraints(app: Sphinx) -> None:
"""Analyse constraints of a single need,
and set corresponding fields on the need data item.
"""
env = app.env
needs_config = NeedsSphinxConfig(env.config)
config_constraints = needs_config.constraints
needs = SphinxNeedsData(env).get_or_create_needs()
workflow = SphinxNeedsData(env).get_or_create_workflow()
if workflow["needs_constraints"]:
return
workflow["needs_constraints"] = True
error_templates_cache: Dict[str, jinja2.Template] = {}
for need in needs.values():
need_id = need["id"]
constraints = need["constraints"]
# flag that is set to False if any check fails
need["constraints_passed"] = True
for constraint in constraints:
try:
executable_constraints = config_constraints[constraint]
except KeyError:
# Note, this is already checked for in add_need
raise NeedsConstraintNotAllowed(
f"Constraint {constraint} of need id {need_id} is not allowed by config value 'needs_constraints'."
)
# name is check_0, check_1, ...
for name, cmd in executable_constraints.items():
if name in ("severity", "error_message"):
# special keys, that are not a check
continue
# compile constraint and check if need fulfils it
constraint_passed = filter_single_need(app, need, cmd)
if constraint_passed:
need["constraints_results"].setdefault(constraint, {})[name] = True
else:
need["constraints_results"].setdefault(constraint, {})[name] = False
need["constraints_passed"] = False
if "error_message" in executable_constraints:
msg = str(executable_constraints["error_message"])
template = error_templates_cache.setdefault(msg, jinja2.Template(msg))
need["constraints_error"] = template.render(**need)
if "severity" not in executable_constraints:
raise NeedsConstraintFailed(
f"'severity' key not set for constraint {constraint!r} in config 'needs_constraints'"
)
severity = executable_constraints["severity"]
if severity not in needs_config.constraint_failed_options:
raise NeedsConstraintFailed(
f"Severity {severity!r} not set in config 'needs_constraint_failed_options'"
)
failed_options = needs_config.constraint_failed_options[severity]
# log/except if needed
if "warn" in failed_options.get("on_fail", []):
logger.warning(
f"Constraint {cmd} for need {need_id} FAILED! severity: {severity} {need.get('constraints_error', '')} [needs.constraint]",
type="needs",
subtype="constraint",
color="red",
location=(need["docname"], need["lineno"]),
)
if "break" in failed_options.get("on_fail", []):
raise NeedsConstraintFailed(
f"FAILED a breaking constraint: >> {cmd} << for need "
f"{need_id} FAILED! breaking build process"
)
# set styles
old_style = need["style"]
if old_style and len(old_style) > 0:
new_styles = "".join(", " + x for x in failed_options.get("style", []))
else:
old_style = ""
new_styles = "".join(x + "," for x in failed_options.get("style", []))
if failed_options.get("force_style", False):
need["style"] = new_styles.strip(", ")
else:
constraint_failed_style = old_style + new_styles
need["style"] = constraint_failed_style