summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrzysztof Jagiełło <me@kjagiello.com>2022-04-08 15:27:33 +0200
committerGitHub <noreply@github.com>2022-04-08 16:27:33 +0300
commitd2d88da71cf8a67acdaccf48dc249f6e3567fafc (patch)
tree503a3a66c57a8c49d56df5867aaa27cbf12a44fa
parent8ef344d9bfabee9bb92d1a87da1f238feef2a380 (diff)
downloadbabel-d2d88da71cf8a67acdaccf48dc249f6e3567fafc.tar.gz
Provide a way of checking if the catalogs are up-to-date (#831)
-rw-r--r--babel/messages/catalog.py23
-rw-r--r--babel/messages/frontend.py39
-rw-r--r--tests/messages/test_frontend.py60
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")