diff --git a/src/metaglot/__init__.py b/src/metaglot/__init__.py index 8665026..8b1c099 100644 --- a/src/metaglot/__init__.py +++ b/src/metaglot/__init__.py @@ -1,12 +1,19 @@ import logging import sys -from .cli import parse -from .cmds import run +from argparse import ArgumentParser +from .cmds import run, register, parse def main() -> None: logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s") - opts = parse() + + parser = ArgumentParser( + prog="metaglot", + description="A command-line tool that unifies localization metadata for applications shipped on multiple platforms.", + ) + register(parser) + args = vars(parser.parse_args()) + opts = parse(args) try: run(opts) diff --git a/src/metaglot/cli.py b/src/metaglot/cli.py deleted file mode 100644 index 6fe22e4..0000000 --- a/src/metaglot/cli.py +++ /dev/null @@ -1,624 +0,0 @@ -import enum -from argparse import ArgumentParser -from dataclasses import dataclass -from pathlib import Path - -# region: Options and Commands - - -@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.""" - - -@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 RcUpdateOpts: - in_pot: Path - """The path to the input POT template file.""" - in_po: list[Path] - """The path to input PO files to update, each may be a glob pattern.""" - - -@dataclass(frozen=True) -class RcOpts: - opts: RcPotOpts | RcRenderOpts | RcUpdateOpts - """The option of one of subcommand.""" - - -class RcCommand(enum.StrEnum): - Pot = "pot" - Render = "render" - Update = "update" - - -@dataclass(frozen=True) -class DesktopPotOpts: - in_manifest: Path - """The path to input manifest file including translation strings.""" - out_pot: Path - """The path to output POT file.""" - - -@dataclass(frozen=True) -class DesktopRenderOpts: - 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_desktop: Path - """The path to the output rendered desktop entry file.""" - - -@dataclass(frozen=True) -class DesktopUpdateOpts: - in_pot: Path - """The path to the input POT template file.""" - in_po: list[Path] - """The path to input PO files to update, each may be a glob pattern.""" - - -@dataclass(frozen=True) -class DesktopOpts: - opts: DesktopPotOpts | DesktopRenderOpts | DesktopUpdateOpts - """The option of one of subcommand.""" - - -class DesktopCommand(enum.StrEnum): - Pot = "pot" - Render = "render" - Update = "update" - - -@dataclass(frozen=True) -class AppStreamPotOpts: - in_manifest: Path - """The path to input manifest file including translation strings.""" - out_pot: Path - """The path to output POT file.""" - - -@dataclass(frozen=True) -class AppStreamRenderOpts: - 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_appstream: Path - """The path to the output rendered AppStream XML file.""" - - -@dataclass(frozen=True) -class AppStreamUpdateOpts: - in_pot: Path - """The path to the input POT template file.""" - in_po: list[Path] - """The path to input PO files to update, each may be a glob pattern.""" - - -@dataclass(frozen=True) -class AppStreamOpts: - opts: AppStreamPotOpts | AppStreamRenderOpts | AppStreamUpdateOpts - """The option of one of subcommand.""" - - -class AppStreamCommand(enum.StrEnum): - Pot = "pot" - Render = "render" - Update = "update" - - -@dataclass(frozen=True) -class Opts: - """The root options.""" - - opts: RcOpts | DesktopOpts | AppStreamOpts - """The option of one of subcommand.""" - - -class Command(enum.StrEnum): - Rc = "rc" - Desktop = "desktop" - Appstream = "appstream" - - -# endregion - -# region: Parameter Register - -_SUBCMD_DEST = "command" -_RC_SUBCMD_DEST = "rc_command" -_DESKTOP_SUBCMD_DEST = "desktop_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" - -_RC_UPD_POT_DEST = "pot" -_RC_UPD_PO_DEST = "po" - -_DESKTOP_POT_MANIFEST_DEST = "manifest" -_DESKTOP_POT_OUTPUT_DEST = "output" - -_DESKTOP_RDR_MANIFEST_DEST = "manifest" -_DESKTOP_RDR_PO_DEST = "po" -_DESKTOP_RDR_TEMPLATE_DEST = "template" -_DESKTOP_RDR_OUTPUT_DEST = "output" - -_DESKTOP_UPD_POT_DEST = "pot" -_DESKTOP_UPD_PO_DEST = "po" - -_APPSTREAM_SUBCMD_DEST = "appstream_command" - -_APPSTREAM_POT_MANIFEST_DEST = "manifest" -_APPSTREAM_POT_OUTPUT_DEST = "output" - -_APPSTREAM_RDR_MANIFEST_DEST = "manifest" -_APPSTREAM_RDR_PO_DEST = "po" -_APPSTREAM_RDR_TEMPLATE_DEST = "template" -_APPSTREAM_RDR_OUTPUT_DEST = "output" - -_APPSTREAM_UPD_POT_DEST = "pot" -_APPSTREAM_UPD_PO_DEST = "po" - - -def _register_rc_pot_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_RC_POT_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_RC_POT_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to output POT file.", - metavar="FILE", - ) - - -def _register_rc_render_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_RC_RDR_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_RC_RDR_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to read, each may be a glob pattern.", - metavar="GLOB", - ) - parser.add_argument( - "-t", - "--template", - dest=_RC_RDR_TEMPLATE_DEST, - action="store", - type=Path, - required=True, - help="The path to the input user-provided Liquid for rendering.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_RC_RDR_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to the output rendered Windows RC file.", - metavar="FILE", - ) - - -def _register_rc_update_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-t", - "--pot", - dest=_RC_UPD_POT_DEST, - action="store", - type=Path, - required=True, - help="The path to the input POT template file.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_RC_UPD_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to update, each may be a glob pattern.", - metavar="GLOB", - ) - - -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) - update_subcmd = cmds.add_parser( - RcCommand.Update.value, help="Update PO files against a POT template." - ) - _register_rc_update_param(update_subcmd) - - -def _register_desktop_pot_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_DESKTOP_POT_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_DESKTOP_POT_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to output POT file.", - metavar="FILE", - ) - - -def _register_desktop_render_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_DESKTOP_RDR_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_DESKTOP_RDR_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to read, each may be a glob pattern.", - metavar="GLOB", - ) - parser.add_argument( - "-t", - "--template", - dest=_DESKTOP_RDR_TEMPLATE_DEST, - action="store", - type=Path, - required=True, - help="The path to the input user-provided Liquid for rendering.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_DESKTOP_RDR_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to the output rendered desktop entry file.", - metavar="FILE", - ) - - -def _register_desktop_update_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-t", - "--pot", - dest=_DESKTOP_UPD_POT_DEST, - action="store", - type=Path, - required=True, - help="The path to the input POT template file.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_DESKTOP_UPD_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to update, each may be a glob pattern.", - metavar="GLOB", - ) - - -def _register_desktop_param(parser: ArgumentParser) -> None: - cmds = parser.add_subparsers(dest=_DESKTOP_SUBCMD_DEST, required=True) - pot_subcmd = cmds.add_parser( - DesktopCommand.Pot.value, - help="Generate a POT template from given manifest.", - ) - _register_desktop_pot_param(pot_subcmd) - render_subcmd = cmds.add_parser( - DesktopCommand.Render.value, - help="Render a user-provided template with manifest data and PO translations.", - ) - _register_desktop_render_param(render_subcmd) - update_subcmd = cmds.add_parser( - DesktopCommand.Update.value, help="Update PO files against a POT template." - ) - _register_desktop_update_param(update_subcmd) - - -def _register_appstream_pot_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_APPSTREAM_POT_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_APPSTREAM_POT_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to output POT file.", - metavar="FILE", - ) - - -def _register_appstream_render_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-m", - "--manifest", - dest=_APPSTREAM_RDR_MANIFEST_DEST, - action="store", - type=Path, - required=True, - help="The path to input manifest file including translation strings.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_APPSTREAM_RDR_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to read, each may be a glob pattern.", - metavar="GLOB", - ) - parser.add_argument( - "-t", - "--template", - dest=_APPSTREAM_RDR_TEMPLATE_DEST, - action="store", - type=Path, - required=True, - help="The path to the input user-provided Liquid for rendering.", - metavar="FILE", - ) - parser.add_argument( - "-o", - "--output", - dest=_APPSTREAM_RDR_OUTPUT_DEST, - action="store", - type=Path, - required=True, - help="The path to the output rendered AppStream XML file.", - metavar="FILE", - ) - - -def _register_appstream_update_param(parser: ArgumentParser) -> None: - parser.add_argument( - "-t", - "--pot", - dest=_APPSTREAM_UPD_POT_DEST, - action="store", - type=Path, - required=True, - help="The path to the input POT template file.", - metavar="FILE", - ) - parser.add_argument( - "-p", - "--po", - dest=_APPSTREAM_UPD_PO_DEST, - action="extend", - nargs="+", - type=Path, - required=True, - help="The PO files to update, each may be a glob pattern.", - metavar="GLOB", - ) - - -def _register_appstream_param(parser: ArgumentParser) -> None: - cmds = parser.add_subparsers(dest=_APPSTREAM_SUBCMD_DEST, required=True) - pot_subcmd = cmds.add_parser( - AppStreamCommand.Pot.value, - help="Generate a POT template from given manifest.", - ) - _register_appstream_pot_param(pot_subcmd) - render_subcmd = cmds.add_parser( - AppStreamCommand.Render.value, - help="Render a user-provided template with manifest data and PO translations.", - ) - _register_appstream_render_param(render_subcmd) - update_subcmd = cmds.add_parser( - AppStreamCommand.Update.value, help="Update PO files against a POT template." - ) - _register_appstream_update_param(update_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) - desktop_subcmd = cmds.add_parser( - Command.Desktop.value, help="Commands for freedesktop desktop entry files." - ) - _register_desktop_param(desktop_subcmd) - appstream_subcmd = cmds.add_parser( - Command.Appstream.value, help="Commands for AppStream metainfo XML files." - ) - _register_appstream_param(appstream_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 RcCommand.Update: - rc_upd_opts = RcUpdateOpts( - args[_RC_UPD_POT_DEST], args[_RC_UPD_PO_DEST] - ) - rc_opts = RcOpts(rc_upd_opts) - case _: - raise ValueError(f"unhandled rc command: {args[_RC_SUBCMD_DEST]}") - opts = Opts(rc_opts) - case Command.Desktop: - match DesktopCommand(args[_DESKTOP_SUBCMD_DEST]): - case DesktopCommand.Pot: - desktop_pot_opts = DesktopPotOpts( - args[_DESKTOP_POT_MANIFEST_DEST], args[_DESKTOP_POT_OUTPUT_DEST] - ) - desktop_opts = DesktopOpts(desktop_pot_opts) - case DesktopCommand.Render: - desktop_rdr_opts = DesktopRenderOpts( - args[_DESKTOP_RDR_MANIFEST_DEST], - args[_DESKTOP_RDR_PO_DEST], - args[_DESKTOP_RDR_TEMPLATE_DEST], - args[_DESKTOP_RDR_OUTPUT_DEST], - ) - desktop_opts = DesktopOpts(desktop_rdr_opts) - case DesktopCommand.Update: - desktop_upd_opts = DesktopUpdateOpts( - args[_DESKTOP_UPD_POT_DEST], args[_DESKTOP_UPD_PO_DEST] - ) - desktop_opts = DesktopOpts(desktop_upd_opts) - case _: - raise ValueError( - f"unhandled desktop command: {args[_DESKTOP_SUBCMD_DEST]}" - ) - opts = Opts(desktop_opts) - case Command.Appstream: - match AppStreamCommand(args[_APPSTREAM_SUBCMD_DEST]): - case AppStreamCommand.Pot: - appstream_pot_opts = AppStreamPotOpts( - args[_APPSTREAM_POT_MANIFEST_DEST], - args[_APPSTREAM_POT_OUTPUT_DEST], - ) - appstream_opts = AppStreamOpts(appstream_pot_opts) - case AppStreamCommand.Render: - appstream_rdr_opts = AppStreamRenderOpts( - args[_APPSTREAM_RDR_MANIFEST_DEST], - args[_APPSTREAM_RDR_PO_DEST], - args[_APPSTREAM_RDR_TEMPLATE_DEST], - args[_APPSTREAM_RDR_OUTPUT_DEST], - ) - appstream_opts = AppStreamOpts(appstream_rdr_opts) - case AppStreamCommand.Update: - appstream_upd_opts = AppStreamUpdateOpts( - args[_APPSTREAM_UPD_POT_DEST], args[_APPSTREAM_UPD_PO_DEST] - ) - appstream_opts = AppStreamOpts(appstream_upd_opts) - case _: - raise ValueError( - f"unhandled appstream command: {args[_APPSTREAM_SUBCMD_DEST]}" - ) - opts = Opts(appstream_opts) - case _: - raise ValueError(f"unhandled command: {args[_SUBCMD_DEST]}") - return opts diff --git a/src/metaglot/cmds/__init__.py b/src/metaglot/cmds/__init__.py index 44bcf0d..7836a77 100644 --- a/src/metaglot/cmds/__init__.py +++ b/src/metaglot/cmds/__init__.py @@ -1,5 +1,58 @@ -from ..cli import Opts, RcOpts, DesktopOpts, AppStreamOpts +import enum +from argparse import ArgumentParser +from dataclasses import dataclass +from typing import Any from . import appstream, desktop, rc +from .rc import RcOpts +from .desktop import DesktopOpts +from .appstream import AppStreamOpts + + +@dataclass(frozen=True) +class Opts: + """The root options.""" + + opts: RcOpts | DesktopOpts | AppStreamOpts + """The option of one of subcommand.""" + + +_SUBCMD_DEST = "command" + + +class _Command(enum.StrEnum): + Rc = "rc" + Desktop = "desktop" + Appstream = "appstream" + + +def register(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_SUBCMD_DEST, required=True) + subcmd = cmds.add_parser( + _Command.Rc.value, help="Commands for Windows RC resource files." + ) + rc.register(subcmd) + subcmd = cmds.add_parser( + _Command.Desktop.value, help="Commands for freedesktop desktop entry files." + ) + desktop.register(subcmd) + subcmd = cmds.add_parser( + _Command.Appstream.value, help="Commands for AppStream metainfo XML files." + ) + appstream.register(subcmd) + + +def parse(args: dict[str, Any]) -> Opts: + match _Command(args[_SUBCMD_DEST]): + case _Command.Rc: + opts = Opts(rc.parse(args)) + case _Command.Desktop: + opts = Opts(desktop.parse(args)) + case _Command.Appstream: + opts = Opts(appstream.parse(args)) + case _: + raise ValueError(f"unhandled command: {args[_SUBCMD_DEST]}") + return opts + def run(opts: Opts) -> None: match opts.opts: diff --git a/src/metaglot/cmds/appstream/__init__.py b/src/metaglot/cmds/appstream/__init__.py index 1ddd789..8ef7ef6 100644 --- a/src/metaglot/cmds/appstream/__init__.py +++ b/src/metaglot/cmds/appstream/__init__.py @@ -1,10 +1,59 @@ -from ...cli import ( - AppStreamOpts, - AppStreamPotOpts, - AppStreamRenderOpts, - AppStreamUpdateOpts, -) +import enum +from argparse import ArgumentParser +from dataclasses import dataclass +from typing import Any from . import pot, render, update +from .pot import AppStreamPotOpts +from .render import AppStreamRenderOpts +from .update import AppStreamUpdateOpts + +_SUBCMD_DEST = "subcommand" + + +@dataclass(frozen=True) +class AppStreamOpts: + opts: AppStreamPotOpts | AppStreamRenderOpts | AppStreamUpdateOpts + """The option of one of subcommand.""" + + +class _Command(enum.StrEnum): + Pot = "pot" + Render = "render" + Update = "update" + + +def register(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_SUBCMD_DEST, required=True) + pot_subcmd = cmds.add_parser( + _Command.Pot.value, + help="Generate a POT template from given manifest.", + ) + pot.register(pot_subcmd) + render_subcmd = cmds.add_parser( + _Command.Render.value, + help="Render a user-provided template with manifest data and PO translations.", + ) + render.register(render_subcmd) + update_subcmd = cmds.add_parser( + _Command.Update.value, help="Update PO files against a POT template." + ) + update.register(update_subcmd) + + +def parse(args: dict[str, Any]) -> AppStreamOpts: + match _Command(args[_SUBCMD_DEST]): + case _Command.Pot: + opts = AppStreamOpts(pot.parse(args)) + case _Command.Render: + opts = AppStreamOpts(render.parse(args)) + case _Command.Update: + opts = AppStreamOpts(update.parse(args)) + case _: + raise ValueError( + f"unhandled appstream command: {args[_SUBCMD_DEST]}" + ) + return opts + def run(opts: AppStreamOpts) -> None: match opts.opts: diff --git a/src/metaglot/cmds/appstream/pot.py b/src/metaglot/cmds/appstream/pot.py index 9f440e9..05d90f1 100644 --- a/src/metaglot/cmds/appstream/pot.py +++ b/src/metaglot/cmds/appstream/pot.py @@ -1,7 +1,49 @@ -from ...cli import AppStreamPotOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...manifest import load_manifest from ...pofile import generate_pot +_MANIFEST_DEST = "manifest" +_OUTPUT_DEST = "output" + + +@dataclass(frozen=True) +class AppStreamPotOpts: + in_manifest: Path + """The path to input manifest file including translation strings.""" + out_pot: Path + """The path to output POT file.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path to output POT file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> AppStreamPotOpts: + return AppStreamPotOpts(args[_MANIFEST_DEST], args[_OUTPUT_DEST]) + + def run(opts: AppStreamPotOpts) -> None: manifest = load_manifest(opts.in_manifest) generate_pot(manifest, opts.out_pot) diff --git a/src/metaglot/cmds/appstream/render.py b/src/metaglot/cmds/appstream/render.py index 3e47663..ca5a076 100644 --- a/src/metaglot/cmds/appstream/render.py +++ b/src/metaglot/cmds/appstream/render.py @@ -1,6 +1,8 @@ import logging - -from ...cli import AppStreamRenderOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...filters import register_appstream_filters, register_general_filters from ...langmap import appstream_lang_map from ...manifest import Manifest, load_manifest @@ -12,6 +14,76 @@ from ...pofile import ( ) from ...render import create_environment, render +_MANIFEST_DEST = "manifest" +_PO_DEST = "po" +_TEMPLATE_DEST = "template" +_OUTPUT_DEST = "output" + + +@dataclass(frozen=True) +class AppStreamRenderOpts: + 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_appstream: Path + """The path of the output rendered AppStream XML file.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to read, each may be a glob pattern.", + metavar="GLOB", + ) + parser.add_argument( + "-t", + "--template", + dest=_TEMPLATE_DEST, + action="store", + type=Path, + required=True, + help="The path to the input user-provided Liquid for rendering.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path of the output rendered AppStream XML file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> AppStreamRenderOpts: + return AppStreamRenderOpts( + args[_MANIFEST_DEST], + args[_PO_DEST], + args[_TEMPLATE_DEST], + args[_OUTPUT_DEST], + ) + def _language_context(pack: LanguagePack, manifest: Manifest) -> dict: lang = appstream_lang_map.convert(pack.lang) diff --git a/src/metaglot/cmds/appstream/update.py b/src/metaglot/cmds/appstream/update.py index 2085ec7..62ed0bc 100644 --- a/src/metaglot/cmds/appstream/update.py +++ b/src/metaglot/cmds/appstream/update.py @@ -1,6 +1,48 @@ -from ...cli import AppStreamUpdateOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...pofile import resolve_glob_files, update_po_files +_POT_DEST = "pot" +_PO_DEST = "po" + + +@dataclass(frozen=True) +class AppStreamUpdateOpts: + in_pot: Path + """The path to the input POT template file.""" + in_po: list[Path] + """The path to input PO files to update, each may be a glob pattern.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-t", + "--pot", + dest=_POT_DEST, + action="store", + type=Path, + required=True, + help="The path to the input POT template file.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to update, each may be a glob pattern.", + metavar="GLOB", + ) + + +def parse(args: dict[str, Any]) -> AppStreamUpdateOpts: + return AppStreamUpdateOpts(args[_POT_DEST], args[_PO_DEST]) + def run(opts: AppStreamUpdateOpts) -> None: update_po_files(opts.in_pot, resolve_glob_files(opts.in_po)) diff --git a/src/metaglot/cmds/desktop/__init__.py b/src/metaglot/cmds/desktop/__init__.py index 097fba6..3a05701 100644 --- a/src/metaglot/cmds/desktop/__init__.py +++ b/src/metaglot/cmds/desktop/__init__.py @@ -1,5 +1,59 @@ -from ...cli import DesktopOpts, DesktopPotOpts, DesktopRenderOpts, DesktopUpdateOpts +import enum +from argparse import ArgumentParser +from dataclasses import dataclass +from typing import Any from . import pot, render, update +from .pot import DesktopPotOpts +from .render import DesktopRenderOpts +from .update import DesktopUpdateOpts + +_SUBCMD_DEST = "subcommand" + + +@dataclass(frozen=True) +class DesktopOpts: + opts: DesktopPotOpts | DesktopRenderOpts | DesktopUpdateOpts + """The option of one of subcommand.""" + + +class _Command(enum.StrEnum): + Pot = "pot" + Render = "render" + Update = "update" + + +def register(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_SUBCMD_DEST, required=True) + pot_subcmd = cmds.add_parser( + _Command.Pot.value, + help="Generate a POT template from given manifest.", + ) + pot.register(pot_subcmd) + render_subcmd = cmds.add_parser( + _Command.Render.value, + help="Render a user-provided template with manifest data and PO translations.", + ) + render.register(render_subcmd) + update_subcmd = cmds.add_parser( + _Command.Update.value, help="Update PO files against a POT template." + ) + update.register(update_subcmd) + + +def parse(args: dict[str, Any]) -> DesktopOpts: + match _Command(args[_SUBCMD_DEST]): + case _Command.Pot: + opts = DesktopOpts(pot.parse(args)) + case _Command.Render: + opts = DesktopOpts(render.parse(args)) + case _Command.Update: + opts = DesktopOpts(update.parse(args)) + case _: + raise ValueError( + f"unhandled desktop command: {args[_SUBCMD_DEST]}" + ) + return opts + def run(opts: DesktopOpts) -> None: match opts.opts: diff --git a/src/metaglot/cmds/desktop/pot.py b/src/metaglot/cmds/desktop/pot.py index 826e73b..e96bab9 100644 --- a/src/metaglot/cmds/desktop/pot.py +++ b/src/metaglot/cmds/desktop/pot.py @@ -1,7 +1,49 @@ -from ...cli import DesktopPotOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...manifest import load_manifest from ...pofile import generate_pot +_MANIFEST_DEST = "manifest" +_OUTPUT_DEST = "output" + + +@dataclass(frozen=True) +class DesktopPotOpts: + in_manifest: Path + """The path to input manifest file including translation strings.""" + out_pot: Path + """The path to output POT file.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path to output POT file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> DesktopPotOpts: + return DesktopPotOpts(args[_MANIFEST_DEST], args[_OUTPUT_DEST]) + + def run(opts: DesktopPotOpts) -> None: manifest = load_manifest(opts.in_manifest) generate_pot(manifest, opts.out_pot) diff --git a/src/metaglot/cmds/desktop/render.py b/src/metaglot/cmds/desktop/render.py index 88c9f27..4555893 100644 --- a/src/metaglot/cmds/desktop/render.py +++ b/src/metaglot/cmds/desktop/render.py @@ -1,6 +1,8 @@ import logging - -from ...cli import DesktopRenderOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...filters import register_desktop_filters, register_general_filters from ...langmap import desktop_lang_map from ...manifest import Manifest, load_manifest @@ -12,6 +14,76 @@ from ...pofile import ( ) from ...render import create_environment, render +_MANIFEST_DEST = "manifest" +_PO_DEST = "po" +_TEMPLATE_DEST = "template" +_OUTPUT_DEST = "output" + + +@dataclass(frozen=True) +class DesktopRenderOpts: + 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_desktop: Path + """The path of the output rendered desktop entry file.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to read, each may be a glob pattern.", + metavar="GLOB", + ) + parser.add_argument( + "-t", + "--template", + dest=_TEMPLATE_DEST, + action="store", + type=Path, + required=True, + help="The path to the input user-provided Liquid for rendering.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path of the output rendered desktop entry file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> DesktopRenderOpts: + return DesktopRenderOpts( + args[_MANIFEST_DEST], + args[_PO_DEST], + args[_TEMPLATE_DEST], + args[_OUTPUT_DEST], + ) + def _language_context(pack: LanguagePack, manifest: Manifest) -> dict: locale = desktop_lang_map.convert(pack.lang) diff --git a/src/metaglot/cmds/desktop/update.py b/src/metaglot/cmds/desktop/update.py index 9fab6d6..7a211af 100644 --- a/src/metaglot/cmds/desktop/update.py +++ b/src/metaglot/cmds/desktop/update.py @@ -1,6 +1,48 @@ -from ...cli import DesktopUpdateOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...pofile import resolve_glob_files, update_po_files +_POT_DEST = "pot" +_PO_DEST = "po" + + +@dataclass(frozen=True) +class DesktopUpdateOpts: + in_pot: Path + """The path to the input POT template file.""" + in_po: list[Path] + """The path to input PO files to update, each may be a glob pattern.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-t", + "--pot", + dest=_POT_DEST, + action="store", + type=Path, + required=True, + help="The path to the input POT template file.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to update, each may be a glob pattern.", + metavar="GLOB", + ) + + +def parse(args: dict[str, Any]) -> DesktopUpdateOpts: + return DesktopUpdateOpts(args[_POT_DEST], args[_PO_DEST]) + def run(opts: DesktopUpdateOpts) -> None: update_po_files(opts.in_pot, resolve_glob_files(opts.in_po)) diff --git a/src/metaglot/cmds/rc/__init__.py b/src/metaglot/cmds/rc/__init__.py index 4f863a0..93b0140 100644 --- a/src/metaglot/cmds/rc/__init__.py +++ b/src/metaglot/cmds/rc/__init__.py @@ -1,5 +1,56 @@ -from ...cli import RcOpts, RcPotOpts, RcRenderOpts, RcUpdateOpts +import enum +from argparse import ArgumentParser +from dataclasses import dataclass +from typing import Any from . import pot, render, update +from .pot import RcPotOpts +from .render import RcRenderOpts +from .update import RcUpdateOpts + +_SUBCMD_DEST = "subcommand" + + +@dataclass(frozen=True) +class RcOpts: + opts: RcPotOpts | RcRenderOpts | RcUpdateOpts + """The option of one of subcommand.""" + + +class _Command(enum.StrEnum): + Pot = "pot" + Render = "render" + Update = "update" + + +def register(parser: ArgumentParser) -> None: + cmds = parser.add_subparsers(dest=_SUBCMD_DEST, required=True) + pot_subcmd = cmds.add_parser( + _Command.Pot.value, help="Generate a POT template from given manifest." + ) + pot.register(pot_subcmd) + render_subcmd = cmds.add_parser( + _Command.Render.value, + help="Render a user-provided template with manifest data and PO translations.", + ) + render.register(render_subcmd) + update_subcmd = cmds.add_parser( + _Command.Update.value, help="Update PO files against a POT template." + ) + update.register(update_subcmd) + + +def parse(args: dict[str, Any]) -> RcOpts: + match _Command(args[_SUBCMD_DEST]): + case _Command.Pot: + opts = RcOpts(pot.parse(args)) + case _Command.Render: + opts = RcOpts(render.parse(args)) + case _Command.Update: + opts = RcOpts(update.parse(args)) + case _: + raise ValueError(f"unhandled rc command: {args[_SUBCMD_DEST]}") + return opts + def run(opts: RcOpts) -> None: match opts.opts: diff --git a/src/metaglot/cmds/rc/pot.py b/src/metaglot/cmds/rc/pot.py index 56f44f8..1106062 100644 --- a/src/metaglot/cmds/rc/pot.py +++ b/src/metaglot/cmds/rc/pot.py @@ -1,7 +1,49 @@ -from ...cli import RcPotOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...manifest import load_manifest from ...pofile import generate_pot +_MANIFEST_DEST = "manifest" +_OUTPUT_DEST = "output" + + +@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.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path to output POT file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> RcPotOpts: + return RcPotOpts(args[_MANIFEST_DEST], args[_OUTPUT_DEST]) + + def run(opts: RcPotOpts) -> None: manifest = load_manifest(opts.in_manifest) generate_pot(manifest, opts.out_pot) diff --git a/src/metaglot/cmds/rc/render.py b/src/metaglot/cmds/rc/render.py index 37db4db..c8101c4 100644 --- a/src/metaglot/cmds/rc/render.py +++ b/src/metaglot/cmds/rc/render.py @@ -1,6 +1,8 @@ import logging - -from ...cli import RcRenderOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...filters import register_general_filters, register_rc_filters from ...langid import PoLang, WinLcid from ...langmap import win_lcid_map @@ -20,6 +22,76 @@ MetaGlot reads and writes everything as UTF-8, so this code page is a fixed constraint of the pipeline, not a configuration point. """ +_MANIFEST_DEST = "manifest" +_PO_DEST = "po" +_TEMPLATE_DEST = "template" +_OUTPUT_DEST = "output" + + +@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 of the output rendered Windows RC file.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-m", + "--manifest", + dest=_MANIFEST_DEST, + action="store", + type=Path, + required=True, + help="The path to input manifest file including translation strings.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to read, each may be a glob pattern.", + metavar="GLOB", + ) + parser.add_argument( + "-t", + "--template", + dest=_TEMPLATE_DEST, + action="store", + type=Path, + required=True, + help="The path to the input user-provided Liquid for rendering.", + metavar="FILE", + ) + parser.add_argument( + "-o", + "--output", + dest=_OUTPUT_DEST, + action="store", + type=Path, + required=True, + help="The path of the output rendered Windows RC file.", + metavar="FILE", + ) + + +def parse(args: dict[str, Any]) -> RcRenderOpts: + return RcRenderOpts( + args[_MANIFEST_DEST], + args[_PO_DEST], + args[_TEMPLATE_DEST], + args[_OUTPUT_DEST], + ) + def _lookup_langid(lang: PoLang) -> WinLcid: try: diff --git a/src/metaglot/cmds/rc/update.py b/src/metaglot/cmds/rc/update.py index 3b3ebec..5613c58 100644 --- a/src/metaglot/cmds/rc/update.py +++ b/src/metaglot/cmds/rc/update.py @@ -1,6 +1,48 @@ -from ...cli import RcUpdateOpts +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path +from typing import Any from ...pofile import resolve_glob_files, update_po_files +_POT_DEST = "pot" +_PO_DEST = "po" + + +@dataclass(frozen=True) +class RcUpdateOpts: + in_pot: Path + """The path to the input POT template file.""" + in_po: list[Path] + """The path to input PO files to update, each may be a glob pattern.""" + + +def register(parser: ArgumentParser) -> None: + parser.add_argument( + "-t", + "--pot", + dest=_POT_DEST, + action="store", + type=Path, + required=True, + help="The path to the input POT template file.", + metavar="FILE", + ) + parser.add_argument( + "-p", + "--po", + dest=_PO_DEST, + action="extend", + nargs="+", + type=Path, + required=True, + help="The PO files to update, each may be a glob pattern.", + metavar="GLOB", + ) + + +def parse(args: dict[str, Any]) -> RcUpdateOpts: + return RcUpdateOpts(args[_POT_DEST], args[_PO_DEST]) + def run(opts: RcUpdateOpts) -> None: update_po_files(opts.in_pot, resolve_glob_files(opts.in_po))