#!/usr/bin/env python
# Copyright Mozilla Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__
import annotations
import logging
import sys
from argparse
import ArgumentParser, RawDescriptionHelpFormatter
from enum
import Enum
from importlib.util
import module_from_spec, spec_from_file_location
from os.path
import abspath
from textwrap
import dedent
from traceback
import format_exc
from moz.l10n.migrate
import all_migrations
from moz.l10n.paths.android_locale
import get_android_locale
from moz.l10n.paths.config
import L10nConfigPaths
from moz.l10n.paths.discover
import L10nDiscoverPaths, MissingSourceDirectoryError
log = logging.getLogger(__name__)
Result = Enum(
"Result", (
"OK",
"SKIP",
"UNSUPPORTED",
"FAIL"))
def cli(args: list[str] |
None =
None) ->
None:
parser = ArgumentParser(
description=dedent(
"""
Apply migrations to localization resources.
Returns
0 on success,
1 on internal error,
or 2 on argument error.
"""
),
formatter_class=RawDescriptionHelpFormatter,
)
parser.add_argument(
"-q",
"--quiet", action=
"store_true", help=
"only log input argument errors"
)
parser.add_argument(
"-v",
"--verbose", action=
"count", default=
0, help=
"increase logging verbosity"
)
parser.add_argument(
"-n",
"--dry-run",
action=
"store_true",
help=
"do not apply changes to file system",
)
parser.add_argument(
"--config", metavar=
"PATH", type=str, help=
"path to l10n.toml config file"
)
parser.add_argument(
"--root",
metavar=
"PATH",
type=str,
help=
"path to localization root, if --config is not set",
)
parser.add_argument(
"--ref",
metavar=
"PATH",
type=str,
help=
"path to localization reference root, if separate from --root",
)
parser.add_argument(
"migration", nargs=
"+", type=str, help=
"path to migration module(s)"
)
ns = parser.parse_args(args)
log_level = (
logging.ERROR
if ns.quiet
else (
logging.WARNING
if ns.verbose ==
0
else logging.INFO
if ns.verbose ==
1
else logging.DEBUG
)
)
logging.basicConfig(format=
"%(message)s", level=log_level)
try:
apply_migrations(
ns.migration,
config_path=ns.config,
discover_root=ns.root,
discover_ref_root=ns.ref,
dry_run=ns.dry_run,
)
except MissingSourceDirectoryError:
log.error(f
"Reference root not found in {ns.ref or ns.root}")
log.debug(format_exc())
sys.exit(
2)
except ValueError
as err:
log.error(str(*err.args))
log.debug(format_exc())
sys.exit(
2)
except Exception:
log.warning(format_exc())
sys.exit(
1)
def apply_migrations(
migration_paths: list[str],
*,
config_path: str |
None =
None,
discover_root: str |
None =
None,
discover_ref_root: str |
None =
None,
dry_run: bool =
False,
) ->
None:
paths: L10nConfigPaths | L10nDiscoverPaths |
None =
None
if config_path:
if discover_root:
raise ValueError(
"--config and --root must not be both set.")
paths = L10nConfigPaths(
config_path, locale_map={
"android_locale": get_android_locale}
)
elif discover_root:
if discover_ref_root
is not None:
discover_ref_root = abspath(discover_ref_root)
paths = L10nDiscoverPaths(abspath(discover_root), ref_root=discover_ref_root)
all_migrations.clear()
for idx, migration_path
in enumerate(migration_paths):
name = f
"migration_{idx}"
spec = spec_from_file_location(name, migration_path)
if spec
is None or spec.loader
is None:
raise ValueError(f
"Loading {migration_path} failed")
module = module_from_spec(spec)
sys.modules[name] = module
try:
spec.loader.exec_module(module)
except Exception
as err:
raise ValueError(f
"Loading {migration_path} failed")
from err
if not all_migrations:
raise ValueError(
"No migrations found")
for migration
in all_migrations:
if paths
is not None:
migration.set_paths(paths)
migration.apply(dry_run=dry_run)
if __name__ ==
"__main__":
cli()