]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Provide a way of checking if the catalogs are up-to-date (#831)
authorKrzysztof Jagiełło <me@kjagiello.com>
Fri, 8 Apr 2022 13:27:33 +0000 (15:27 +0200)
committerGitHub <noreply@github.com>
Fri, 8 Apr 2022 13:27:33 +0000 (16:27 +0300)
babel/messages/catalog.py
babel/messages/frontend.py
tests/messages/test_frontend.py

index f9e377dc054b054f493c459cf63529167c56d724..228b10b710a0ec651efae87213d9cb870d5b4927 100644 (file)
@@ -139,6 +139,13 @@ class Message(object):
     def __ne__(self, other):
         return self.__cmp__(other) != 0
 
+    def is_identical(self, other):
+        """Checks whether messages are identical, taking into account all
+        properties.
+        """
+        assert isinstance(other, Message)
+        return self.__dict__ == other.__dict__
+
     def clone(self):
         return Message(*map(copy, (self.id, self.string, self.locations,
                                    self.flags, self.auto_comments,
@@ -837,3 +844,19 @@ class Catalog(object):
         if context is not None:
             key = (key, context)
         return key
+
+    def is_identical(self, other):
+        """Checks if catalogs are identical, taking into account messages and
+        headers.
+        """
+        assert isinstance(other, Catalog)
+        for key in self._messages.keys() | other._messages.keys():
+            message_1 = self.get(key)
+            message_2 = other.get(key)
+            if (
+                message_1 is None
+                or message_2 is None
+                or not message_1.is_identical(message_2)
+            ):
+                return False
+        return dict(self.mime_headers) == dict(other.mime_headers)
index 36f80f1c8d96955543c165f9f9441dcb0eb7e3d4..6e3686ecc0a786c44b73a8c3e20815fee77053b3 100644 (file)
@@ -40,14 +40,15 @@ try:
     distutils_log = log  # "distutils.log → (no replacement yet)"
 
     try:
-        from setuptools.errors import OptionError, SetupError
+        from setuptools.errors import OptionError, SetupError, BaseError
     except ImportError:  # Error aliases only added in setuptools 59 (2021-11).
-        OptionError = SetupError = Exception
+        OptionError = SetupError = BaseError = Exception
 
 except ImportError:
     from distutils import log as distutils_log
     from distutils.cmd import Command as _Command
-    from distutils.errors import OptionError as OptionError, DistutilsSetupError as SetupError
+    from distutils.errors import OptionError as OptionError, DistutilsSetupError as SetupError, DistutilsError as BaseError
+
 
 
 def listify_value(arg, split=None):
@@ -713,10 +714,15 @@ class update_catalog(Command):
          'update target header comment'),
         ('previous', None,
          'keep previous msgids of translated messages'),
+        ('check=', None,
+         'don\'t update the catalog, just return the status. Return code 0 '
+         'means nothing would change. Return code 1 means that the catalog '
+         'would be updated'),
     ]
     boolean_options = [
         'omit-header', 'no-wrap', 'ignore-obsolete', 'init-missing',
         'no-fuzzy-matching', 'previous', 'update-header-comment',
+        'check',
     ]
 
     def initialize_options(self):
@@ -733,6 +739,7 @@ class update_catalog(Command):
         self.no_fuzzy_matching = False
         self.update_header_comment = False
         self.previous = False
+        self.check = False
 
     def finalize_options(self):
         if not self.input_file:
@@ -766,6 +773,7 @@ class update_catalog(Command):
             self.previous = False
 
     def run(self):
+        check_status = {}
         po_files = []
         if not self.output_file:
             if self.locale:
@@ -795,6 +803,9 @@ class update_catalog(Command):
 
         for locale, filename in po_files:
             if self.init_missing and not os.path.exists(filename):
+                if self.check:
+                    check_status[filename] = False
+                    continue
                 self.log.info(
                     'creating catalog %s based on %s', filename, self.input_file
                 )
@@ -833,6 +844,16 @@ class update_catalog(Command):
                 os.remove(tmpname)
                 raise
 
+            if self.check:
+                with open(filename, "rb") as origfile:
+                    original_catalog = read_po(origfile)
+                with open(tmpname, "rb") as newfile:
+                    updated_catalog = read_po(newfile)
+                updated_catalog.revision_date = original_catalog.revision_date
+                check_status[filename] = updated_catalog.is_identical(original_catalog)
+                os.remove(tmpname)
+                continue
+
             try:
                 os.rename(tmpname, filename)
             except OSError:
@@ -845,6 +866,18 @@ class update_catalog(Command):
                 shutil.copy(tmpname, filename)
                 os.remove(tmpname)
 
+        if self.check:
+            for filename, up_to_date in check_status.items():
+                if up_to_date:
+                    self.log.info('Catalog %s is up to date.', filename)
+                else:
+                    self.log.warning('Catalog %s is out of date.', filename)
+            if not all(check_status.values()):
+                raise BaseError("Some catalogs are out of date.")
+            else:
+                self.log.info("All the catalogs are up-to-date.")
+            return
+
 
 class CommandLineInterface(object):
     """Command-line interface.
index 40afab7f9a8d3b95be9fc47640fe01c1fbb0bd96..54a371f4966b7586d5c906a6ae4271d15eb5662e 100644 (file)
@@ -27,7 +27,7 @@ import pytest
 from babel import __version__ as VERSION
 from babel.dates import format_datetime
 from babel.messages import frontend, Catalog
-from babel.messages.frontend import CommandLineInterface, extract_messages, update_catalog, OptionError
+from babel.messages.frontend import CommandLineInterface, extract_messages, update_catalog, OptionError, BaseError
 from babel.util import LOCALTZ
 from babel.messages.pofile import read_po, write_po
 
@@ -1214,6 +1214,64 @@ compiling catalog %s to %s
             catalog = read_po(infp)
             assert len(catalog) == 4  # Catalog was updated
 
+    def test_check(self):
+        template = Catalog()
+        template.add("1")
+        template.add("2")
+        template.add("3")
+        tmpl_file = os.path.join(i18n_dir, 'temp-template.pot')
+        with open(tmpl_file, "wb") as outfp:
+            write_po(outfp, template)
+        po_file = os.path.join(i18n_dir, 'temp1.po')
+        self.cli.run(sys.argv + ['init',
+                                 '-l', 'fi_FI',
+                                 '-o', po_file,
+                                 '-i', tmpl_file
+                                 ])
+
+        # Update the catalog file
+        self.cli.run(sys.argv + ['update',
+                                 '-l', 'fi_FI',
+                                 '-o', po_file,
+                                 '-i', tmpl_file])
+
+        # Run a check without introducing any changes to the template
+        self.cli.run(sys.argv + ['update',
+                                 '--check',
+                                 '-l', 'fi_FI',
+                                 '-o', po_file,
+                                 '-i', tmpl_file])
+
+        # Add a new entry and expect the check to fail
+        template.add("4")
+        with open(tmpl_file, "wb") as outfp:
+            write_po(outfp, template)
+
+        with self.assertRaises(BaseError):
+            self.cli.run(sys.argv + ['update',
+                                     '--check',
+                                     '-l', 'fi_FI',
+                                     '-o', po_file,
+                                     '-i', tmpl_file])
+
+        # Write the latest changes to the po-file
+        self.cli.run(sys.argv + ['update',
+                                 '-l', 'fi_FI',
+                                 '-o', po_file,
+                                 '-i', tmpl_file])
+
+        # Update an entry and expect the check to fail
+        template.add("4", locations=[("foo.py", 1)])
+        with open(tmpl_file, "wb") as outfp:
+            write_po(outfp, template)
+
+        with self.assertRaises(BaseError):
+            self.cli.run(sys.argv + ['update',
+                                     '--check',
+                                     '-l', 'fi_FI',
+                                     '-o', po_file,
+                                     '-i', tmpl_file])
+
     def test_update_init_missing(self):
         template = Catalog()
         template.add("1")