Repository: mondeja/pre-commit-po-hooks (language: python)

Issue:
Add hook to check fuzzy messages



Discussion / hints:

Fix patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index efdcfb4..46d8bd0 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 1.6.1
+current_version = 1.7.0
 
 [bumpversion:file:setup.cfg]
 
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index d550896..a4f36ef 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -10,6 +10,12 @@
   description: Checks for untranslated messages in PO files
   files: \.po$
   language: python
+- id: fuzzy-messages
+  name: fuzzy-messages
+  entry: fuzzy-messages-hook
+  description: Checks for fuzzy messages in PO files
+  files: \.po$
+  language: python
 - id: remove-django-translators
   name: remove-django-translators
   entry: lreplace-extracted-comments-hook --django-translators
diff --git a/README.md b/README.md
index bfd533f..91d5999 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,11 @@ Hooks for pre-commit useful working with PO files.
 
 ```yaml
 - repo: https://github.com/mondeja/pre-commit-po-hooks
-  rev: v1.6.1
+  rev: v1.7.0
   hooks:
     - id: obsolete-messages
     - id: untranslated-messages
+    - id: fuzzy-messages
     - id: remove-django-translators
     - id: standard-metadata
     - id: max-lines
@@ -37,6 +38,10 @@ Checks for untranslated messages printing their line numbers if found.
  to pass this check. Can be defined as a percentage of the messages translated
  appending a character `%` at the end of the value.
 
+### **`fuzzy-messages`**
+
+Checks for fuzzy messages printing their line numbers if found.
+
 ### **`lreplace-extracted-comments`**
 
 Replaces a matching string at the beginning of extracted comments.
diff --git a/hooks/fuzzy_messages.py b/hooks/fuzzy_messages.py
new file mode 100644
index 0000000..0d212d4
--- /dev/null
+++ b/hooks/fuzzy_messages.py
@@ -0,0 +1,52 @@
+"""Checks for fuzzy messages in PO files.
+
+Returns an error code if a PO file has a fuzzy message.
+"""
+
+import argparse
+import sys
+
+
+def check_fuzzy_messages(filenames, quiet=False):
+    """Warns about all fuzzy messages found in a set of PO files.
+
+    Parameters
+    ----------
+
+    filenames : list
+      Set of file names to check.
+
+    quiet : bool, optional
+      Enabled, don't print output to stderr when an fuzzy message is found.
+
+    Returns
+    -------
+
+    int: 0 if no fuzzy messages found, 1 otherwise.
+    """
+    exitcode = 0
+    for filename in filenames:
+        with open(filename) as f:
+            content_lines = f.readlines()
+
+        for i, line in enumerate(content_lines):
+            if line.startswith("#,") and "fuzzy" in line:
+                exitcode = 1
+                if not quiet:
+                    sys.stderr.write(f"Found fuzzy message at {filename}:{i + 1}\n")
+
+    return exitcode
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "filenames", nargs="*", help="Filenames to check for fuzzy messages"
+    )
+    parser.add_argument("-q", "--quiet", action="store_true", help="Supress output")
+    args = parser.parse_args()
+    return check_fuzzy_messages(args.filenames, quiet=args.quiet)
+
+
+if __name__ == "__main__":
+    exit(main())
diff --git a/setup.cfg b/setup.cfg
index 18e10bd..157d974 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = pre_commit_po_hooks
-version = 1.6.1
+version = 1.7.0
 description = pre-commit hooks for PO files
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -32,6 +32,7 @@ exclude =
 console_scripts =
     obsolete-messages-hook = hooks.obsolete_messages:main
     untranslated-messages-hook = hooks.untranslated_messages:main
+    fuzzy-messages-hook = hooks.fuzzy_messages:main
     lreplace-extracted-comments-hook = hooks.lreplace_extracted_comments:main
     check-metadata-hook = hooks.check_metadata:main
     check-entries-hook = hooks.check_entries:main


Test patch:
diff --git a/tests/test_fuzzy_messages.py b/tests/test_fuzzy_messages.py
new file mode 100644
index 0000000..528ebff
--- /dev/null
+++ b/tests/test_fuzzy_messages.py
@@ -0,0 +1,115 @@
+"""Obsolete messages hook tests."""
+
+import contextlib
+import io
+import os
+import uuid
+
+import pytest
+
+from hooks.fuzzy_messages import check_fuzzy_messages
+
+
+"""
+        (
+            [
+                (
+                    '#\nmsgid ""\nmsgstr ""\n\n#~ msgid "Obsolete "\n#~ "message"'
+                    '\n#~ msgstr "Mensaje obsoleto"\n'
+                ),
+                '#\nmsgid ""\nmsgstr ""\n\nmsgid "Hello"\nmsgstr "Hola"\n',
+                (
+                    '#\nmsgid ""\nmsgstr ""\n\n#~ msgid "Hello"\n#~ msgstr "Hola"\n'
+                    '\nmsgid "Foo"\nmsgstr "Bar"\n'
+                ),
+            ],
+            2,
+            1,
+            [5, 5],
+        ),
+        (
+            ['#\nmsgid ""\nmsgstr ""\n\nmsgid "Current"\nmsgstr "Actual"\n'],
+            0,
+            0,
+            None,
+        ),
+        """
+
+
+@pytest.mark.parametrize("quiet", (False, True), ids=("quiet=False", "quiet=True"))
+@pytest.mark.parametrize(
+    ("contents", "n_printed_errors", "expected_exitcode", "expected_line_numbers"),
+    (
+        pytest.param(
+            [
+                (
+                    '#\nmsgid ""\nmsgstr ""\n\n'
+                    '#, fuzzy\n#~ msgid "Obsolete"\n#~ msgstr "Obsoleto"\n'
+                )
+            ],
+            1,
+            1,
+            [5],
+            id="one-fuzzy-message",
+        ),
+        pytest.param(
+            [
+                (
+                    '#\nmsgid ""\nmsgstr ""\n\n'
+                    '#, fuzzy\n#~ msgid "Obsolete"\n#~ msgstr "Obsoleto"\n'
+                ),
+                (
+                    '#\nmsgid ""\nmsgstr ""\n\n'
+                    '#, fuzzy\nmsgid "Foo"\nmsgstr "Foo es"\n\n'
+                    'msgid "Bar"\nmsgstr "Bar es"\n'
+                ),
+            ],
+            2,
+            1,
+            [5, 5],
+            id="two-fuzzy-messages-in-two-files",
+        ),
+        pytest.param(
+            ['#\nmsgid ""\nmsgstr ""\n\nmsgid "Current"\nmsgstr "Actual"\n'],
+            0,
+            0,
+            None,
+            id="no-fuzzy-messages",
+        ),
+    ),
+)
+def test_check_fuzzy_messages(
+    quiet,
+    contents,
+    n_printed_errors,
+    expected_exitcode,
+    expected_line_numbers,
+    tmp_path,
+):
+    filenames = []
+    for content in contents:
+        filename = tmp_path / f"{uuid.uuid4().hex[:16]}.po"
+
+        with open(filename, "w") as f:
+            f.write(content)
+
+        filenames.append(filename)
+
+    stderr = io.StringIO()
+    with contextlib.redirect_stderr(stderr):
+        assert check_fuzzy_messages(filenames, quiet=quiet) == expected_exitcode
+
+    stderr_lines = stderr.getvalue().splitlines()
+    if quiet:
+        n_printed_errors = 0
+    assert len(stderr_lines) == n_printed_errors
+
+    if n_printed_errors:
+        assert len(stderr_lines) == len(expected_line_numbers)
+
+        for i, line in enumerate(stderr_lines):
+            line_number = int(line.split(":")[-1])
+            assert line_number == expected_line_numbers[i]
+
+    for filename in filenames:
+        os.remove(filename)