From 312e323d1d790c0286816eb66271e2290e07d8f5 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 14 Sep 2026 14:27:09 +0800 Subject: [PATCH] refactor: refactor cli module --- src/metaglot/__init__.py | 30 ++++--- src/metaglot/cli.py | 183 ++++++++++++++++++++++++++++++--------- 2 files changed, 162 insertions(+), 51 deletions(-) diff --git a/src/metaglot/__init__.py b/src/metaglot/__init__.py index 667064d..4014dc4 100644 --- a/src/metaglot/__init__.py +++ b/src/metaglot/__init__.py @@ -2,22 +2,32 @@ import logging import sys from . import pofile, render -from .cli import create_argparser +from .cli import parse, RcOpts, RcPotOpts, RcRenderOpts def main() -> None: logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s") - parser = create_argparser() - args = parser.parse_args() + opts = parse() try: - if args.command == "rc": - if args.rc_command == "pot": - pofile.generate(args.manifest, args.output) - elif args.rc_command == "render": - render.render(args.manifest, args.po, args.template, args.output) - except (ValueError, RuntimeError) as exc: - logging.fatal("%s", exc) + match opts.opts: + case RcOpts() as rc_opts: + match rc_opts.opts: + case RcPotOpts() as rc_pot_opts: + pofile.generate(rc_pot_opts.in_manifest, rc_pot_opts.out_pot) + case RcRenderOpts() as rc_rdr_opts: + render.render( + rc_rdr_opts.in_manifest, + rc_rdr_opts.in_po, + rc_rdr_opts.in_template, + rc_rdr_opts.out_rc, + ) + case _: + raise RuntimeError(f"unhandled rc options: {rc_opts.opts!r}") + case _: + raise RuntimeError(f"unhandled options: {opts.opts!r}") + except Exception as e: + logging.fatal("Runtime error: %s", e, exc_info=True) sys.exit(1) diff --git a/src/metaglot/cli.py b/src/metaglot/cli.py index 670ead3..963da98 100644 --- a/src/metaglot/cli.py +++ b/src/metaglot/cli.py @@ -1,68 +1,109 @@ +import enum from argparse import ArgumentParser +from dataclasses import dataclass from pathlib import Path +# region: Options and Commands -def create_argparser() -> ArgumentParser: - """ - Create MetaGlot argument parser. - :return: The built argument parser. - """ - parser = ArgumentParser( - prog="metaglot", - description="A command-line tool that unifies localization metadata for applications shipped on multiple platforms.", - ) - commands = parser.add_subparsers(dest="command", required=True) +@dataclass(frozen=True) +class RcPotOpts: + in_manifest: Path + """The path to input manifest file including translation strings.""" + out_pot: Path + """The path to output POT file.""" - rc = commands.add_parser( - "rc", - help="Commands for Windows RC resource files.", - ) - rc_commands = rc.add_subparsers(dest="rc_command", required=True) - rc_pot = rc_commands.add_parser( - "pot", - help="Generate a POT template from a string manifest.", - ) - rc_pot.add_argument( +@dataclass(frozen=True) +class RcRenderOpts: + in_manifest: Path + """The path to input manifest file including translation strings.""" + in_po: list[Path] + """The path to input PO files to read, each may be a glob pattern.""" + in_template: Path + """The path to the input user-provided Liquid for rendering.""" + out_rc: Path + """The path to the output rendered Windows RC file.""" + + +@dataclass(frozen=True) +class RcOpts: + opts: RcPotOpts | RcRenderOpts + """The option of one of subcommand.""" + + +class RcCommand(enum.StrEnum): + Pot = "pot" + Render = "render" + + +@dataclass(frozen=True) +class Opts: + """The root options.""" + + opts: RcOpts + """The option of one of subcommand.""" + + +class Command(enum.StrEnum): + Rc = "rc" + + +# endregion + +# region: Parameter Register + +_SUBCMD_DEST = "command" +_RC_SUBCMD_DEST = "rc_command" + + +_RC_POT_MANIFEST_DEST = "manifest" +_RC_POT_OUTPUT_DEST = "output" + +_RC_RDR_MANIFEST_DEST = "manifest" +_RC_RDR_PO_DEST = "po" +_RC_RDR_TEMPLATE_DEST = "template" +_RC_RDR_OUTPUT_DEST = "output" + + +def _register_rc_pot_param(parser: ArgumentParser) -> None: + parser.add_argument( "-m", "--manifest", - dest="manifest", + dest=_RC_POT_MANIFEST_DEST, action="store", type=Path, required=True, - help="The path to the string manifest file.", + help="The path to input manifest file including translation strings.", metavar="FILE", ) - rc_pot.add_argument( + parser.add_argument( "-o", "--output", - dest="output", + dest=_RC_POT_OUTPUT_DEST, action="store", type=Path, required=True, - help="The path of the POT file to write.", + help="The path to output POT file.", metavar="FILE", ) - rc_render = rc_commands.add_parser( - "render", - help="Render a user-provided template with manifest data and PO translations.", - ) - rc_render.add_argument( + +def _register_rc_render_param(parser: ArgumentParser) -> None: + parser.add_argument( "-m", "--manifest", - dest="manifest", + dest=_RC_RDR_MANIFEST_DEST, action="store", type=Path, required=True, - help="The path to the string manifest file.", + help="The path to input manifest file including translation strings.", metavar="FILE", ) - rc_render.add_argument( + parser.add_argument( "-p", "--po", - dest="po", + dest=_RC_RDR_PO_DEST, action="extend", nargs="+", type=Path, @@ -70,25 +111,85 @@ def create_argparser() -> ArgumentParser: help="The PO files to read, each may be a glob pattern.", metavar="GLOB", ) - rc_render.add_argument( + parser.add_argument( "-t", "--template", - dest="template", + dest=_RC_RDR_TEMPLATE_DEST, action="store", type=Path, required=True, - help="The path to the user-provided Liquid template.", + help="The path to the input user-provided Liquid for rendering.", metavar="FILE", ) - rc_render.add_argument( + parser.add_argument( "-o", "--output", - dest="output", + dest=_RC_RDR_OUTPUT_DEST, action="store", type=Path, required=True, - help="The path of the rendered file to write.", + help="The path to the output rendered Windows RC file.", metavar="FILE", ) - return parser + +def _register_rc_param(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_RC_SUBCMD_DEST, required=True) + pot_subcmd = cmds.add_parser( + RcCommand.Pot.value, help="Generate a POT template from given manifest." + ) + _register_rc_pot_param(pot_subcmd) + render_subcmd = cmds.add_parser( + RcCommand.Render.value, + help="Render a user-provided template with manifest data and PO translations.", + ) + _register_rc_render_param(render_subcmd) + + +def _register_param(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_SUBCMD_DEST, required=True) + rc_subcmd = cmds.add_parser( + Command.Rc.value, help="Commands for Windows RC resource files." + ) + _register_rc_param(rc_subcmd) + + +# endregion + + +def parse() -> Opts: + """ + Parse MetaGlot command line options. + + :return: The parsed command line options. + """ + parser = ArgumentParser( + prog="metaglot", + description="A command-line tool that unifies localization metadata for applications shipped on multiple platforms.", + ) + _register_param(parser) + + args = vars(parser.parse_args()) + + match Command(args[_SUBCMD_DEST]): + case Command.Rc: + match RcCommand(args[_RC_SUBCMD_DEST]): + case RcCommand.Pot: + rc_pot_opts = RcPotOpts( + args[_RC_POT_MANIFEST_DEST], args[_RC_POT_OUTPUT_DEST] + ) + rc_opts = RcOpts(rc_pot_opts) + case RcCommand.Render: + rc_rdr_opts = RcRenderOpts( + args[_RC_RDR_MANIFEST_DEST], + args[_RC_RDR_PO_DEST], + args[_RC_RDR_TEMPLATE_DEST], + args[_RC_RDR_OUTPUT_DEST], + ) + rc_opts = RcOpts(rc_rdr_opts) + case _: + raise ValueError(f"unhandled rc command: {args[_RC_SUBCMD_DEST]}") + opts = Opts(rc_opts) + case _: + raise ValueError(f"unhandled command: {args[_SUBCMD_DEST]}") + return opts