Source code

Revision control

Copy as Markdown

Other Tools

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
'Commands exposed to commandlines'
import logging
from argparse import ArgumentParser
from json import dump as json_dump
import os
import sys
from compare_locales import mozpath
from compare_locales import version
from compare_locales.paths import EnumerateApp, TOMLParser, ConfigNotFound
from compare_locales.compare import compareProjects
class CompareLocales:
"""Check the localization status of gecko applications.
The first arguments are paths to the l10n.toml or ini files for the
applications, followed by the base directory of the localization repositories.
Then you pass in the list of locale codes you want to compare. If there are
no locales given, the list of locales will be taken from the l10n.toml file
or the all-locales file referenced by the application\'s l10n.ini."""
def __init__(self):
self.parser = self.get_parser()
def get_parser(self):
"""Get an ArgumentParser, with class docstring as description.
"""
parser = ArgumentParser(description=self.__doc__)
parser.add_argument('--version', action='version',
version='%(prog)s ' + version)
parser.add_argument('-v', '--verbose', action='count',
default=0, help='Make more noise')
parser.add_argument('-q', '--quiet', action='count',
default=0, help='''Show less data.
Specified once, don't show obsolete entities. Specified twice, also hide
missing entities. Specify thrice to exclude warnings and four times to
just show stats''')
parser.add_argument('--validate', action='store_true',
help='Run compare-locales against reference')
parser.add_argument('-m', '--merge',
help='''Use this directory to stage merged files,
use {ab_CD} to specify a different directory for each locale''')
parser.add_argument('config_paths', metavar='l10n.toml', nargs='+',
help='TOML or INI file for the project')
parser.add_argument('l10n_base_dir', metavar='l10n-base-dir',
help='Parent directory of localizations')
parser.add_argument('locales', nargs='*', metavar='locale-code',
help='Locale code and top-level directory of '
'each localization')
parser.add_argument('--json',
help='''Serialize to JSON. Value is the name of
the output file, pass "-" to serialize to stdout and hide the default output.
''')
parser.add_argument('-D', action='append', metavar='var=value',
default=[], dest='defines',
help='Overwrite variables in TOML files')
parser.add_argument('--full', action="store_true",
help="Compare sub-projects that are disabled")
parser.add_argument('--return-zero', action="store_true",
help="Return 0 regardless of l10n status")
parser.add_argument('--clobber-merge', action="store_true",
default=False, dest='clobber',
help="""WARNING: DATALOSS.
Use this option with care. If specified, the merge directory will
be clobbered for each module. That means, the subdirectory will
be completely removed, any files that were there are lost.
Be careful to specify the right merge directory when using this option.""")
return parser
@classmethod
def call(cls):
"""Entry_point for setuptools.
The actual command handling is done in the handle() method of the
subclasses.
"""
cmd = cls()
args = cmd.parser.parse_args()
return cmd.handle(**vars(args))
def handle(
self,
quiet=0, verbose=0,
validate=False,
merge=None,
config_paths=[], l10n_base_dir=None, locales=[],
defines=[],
full=False,
return_zero=False,
clobber=False,
json=None,
):
"""The instance part of the classmethod call.
Using keyword arguments as that is what we need for mach
commands in mozilla-central.
"""
# log as verbose or quiet as we want, warn by default
logging_level = logging.WARNING - (verbose - quiet) * 10
logging.basicConfig()
logging.getLogger().setLevel(logging_level)
config_paths, l10n_base_dir, locales = self.extract_positionals(
validate=validate,
config_paths=config_paths,
l10n_base_dir=l10n_base_dir,
locales=locales,
)
# when we compare disabled projects, we set our locales
# on all subconfigs, so deep is True.
locales_deep = full
configs = []
config_env = {
'l10n_base': l10n_base_dir
}
for define in defines:
var, _, value = define.partition('=')
config_env[var] = value
for config_path in config_paths:
if config_path.endswith('.toml'):
try:
config = TOMLParser().parse(config_path, env=config_env)
except ConfigNotFound as e:
self.parser.exit('config file %s not found' % e.filename)
if locales_deep:
if not locales:
# no explicit locales given, force all locales
config.set_locales(config.all_locales, deep=True)
else:
config.set_locales(locales, deep=True)
configs.append(config)
else:
app = EnumerateApp(config_path, l10n_base_dir)
configs.append(app.asConfig())
try:
observers = compareProjects(
configs,
locales,
l10n_base_dir,
quiet=quiet,
merge_stage=merge, clobber_merge=clobber)
except OSError as exc:
print("FAIL: " + str(exc))
self.parser.exit(2)
if json is None or json != '-':
details = observers.serializeDetails()
if details:
print(details)
if len(configs) > 1:
if details:
print('')
print("Summaries for")
for config_path in config_paths:
print(" " + config_path)
print(" and the union of these, counting each string once")
print(observers.serializeSummaries())
if json is not None:
data = [observer.toJSON() for observer in observers]
stdout = json == '-'
indent = 1 if stdout else None
fh = sys.stdout if stdout else open(json, 'w')
json_dump(data, fh, sort_keys=True, indent=indent)
if stdout:
fh.write('\n')
fh.close()
rv = 1 if not return_zero and observers.error else 0
return rv
def extract_positionals(
self,
validate=False,
config_paths=[], l10n_base_dir=None, locales=[],
):
# using nargs multiple times in argparser totally screws things
# up, repair that.
# First files are configs, then the base dir, everything else is
# locales
all_args = config_paths + [l10n_base_dir] + locales
config_paths = []
# The first directory is our l10n base, split there.
while all_args and not os.path.isdir(all_args[0]):
config_paths.append(all_args.pop(0))
if not config_paths:
self.parser.error('no configuration file given')
for cf in config_paths:
if not os.path.isfile(cf):
self.parser.error('config file %s not found' % cf)
if not all_args:
self.parser.error('l10n-base-dir not found')
l10n_base_dir = mozpath.abspath(all_args.pop(0))
if validate:
# signal validation mode by setting locale list to [None]
locales = [None]
else:
locales = all_args
return config_paths, l10n_base_dir, locales