Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way of checking if the catalogs are up-to-date #831

Merged
merged 2 commits into from Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions babel/messages/catalog.py
Expand Up @@ -139,6 +139,13 @@ def __eq__(self, other):
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,
Expand Down Expand Up @@ -837,3 +844,19 @@ def _key_for(self, id, context=None):
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)
39 changes: 36 additions & 3 deletions babel/messages/frontend.py
Expand Up @@ -41,14 +41,15 @@
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):
Expand Down Expand Up @@ -714,10 +715,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):
Expand All @@ -734,6 +740,7 @@ def initialize_options(self):
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:
Expand Down Expand Up @@ -767,6 +774,7 @@ def finalize_options(self):
self.previous = False

def run(self):
check_status = {}
po_files = []
if not self.output_file:
if self.locale:
Expand Down Expand Up @@ -796,6 +804,9 @@ def run(self):

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
)
Expand Down Expand Up @@ -834,6 +845,16 @@ def run(self):
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:
Expand All @@ -846,6 +867,18 @@ def run(self):
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.
Expand Down
60 changes: 59 additions & 1 deletion tests/messages/test_frontend.py
Expand Up @@ -27,7 +27,7 @@
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

Expand Down Expand Up @@ -1203,6 +1203,64 @@ def test_update(self):
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")
Expand Down