diff options
author | Krzysztof Jagiełło <me@kjagiello.com> | 2022-04-08 15:27:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-08 16:27:33 +0300 |
commit | d2d88da71cf8a67acdaccf48dc249f6e3567fafc (patch) | |
tree | 503a3a66c57a8c49d56df5867aaa27cbf12a44fa | |
parent | 8ef344d9bfabee9bb92d1a87da1f238feef2a380 (diff) | |
download | babel-d2d88da71cf8a67acdaccf48dc249f6e3567fafc.tar.gz |
Provide a way of checking if the catalogs are up-to-date (#831)
-rw-r--r-- | babel/messages/catalog.py | 23 | ||||
-rw-r--r-- | babel/messages/frontend.py | 39 | ||||
-rw-r--r-- | tests/messages/test_frontend.py | 60 |
3 files changed, 118 insertions, 4 deletions
diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index f9e377d..228b10b 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -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) diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index 36f80f1..6e3686e 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -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. diff --git a/tests/messages/test_frontend.py b/tests/messages/test_frontend.py index 40afab7..54a371f 100644 --- a/tests/messages/test_frontend.py +++ b/tests/messages/test_frontend.py @@ -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") |