From 67cc5462e7b96b176dbd0544e308b23b8ce3c937 Mon Sep 17 00:00:00 2001 From: Cristian Consonni Date: Wed, 19 Aug 2026 02:03:39 +0200 Subject: [PATCH 1/3] Add option --exclude to audit-translations --- PythonScripts/audit_translations/README.md | 11 ++++++- PythonScripts/audit_translations/auditor.py | 34 +++++++++++++-------- PythonScripts/audit_translations/cli.py | 14 ++++++++- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/PythonScripts/audit_translations/README.md b/PythonScripts/audit_translations/README.md index 8aedf126..fd399e41 100644 --- a/PythonScripts/audit_translations/README.md +++ b/PythonScripts/audit_translations/README.md @@ -77,6 +77,7 @@ uv run --project PythonScripts audit-translations --list * Region variants are shown as `lang-region` (e.g., `zz-aa`) based on subdirectories under `Rules/Languages/`. * `--source`: Sets the source/reference language. Defaults to `en`. * `--file`: Audits a single specific file instead of the whole directory. +* `--exclude`: Exclude one or more files from the audit. * `--rules-dir`: Override the Rules/Languages directory path. * `--only`: Filter issue types (comma-separated): `missing`, `untranslated`, `extra`, `diffs`, `all`. * `--verbose`: Show detailed output including source/target snippets for rule differences. @@ -100,9 +101,17 @@ uv run audit-translations de # Compare Norwegian Bokmal against Swedish instead of English uv run audit-translations nb --source sv -# Audit only a specific file +# Audit only a specific file (note: --file is incompatible with --exclude) +uv run audit-translations es --file ClearSpeak_Rules.yaml uv run audit-translations es --file SharedRules/default.yaml +# Exclude a list of files from the audit, note that if you use this option before specifying the +# target language you'll need to use the option terminator (--). +# (note: --exclude is incompatible with --file) +uv run audit-translations es --exclude unicode-full.yaml +uv run audit-translations es --exclude unicode-full.yaml unicode.yaml +uv run audit-translations --exclude unicode-full.yaml -- es + # Audit a regional variant (merges Rules/Languages/de and Rules/Languages/de/CH) uv run audit-translations de-CH diff --git a/PythonScripts/audit_translations/auditor.py b/PythonScripts/audit_translations/auditor.py index b67cbf91..37ec44f1 100644 --- a/PythonScripts/audit_translations/auditor.py +++ b/PythonScripts/audit_translations/auditor.py @@ -46,24 +46,31 @@ def is_definitions_file(file_path: str | Path) -> bool: return Path(file_path).name == "definitions.yaml" -def get_yaml_files(lang_dir: Path, region_dir: Path | None = None) -> list[Path]: +def get_yaml_files(lang_dir: Path, region_dir: Path | None = None, excluded_files: list[str] | None = None) -> list[Path]: """Get all YAML files to audit for a language, including region overrides.""" files: set[Path] = set() - def collect_from(directory: Path, root: Path) -> None: + def collect_from(directory: Path, root: Path, excluded: list[str] | None) -> None: if not directory.exists(): return - for f in directory.glob("*.yaml"): - if f.name != "prefs.yaml": - files.add(f.relative_to(root)) - shared_dir = directory / "SharedRules" - if shared_dir.exists(): - for f in shared_dir.glob("*.yaml"): - files.add(f.relative_to(root)) - - collect_from(lang_dir, lang_dir) + + excluded_paths = {Path(path) for path in excluded or []} + + candidates = {f for f in directory.glob("*.yaml") if f.name != "prefs.yaml"} + candidates.update((directory / "SharedRules").glob("*.yaml")) + + relative_candidates = {f.relative_to(directory): f for f in candidates} + + for path in excluded_paths - relative_candidates.keys(): + console.print(f"\n[yellow]⚠ Warning:[/] File to exclude {path} does not exist.") + + files.update( + f.relative_to(root) for relative_path, f in relative_candidates.items() if relative_path not in excluded_paths + ) + + collect_from(lang_dir, lang_dir, excluded_files) if region_dir: - collect_from(region_dir, region_dir) + collect_from(region_dir, region_dir, excluded_files) return sorted(files) @@ -215,6 +222,7 @@ def merge_definitions( def audit_language( language: str, specific_file: str | None = None, + excluded_files: list[str] | None = None, rules_dir: str | None = None, issue_filter: set[str] | None = None, verbose: bool = False, @@ -248,7 +256,7 @@ def audit_language( raise AuditError(f"Target region directory not found: {translated_region_dir}") # Get list of files to audit - files = [specific_file] if specific_file else get_yaml_files(source_dir, source_region_dir) + files = [specific_file] if specific_file else get_yaml_files(source_dir, source_region_dir, excluded_files) print_audit_header(language, len(files), source_language) diff --git a/PythonScripts/audit_translations/cli.py b/PythonScripts/audit_translations/cli.py index 6916a636..5bdf30e6 100644 --- a/PythonScripts/audit_translations/cli.py +++ b/PythonScripts/audit_translations/cli.py @@ -30,7 +30,18 @@ def main() -> None: parser.add_argument("language", nargs="?", help="Language code to audit (e.g., 'es', 'de', 'fi')") parser.add_argument("--source", default="en", help="Source/reference language code (default: 'en')") - parser.add_argument("--file", dest="specific_file", help="Audit only a specific file (e.g., 'SharedRules/default.yaml')") + file_group = parser.add_mutually_exclusive_group() + file_group.add_argument( + "--file", + dest="specific_file", + help="Audit only a specific file (e.g., 'SharedRules/default.yaml')", + ) + file_group.add_argument( + "--exclude", + nargs="+", + dest="excluded_files", + help="Exclude a list of files from the audit.", + ) parser.add_argument("--list", action="store_true", help="List available languages") parser.add_argument("--rules-dir", help="Override Rules/Languages directory path") parser.add_argument( @@ -67,6 +78,7 @@ def main() -> None: audit_language( args.language, args.specific_file, + args.excluded_files, args.rules_dir, issue_filter, args.verbose, From 3d267a07461db2e597f03ee22d1e936c45e1e522 Mon Sep 17 00:00:00 2001 From: Cristian Consonni Date: Wed, 19 Aug 2026 03:21:14 +0200 Subject: [PATCH 2/3] Add tests for --exclude option --- .../tests/test_cli_end_to_end.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/PythonScripts/audit_translations/tests/test_cli_end_to_end.py b/PythonScripts/audit_translations/tests/test_cli_end_to_end.py index 7876bc0d..4ef5ee9d 100644 --- a/PythonScripts/audit_translations/tests/test_cli_end_to_end.py +++ b/PythonScripts/audit_translations/tests/test_cli_end_to_end.py @@ -127,6 +127,79 @@ def test_cli_main_rich_only_filters_issue_groups(capsys, monkeypatch) -> None: assert "Structure Differences" not in output +@pytest.mark.parametrize( + ("excluded_file", "expected_file", "expected_rule", "unexpected_rule"), + [ + ( + "default.yaml", + "SharedRules/default.yaml", + "shared-only", + "root-only", + ), + ( + "SharedRules/default.yaml", + "default.yaml", + "root-only", + "shared-only", + ), + ], +) +def test_cli_main_exclude_uses_relative_paths( + tmp_path, + capsys, + monkeypatch, + excluded_file, + expected_file, + expected_rule, + unexpected_rule, +) -> None: + """Ensure --exclude distinguishes root files from SharedRules files.""" + rules_dir = tmp_path / "Rules" / "Languages" + source_dir = rules_dir / "en" + target_dir = rules_dir / "de" + + (source_dir / "SharedRules").mkdir(parents=True) + (target_dir / "SharedRules").mkdir(parents=True) + + (source_dir / "default.yaml").write_text( + '- name: root-only\n tag: mo\n match: ".//m:mi"\n replace:\n - t: "root"\n', + encoding="utf-8", + ) + (source_dir / "SharedRules" / "default.yaml").write_text( + '- name: shared-only\n tag: mo\n match: ".//m:mi"\n replace:\n - t: "shared"\n', + encoding="utf-8", + ) + + target_rule = '- name: target-only\n tag: mo\n match: ".//m:mi"\n replace:\n - t: "target"\n' + (target_dir / "default.yaml").write_text( + target_rule, + encoding="utf-8", + ) + (target_dir / "SharedRules" / "default.yaml").write_text( + target_rule, + encoding="utf-8", + ) + + args = [ + "de", + "--rules-dir", + str(rules_dir), + "--exclude", + excluded_file, + "--only", + "missing", + ] + monkeypatch.setattr(sys, "argv", ["audit_translations", *args]) + + audit_cli.main() + output = strip_ansi(capsys.readouterr().out) + + assert "Files to check: 1" in output + assert expected_file in output + assert expected_rule in output + assert unexpected_rule not in output + + def test_cli_main_accepts_source_language(capsys, monkeypatch) -> None: """ Ensure --source changes the reference language without changing target semantics. From 0f4791ccd674d4c0ea78a1576140e09ae1c33315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Gro=C3=9F?= Date: Wed, 19 Aug 2026 08:39:00 +0200 Subject: [PATCH 3/3] Add test case IDs for CLI main exclude tests --- PythonScripts/audit_translations/tests/test_cli_end_to_end.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PythonScripts/audit_translations/tests/test_cli_end_to_end.py b/PythonScripts/audit_translations/tests/test_cli_end_to_end.py index 4ef5ee9d..40cf3f8b 100644 --- a/PythonScripts/audit_translations/tests/test_cli_end_to_end.py +++ b/PythonScripts/audit_translations/tests/test_cli_end_to_end.py @@ -143,6 +143,7 @@ def test_cli_main_rich_only_filters_issue_groups(capsys, monkeypatch) -> None: "shared-only", ), ], + ids=["exclude-root", "exclude-shared-rules"], ) def test_cli_main_exclude_uses_relative_paths( tmp_path,