245 lines
8.5 KiB
Python
245 lines
8.5 KiB
Python
# 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
|