Skip to content

Commit 79b309d

Browse files
vojtapolasekclaude
andcommitted
strip Jinja constructs before running yamllint
Many YAML files in this project contain Jinja2 templating ({{% %}}, {{{ }}}, {{# #}}) which causes yamllint to report false syntax errors. Add a helper script (utils/strip_jinja_for_yamllint.py) that removes Jinja constructs while preserving line numbers, and update the CI workflow to use it for files that contain Jinja. Files without Jinja are linted directly as before. Warnings are reported but do not fail the job. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5fa35ca commit 79b309d

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

.github/workflows/ci_lint.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,33 @@ jobs:
3535

3636
- name: Run yamllint on files modified by the PR
3737
run: |
38+
exit_code=0
3839
for file in $(cat filenames.txt); do
40+
if [[ ! -f "$file" ]]; then
41+
continue
42+
fi
3943
echo "Running yamllint on $file..."
40-
yamllint -c .yamllint "$file"
44+
if grep -qP '\{\{[%{#]' "$file"; then
45+
# File contains Jinja2 constructs — strip them before linting.
46+
# yamllint -s exits: 0 = clean, 1 = errors, 2 = warnings only.
47+
output=$(python3 utils/strip_jinja_for_yamllint.py "$file" \
48+
| yamllint -s -c .yamllint - 2>&1)
49+
rc=$?
50+
# Show all output (warnings and errors), replacing "stdin"
51+
# with the actual filename since yamllint reads from a pipe.
52+
if [ -n "$output" ]; then
53+
echo "$output" | sed "s|^stdin|$file|"
54+
fi
55+
# Fail only on errors (exit code 1), not warnings (exit code 2).
56+
if [ "$rc" -eq 1 ]; then
57+
exit_code=1
58+
fi
59+
else
60+
yamllint -s -c .yamllint "$file"
61+
rc=$?
62+
if [ "$rc" -eq 1 ]; then
63+
exit_code=1
64+
fi
65+
fi
4166
done
67+
exit $exit_code

utils/strip_jinja_for_yamllint.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
"""Strip Jinja2 constructs from YAML files to make them yamllint-safe.
3+
4+
This project uses Jinja2 templating ({{% %}}, {{{ }}}, {{# #}}) inside YAML
5+
files. yamllint cannot parse these constructs, so this script removes them
6+
while preserving line numbers (replaced regions become blank lines) so that
7+
yamllint error messages still point to the correct source lines.
8+
9+
Usage:
10+
python3 utils/strip_jinja_for_yamllint.py FILE
11+
12+
The cleaned content is written to stdout.
13+
"""
14+
15+
import re
16+
import sys
17+
18+
19+
def _replace_with_blanks(match):
20+
"""Replace a match with the same number of newlines to preserve line numbers."""
21+
return "\n" * match.group(0).count("\n")
22+
23+
24+
def strip_jinja(content):
25+
# 1. Remove whole-line Jinja block tags: {{% ... %}} on their own line(s).
26+
# Match the entire line (including leading whitespace) to avoid leaving
27+
# trailing spaces behind.
28+
content = re.sub(
29+
r"^[ \t]*\{\{%.*?%\}\}[ \t]*$",
30+
_replace_with_blanks,
31+
content,
32+
flags=re.MULTILINE | re.DOTALL,
33+
)
34+
# Remove any remaining inline block tags (rare).
35+
content = re.sub(r"\{\{%.*?%\}\}", _replace_with_blanks, content, flags=re.DOTALL)
36+
37+
# 2. Remove whole-line Jinja comments: {{# ... #}}
38+
content = re.sub(
39+
r"^[ \t]*\{\{#.*?#\}\}[ \t]*$",
40+
_replace_with_blanks,
41+
content,
42+
flags=re.MULTILINE | re.DOTALL,
43+
)
44+
# Remove any remaining inline comments.
45+
content = re.sub(r"\{\{#.*?#\}\}", "", content, flags=re.DOTALL)
46+
47+
# 3a. Standalone Jinja expressions occupying entire lines — these typically
48+
# expand to top-level YAML keys (e.g. ocil/ocil_clause macros) or
49+
# Ansible tasks, so replacing them with a placeholder string would
50+
# produce invalid YAML. Replace with a YAML-safe comment placeholder
51+
# to avoid trailing whitespace on otherwise blank lines.
52+
content = re.sub(
53+
r"^([ \t]*)\{\{\{.*?\}\}\}[ \t]*$",
54+
lambda m: m.group(1) + "# jinja" + "\n" * (m.group(0).count("\n") - 1)
55+
if m.group(0).count("\n") > 0
56+
else m.group(1) + "# jinja",
57+
content,
58+
flags=re.MULTILINE | re.DOTALL,
59+
)
60+
61+
# 3b. Inline Jinja expressions embedded inside a YAML value — replace
62+
# with a short placeholder so the surrounding YAML stays valid.
63+
content = re.sub(r"\{\{\{.*?\}\}\}", "JINJA_EXPRESSION", content)
64+
65+
return content
66+
67+
68+
def main():
69+
if len(sys.argv) != 2:
70+
print(f"Usage: {sys.argv[0]} FILE", file=sys.stderr)
71+
sys.exit(2)
72+
73+
with open(sys.argv[1]) as f:
74+
sys.stdout.write(strip_jinja(f.read()))
75+
76+
77+
if __name__ == "__main__":
78+
main()

0 commit comments

Comments
 (0)