Skip to content

Commit d28cfaf

Browse files
committed
fix djlint
Signed-off-by: Kashish Mittal <kmittal@redhat.com>
1 parent a0d5db7 commit d28cfaf

3 files changed

Lines changed: 211 additions & 42 deletions

File tree

skills/rhdh-templates/scripts/validate.py

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -159,32 +159,75 @@ def check_location_yaml(repo_root: Path) -> list[dict]:
159159
return findings
160160

161161

162-
def run_djlint(skeleton_dir: Path) -> list[dict]:
163-
import shutil
162+
# djlint defaults to --extension=html; scaffolder skeletons are mostly non-HTML.
163+
SKELETON_LINT_SKIP_SUFFIXES = frozenset(
164+
{
165+
".png",
166+
".jpg",
167+
".jpeg",
168+
".gif",
169+
".webp",
170+
".ico",
171+
".svg",
172+
".pdf",
173+
".zip",
174+
".tar",
175+
".gz",
176+
".bz2",
177+
".xz",
178+
".7z",
179+
".jar",
180+
".war",
181+
".ear",
182+
".class",
183+
".so",
184+
".dll",
185+
".dylib",
186+
".exe",
187+
".bin",
188+
".dat",
189+
".woff",
190+
".woff2",
191+
".ttf",
192+
".eot",
193+
".mp3",
194+
".mp4",
195+
".avi",
196+
".mov",
197+
".wav",
198+
}
199+
)
164200

165-
if not shutil.which("djlint"):
166-
return [
167-
{
168-
"check": "nunjucks_lint",
169-
"severity": "info",
170-
"message": "djlint not installed — skipping Nunjucks lint",
171-
}
172-
]
173-
cmd = [
174-
"djlint",
175-
str(skeleton_dir),
176-
"--profile=jinja",
177-
"--lint",
178-
"--quiet",
179-
]
180-
try:
181-
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
182-
except OSError as exc:
201+
202+
def collect_skeleton_lint_targets(skeleton_dir: Path) -> tuple[set[str], list[Path]]:
203+
"""Return (extensions, extensionless files) djlint should lint under skeleton/."""
204+
extensions: set[str] = set()
205+
extensionless: list[Path] = []
206+
for path in skeleton_dir.rglob("*"):
207+
if not path.is_file():
208+
continue
209+
suffix = path.suffix.lower()
210+
if suffix in SKELETON_LINT_SKIP_SUFFIXES:
211+
continue
212+
try:
213+
path.read_text(encoding="utf-8")
214+
except (UnicodeDecodeError, OSError):
215+
continue
216+
if suffix:
217+
extensions.add(suffix.lstrip("."))
218+
else:
219+
extensionless.append(path)
220+
return extensions, sorted(extensionless)
221+
222+
223+
def _parse_djlint_output(proc: subprocess.CompletedProcess) -> list[dict]:
224+
combined = f"{proc.stdout}\n{proc.stderr}"
225+
if "No files to check" in combined:
183226
return [
184227
{
185228
"check": "nunjucks_lint",
186-
"severity": "info",
187-
"message": f"djlint skipped: {exc}",
229+
"severity": "warning",
230+
"message": "djlint found no files to check for this target",
188231
}
189232
]
190233
if proc.returncode == 0:
@@ -212,6 +255,75 @@ def run_djlint(skeleton_dir: Path) -> list[dict]:
212255
return findings
213256

214257

258+
def _run_djlint_cmd(cmd: list[str]) -> list[dict]:
259+
try:
260+
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
261+
except OSError as exc:
262+
return [
263+
{
264+
"check": "nunjucks_lint",
265+
"severity": "info",
266+
"message": f"djlint skipped: {exc}",
267+
}
268+
]
269+
return _parse_djlint_output(proc)
270+
271+
272+
def run_djlint(skeleton_dir: Path) -> list[dict]:
273+
import shutil
274+
275+
if not shutil.which("djlint"):
276+
return [
277+
{
278+
"check": "nunjucks_lint",
279+
"severity": "info",
280+
"message": "djlint not installed — skipping Nunjucks lint",
281+
}
282+
]
283+
284+
extensions, extensionless = collect_skeleton_lint_targets(skeleton_dir)
285+
if not extensions and not extensionless:
286+
file_count = sum(1 for path in skeleton_dir.rglob("*") if path.is_file())
287+
if file_count:
288+
return [
289+
{
290+
"check": "nunjucks_lint",
291+
"severity": "info",
292+
"message": "No readable text skeleton files to lint",
293+
}
294+
]
295+
return [
296+
{
297+
"check": "nunjucks_lint",
298+
"severity": "info",
299+
"message": "Skeleton directory is empty — nothing to lint",
300+
}
301+
]
302+
303+
findings: list[dict] = []
304+
for ext in sorted(extensions):
305+
cmd = [
306+
"djlint",
307+
str(skeleton_dir),
308+
"-e",
309+
ext,
310+
"--profile=jinja",
311+
"--lint",
312+
"--quiet",
313+
]
314+
findings.extend(_run_djlint_cmd(cmd))
315+
for path in extensionless:
316+
cmd = [
317+
"djlint",
318+
str(path),
319+
"--profile=jinja",
320+
"--lint",
321+
"--quiet",
322+
]
323+
findings.extend(_run_djlint_cmd(cmd))
324+
return findings
325+
326+
215327
def validate_template(
216328
path: Path, *, check_repo: bool, lint_skeleton: bool, use_jsonschema: bool = True
217329
) -> dict:

tests/unit/test_rhdh_templates.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,63 @@ def test_minimal_example_passes(self) -> None:
375375
assert data["ok"] is True
376376
assert data["critical_count"] == 0
377377

378+
def test_collect_skeleton_lint_targets_includes_non_html_extensions(self) -> None:
379+
sys.path.insert(0, str(SCRIPTS))
380+
import validate
381+
382+
skeleton = BUNDLED_EXAMPLES / "java-springboot" / "skeleton"
383+
extensions, extensionless = validate.collect_skeleton_lint_targets(skeleton)
384+
assert {"java", "md", "properties", "xml", "yaml"}.issubset(extensions)
385+
assert extensionless == []
386+
387+
def test_run_djlint_uses_per_extension_targets(self, monkeypatch, tmp_path: Path) -> None:
388+
sys.path.insert(0, str(SCRIPTS))
389+
import validate
390+
391+
skeleton = tmp_path / "skeleton"
392+
skeleton.mkdir()
393+
(skeleton / "App.java").write_text("public class App {}\n", encoding="utf-8")
394+
(skeleton / "config.yaml").write_text("name: {{ values.name }}\n", encoding="utf-8")
395+
396+
calls: list[list[str]] = []
397+
398+
def fake_run(cmd, **kwargs):
399+
calls.append(cmd)
400+
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
401+
402+
monkeypatch.setattr(subprocess, "run", fake_run)
403+
monkeypatch.setattr("shutil.which", lambda _: "/usr/bin/djlint")
404+
405+
findings = validate.run_djlint(skeleton)
406+
assert findings == []
407+
assert len(calls) == 2
408+
assert all(call[0] == "djlint" for call in calls)
409+
assert all("-e" in call for call in calls)
410+
used_extensions = {call[call.index("-e") + 1] for call in calls}
411+
assert used_extensions == {"java", "yaml"}
412+
413+
def test_run_djlint_warns_when_djlint_checks_nothing(self, monkeypatch, tmp_path: Path) -> None:
414+
sys.path.insert(0, str(SCRIPTS))
415+
import validate
416+
417+
skeleton = tmp_path / "skeleton"
418+
skeleton.mkdir()
419+
(skeleton / "config.yaml").write_text("name: test\n", encoding="utf-8")
420+
421+
def fake_run(cmd, **kwargs):
422+
return subprocess.CompletedProcess(
423+
cmd, 0, stdout="", stderr="No files to check! 😢"
424+
)
425+
426+
monkeypatch.setattr(subprocess, "run", fake_run)
427+
monkeypatch.setattr("shutil.which", lambda _: "/usr/bin/djlint")
428+
429+
findings = validate.run_djlint(skeleton)
430+
assert any(
431+
f["check"] == "nunjucks_lint" and "no files to check" in f["message"].lower()
432+
for f in findings
433+
)
434+
378435
def test_detects_bad_api_version(self, tmp_path: Path) -> None:
379436
bad = tmp_path / "template.yaml"
380437
bad.write_text(

uv.lock

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)