Coverage for /builds/svesch/cikit/src/cikit/macros/ruff.py: 100%
54 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
1# SPDX-License-Identifier: GPL-3.0-or-later
2"""Ruff macro helpers for cikit.
4This module executes Ruff lint checks, stores lint metrics in cikit data,
5generates badges and converts JSON lint reports into generated Sphinx
6RST pages.
8Author:
9 Sven Schlender
10Date:
11 2026-05-28
12"""
14import json
15import logging
16from pathlib import Path
17from tempfile import TemporaryDirectory
19from cikit.badge import write_badge
20from cikit.command import Command, run_command
21from cikit.artifacts import Artifact, ArtifactType
22from cikit.env import write_file
23from cikit.errors import CiToolsError
24from cikit.lint_to_rst import LintIssue, read_lint_issues, write_lint_rst
25from cikit.rst_tools import RstDocument, timeline_artifacts_block
27from .common import resolve_name_label
30LOGGER = logging.getLogger(__name__)
31RST_TOC_DEPTH = 2
34def get_ruff_check_file_path(env) -> Path:
35 """Return the registered Ruff JSON report path.
37 Args:
38 env: Resolved CI run environment.
40 Returns:
41 Absolute path to the Ruff JSON report artifact.
42 """
43 return env.get_run_artifact("ruff.json", ArtifactType.LINT).path
46def ruff(env, paths):
47 """Run Ruff lint checks.
49 The generated JSON report is registered as a lint artifact and the number
50 of violations is stored in the current run data context.
52 Args:
53 env: Resolved CI run environment.
54 paths: Paths passed to Ruff.
56 Raises:
57 CiToolsError: If a lint path does not exist.
58 """
59 for path in paths:
60 if not Path(path).exists():
61 raise CiToolsError(f"Path '{path}' to lint does not exist!")
63 cmd = Command("ruff", use_exec_tool=True)
64 cmd.add_subcommand("check", ["check", "--output-format=json", "--exit-zero"])
65 argv = env.argv(cmd, "check", extra_args=paths)
66 result = run_command(argv, check_exit_code=True, live_output=True, cwd=env["PROJECT_ROOT"])
68 violations = json.loads(result.stdout or "[]")
69 ruff_count = len(violations)
71 with TemporaryDirectory() as temp_dir:
72 json_file = Path(temp_dir) / "ruff.json"
73 write_file(json_file, result.stdout or "")
74 env.copy_artifact(json_file, ArtifactType.LINT)
76 env.data_set("violations", ruff_count)
79def gitlab_code_quality(env, paths):
80 """Generate GitLab Code Quality output for Ruff.
82 Args:
83 env: Resolved CI run environment.
84 paths: Paths passed to Ruff.
85 """
86 with TemporaryDirectory() as temp_dir:
87 json_file = Path(temp_dir) / "ruff-codequality.json"
89 cmd = Command("ruff", use_exec_tool=True)
90 cmd.add_subcommand("check_gitlab", ["check", "--output-format=gitlab", "-o", str(json_file),
91 "--exit-zero"])
92 argv = env.argv(cmd, "check_gitlab", extra_args=paths)
93 run_command(argv, check_exit_code=True, live_output=True, cwd=env["PROJECT_ROOT"])
95 env.copy_artifact(json_file, ArtifactType.LINT)
98def badge(env, name: str = None, label: str = None) -> Artifact:
99 """Generate the Ruff violations badge.
101 Args:
102 env: Resolved CI run environment.
103 name: Basename of the badge file. If omitted, the run name is used.
104 label: Label of the badge. If omitted, the run name is used.
106 Returns:
107 Created and registered badge artifact.
108 """
109 name, label = resolve_name_label(env, name, label)
110 return write_badge(env=env, name=name, label=label, value=env.data.get("violations", 0),
111 thresholds={1: "green", 3: "yellow", 5: "orange", 10: "red", 9999: "red"})
114def _ruff_issue_from_dict(entry: dict) -> LintIssue:
115 """Convert one Ruff JSON entry into a LintIssue.
117 Args:
118 entry: Ruff JSON issue entry.
120 Returns:
121 Converted lint issue.
122 """
123 return LintIssue(code=entry.get("code") or entry.get("rule") or "unknown",
124 file_path=Path(entry.get("filename")).resolve(),
125 line=entry.get("noqa_row") or entry.get("location", {}).get("row") or 0,
126 message=entry.get("message") or "",
127 severity="warning")
130def write_rst(env, name: str = None, report_title: str = None, filter_code: str = None) -> Artifact:
131 """Create an RST report from Ruff JSON output.
133 Args:
134 env: Resolved CI run environment.
135 name: Output file basename without extension. If omitted, the run name
136 is used.
137 report_title: Report title. If omitted, it is derived from the current
138 violation count.
139 filter_code: Optional Ruff rule code filter.
141 Returns:
142 Created and registered RST artifact.
143 """
144 if name is None:
145 name = env.name
147 if report_title is None:
148 report_title = f"Ruff Report (Violations {env.data.get('violations')})"
150 issues = read_lint_issues(get_ruff_check_file_path(env), _ruff_issue_from_dict)
152 document = RstDocument(report_title, toc_depth=RST_TOC_DEPTH)
154 if not filter_code:
155 document.add_block(timeline_artifacts_block(env.get_run_artifacts(ArtifactType.TIMELINE)))
157 return write_lint_rst(env, document, issues, name, filter_code)