diff --git a/commit_check/rules_catalog.py b/commit_check/rules_catalog.py index 29717b2e..93faf529 100644 --- a/commit_check/rules_catalog.py +++ b/commit_check/rules_catalog.py @@ -24,6 +24,22 @@ RULES_DOCS_URL = "https://commit-check.com/rules/" +def display_name(check: str) -> str: + """Human-readable form of a check name, e.g. ``subject-imperative``. + + Config files and the JSON output carry the snake_case key, because that is + what a reader sets in ``cchk.toml`` and what a consumer maps back to an + option. Text written for a person uses the kebab-case form instead: it is + how the rules reference titles each rule, so a name printed to a terminal + can be searched for there verbatim. + + Every text surface goes through here so the two forms cannot drift apart + again — the compact output once printed the config key while the default + output printed this one. + """ + return check.replace("_", "-") + + @dataclass(frozen=True) class RuleCatalogEntry: check: str @@ -35,7 +51,7 @@ class RuleCatalogEntry: @property def name(self) -> str: """Human-readable rule name, e.g. ``subject-imperative``.""" - return self.check.replace("_", "-") + return display_name(self.check) @property def docs_url(self) -> str | None: diff --git a/commit_check/util.py b/commit_check/util.py index 8053d8c3..6de1af03 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -11,6 +11,7 @@ import sys from subprocess import CalledProcessError from commit_check import RED, GREEN, YELLOW, RESET_COLOR +from commit_check.rules_catalog import display_name def _print_failure( @@ -23,7 +24,8 @@ def _print_failure( rule_id = check.get("rule_id", "") if compact: compact_value = actual.splitlines()[0] if actual else actual - label = f"{rule_id} {check['check']}" if rule_id else check["check"] + name = display_name(check["check"]) + label = f"{rule_id} {name}" if rule_id else name print(f"[FAIL] {label}: {compact_value}") return if not no_banner and not print_error_header.has_been_called: @@ -364,9 +366,7 @@ def print_error_message( :returns: Give error messages to user """ - # The kebab-case form is what the rules reference uses as its headings, so - # the name printed here can be searched for there verbatim. - name = check_type.replace("_", "-") + name = display_name(check_type) label = rule_id if rule_id and docs_url and supports_hyperlinks(): label = hyperlink(rule_id, docs_url) diff --git a/tests/main_test.py b/tests/main_test.py index 9cc43b41..d1b5f59c 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -728,6 +728,35 @@ def test_compact_shows_one_line_per_failure(self, mocker, capsys, monkeypatch): assert all(line.startswith("[FAIL]") for line in lines) assert len(lines) >= 1 + @pytest.mark.benchmark + def test_compact_names_checks_the_way_the_default_output_does( + self, mocker, capsys, monkeypatch + ): + """--compact prints the kebab-case name, not the config key. + + Both are text written for a person, so they have to agree. This + assertion is the one the suite was missing: --compact shipped + printing ``subject_imperative`` while the default output printed + ``subject-imperative``, and nothing here noticed. + """ + mocker.patch("sys.stdin.isatty", return_value=False) + mocker.patch("sys.stdin.read", return_value="docs: revamped the profile\n") + mocker.patch("commit_check.engine.get_commit_info", return_value="test-author") + + # A check whose name contains an underscore, so the two forms differ. + monkeypatch.setattr( + "sys.argv", [CMD, "-m", "--compact", "--subject-imperative=true"] + ) + main() + + out, _ = capsys.readouterr() + assert "CC003 subject-imperative:" in out, ( + f"--compact should print the display name: {out!r}" + ) + assert "subject_imperative" not in out, ( + f"--compact printed the config key: {out!r}" + ) + @pytest.mark.benchmark def test_compact_no_suggestions(self, mocker, capsys, monkeypatch): """--compact output must not include 'Suggest:' lines."""