/
/
/
1"""
2Shared helpers for baseline-aware Music Assistant lint checks.
3
4Some rules (oversized provider icons, naive ``datetime`` usage, blocking IO in async code) already
5have pre-existing violations in the tree. Rather than block the introduction of the check on a
6repo-wide cleanup, each such check records the *current* violations in a baseline file and only
7fails on **new** regressions. The follow-up cleanup then shrinks the baseline over time.
8
9A baseline file is a sorted list of tab-separated ``<repo-relative-path>`` and ``<count>`` lines
10(``count`` = the number of currently-grandfathered violations in that file); ``#`` comment and blank
11lines are ignored, and a bare path without a tab implies a count of ``1``. Regenerate one with the
12owning check's ``--update-baseline`` flag.
13"""
14
15from __future__ import annotations
16
17from collections.abc import Mapping
18from pathlib import Path
19
20
21def load_baseline(path: Path) -> dict[str, int]:
22 """
23 Return the allowed per-file violation counts recorded in a baseline file.
24
25 :param path: Path to the baseline file. A missing file yields an empty mapping.
26 """
27 counts: dict[str, int] = {}
28 if not path.is_file():
29 return counts
30 for raw_line in path.read_text(encoding="utf-8").splitlines():
31 line = raw_line.strip()
32 if not line or line.startswith("#"):
33 continue
34 relpath, separator, count = line.rpartition("\t")
35 if separator:
36 counts[relpath] = int(count)
37 else:
38 counts[line] = 1
39 return counts
40
41
42def render_baseline(counts: Mapping[str, int], header: str) -> str:
43 """
44 Render a baseline file body from per-file violation counts.
45
46 :param counts: Mapping of repo-relative path to its grandfathered violation count.
47 :param header: Explanatory text written as leading ``#`` comment lines.
48 """
49 lines = [f"# {comment_line}" for comment_line in header.strip().splitlines()]
50 lines.extend(f"{path}\t{counts[path]}" for path in sorted(counts) if counts[path] > 0)
51 return "\n".join(lines) + "\n"
52
53
54def diff_baseline(
55 current: Mapping[str, int], baseline: Mapping[str, int]
56) -> tuple[dict[str, int], dict[str, int]]:
57 """
58 Compare current violation counts against the baseline.
59
60 :param current: Mapping of repo-relative path to its current violation count.
61 :param baseline: Mapping of repo-relative path to its grandfathered violation count.
62 :return: ``(regressions, improvements)`` where ``regressions`` maps each path whose current
63 count exceeds its baseline to that current count, and ``improvements`` maps each path whose
64 current count dropped below its baseline to that (now stale) baseline count.
65 """
66 regressions = {path: count for path, count in current.items() if count > baseline.get(path, 0)}
67 improvements = {
68 path: allowed for path, allowed in baseline.items() if current.get(path, 0) < allowed
69 }
70 return regressions, improvements
71