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,
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)
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):
'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):
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:
self.previous = False
def run(self):
+ check_status = {}
po_files = []
if not self.output_file:
if self.locale:
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
)
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:
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.
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
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")