refactor: refactor cli order

This commit is contained in:
2026-09-18 15:40:36 +08:00
parent a527b5807a
commit 1c40731ffa
17 changed files with 104 additions and 432 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# AppStream Render Context
This document describes the data that `metaglot appstream render` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering AppStream metainfo XML files.
This document describes the data that `metaglot render appstream` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering AppStream metainfo XML files.
MetaGlot reads templates as UTF-8 and writes the rendered file as UTF-8 (without BOM) using the system's native line endings. This encoding is a built-in constraint of the tool and is not configurable. AppStream XML files are UTF-8 by definition, so the XML declaration should say so (see the `example` folder for a complete sample).
+1 -1
View File
@@ -1,6 +1,6 @@
# Desktop Render Context
This document describes the data that `metaglot desktop render` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering freedesktop desktop entry files.
This document describes the data that `metaglot render desktop` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering freedesktop desktop entry files.
MetaGlot reads templates as UTF-8 and writes the rendered file as UTF-8 (without BOM) using the system's native line endings. This encoding is a built-in constraint of the tool and is not configurable. Desktop entry files are UTF-8 by definition, so no encoding declaration is needed (see the `example` folder for a complete sample).
+1 -1
View File
@@ -1,6 +1,6 @@
# RC Render Context
This document describes the data that `metaglot rc render` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering Windows RC files.
This document describes the data that `metaglot render rc` provides to a user-provided [Liquid](https://shopify.github.io/liquid/) template when rendering Windows RC files.
MetaGlot reads templates as UTF-8 and writes the rendered file as UTF-8 (without BOM) using the system's native line endings. This encoding is a built-in constraint of the tool and is not configurable. Because the generated file is UTF-8, templates should declare the encoding with `#pragma code_page(65001)` so that the RC compiler parses the file correctly (see the `example` folder for a complete sample).
+27 -26
View File
@@ -2,17 +2,17 @@ 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
from . import pot, update, render
from .pot import PotOpts
from .update import UpdateOpts
from .render import RenderOpts
@dataclass(frozen=True)
class Opts:
"""The root options."""
opts: RcOpts | DesktopOpts | AppStreamOpts
opts: PotOpts | UpdateOpts | RenderOpts
"""The option of one of subcommand."""
@@ -20,35 +20,36 @@ _SUBCMD_DEST = "command"
class _Command(enum.StrEnum):
Rc = "rc"
Desktop = "desktop"
Appstream = "appstream"
Pot = "pot"
Update = "update"
Render = "render"
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."
_Command.Pot.value, help="Generate a POT template from given manifest."
)
rc.register(subcmd)
pot.register(subcmd)
subcmd = cmds.add_parser(
_Command.Desktop.value, help="Commands for freedesktop desktop entry files."
_Command.Update.value, help="Update PO files against a POT template."
)
desktop.register(subcmd)
update.register(subcmd)
subcmd = cmds.add_parser(
_Command.Appstream.value, help="Commands for AppStream metainfo XML files."
_Command.Render.value,
help="Render a user-provided template with manifest data and PO translations.",
)
appstream.register(subcmd)
render.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 _Command.Pot:
opts = Opts(pot.parse(args))
case _Command.Update:
opts = Opts(update.parse(args))
case _Command.Render:
opts = Opts(render.parse(args))
case _:
raise ValueError(f"unhandled command: {args[_SUBCMD_DEST]}")
return opts
@@ -56,11 +57,11 @@ def parse(args: dict[str, Any]) -> Opts:
def run(opts: Opts) -> None:
match opts.opts:
case RcOpts() as rc_opts:
rc.run(rc_opts)
case DesktopOpts() as desktop_opts:
desktop.run(desktop_opts)
case AppStreamOpts() as appstream_opts:
appstream.run(appstream_opts)
case PotOpts() as pot_opts:
pot.run(pot_opts)
case UpdateOpts() as update_opts:
update.run(update_opts)
case RenderOpts() as render_opts:
render.run(render_opts)
case _:
raise RuntimeError(f"unhandled options: {opts.opts!r}")
-67
View File
@@ -1,67 +0,0 @@
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:
case AppStreamPotOpts() as pot_opts:
pot.run(pot_opts)
case AppStreamRenderOpts() as render_opts:
render.run(render_opts)
case AppStreamUpdateOpts() as update_opts:
update.run(update_opts)
case _:
raise RuntimeError(f"unhandled appstream options: {opts.opts!r}")
-49
View File
@@ -1,49 +0,0 @@
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)
-48
View File
@@ -1,48 +0,0 @@
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))
-67
View File
@@ -1,67 +0,0 @@
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:
case DesktopPotOpts() as pot_opts:
pot.run(pot_opts)
case DesktopRenderOpts() as render_opts:
render.run(render_opts)
case DesktopUpdateOpts() as update_opts:
update.run(update_opts)
case _:
raise RuntimeError(f"unhandled desktop options: {opts.opts!r}")
-49
View File
@@ -1,49 +0,0 @@
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)
-48
View File
@@ -1,48 +0,0 @@
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))
@@ -2,15 +2,15 @@ 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
from ..manifest import load_manifest
from ..pofile import generate_pot
_MANIFEST_DEST = "manifest"
_OUTPUT_DEST = "output"
@dataclass(frozen=True)
class RcPotOpts:
class PotOpts:
in_manifest: Path
"""The path to input manifest file including translation strings."""
out_pot: Path
@@ -40,10 +40,10 @@ def register(parser: ArgumentParser) -> None:
)
def parse(args: dict[str, Any]) -> RcPotOpts:
return RcPotOpts(args[_MANIFEST_DEST], args[_OUTPUT_DEST])
def parse(args: dict[str, Any]) -> PotOpts:
return PotOpts(args[_MANIFEST_DEST], args[_OUTPUT_DEST])
def run(opts: RcPotOpts) -> None:
def run(opts: PotOpts) -> None:
manifest = load_manifest(opts.in_manifest)
generate_pot(manifest, opts.out_pot)
-64
View File
@@ -1,64 +0,0 @@
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:
case RcPotOpts() as pot_opts:
pot.run(pot_opts)
case RcRenderOpts() as render_opts:
render.run(render_opts)
case RcUpdateOpts() as update_opts:
update.run(update_opts)
case _:
raise RuntimeError(f"unhandled rc options: {opts.opts!r}")
+63
View File
@@ -0,0 +1,63 @@
import enum
from argparse import ArgumentParser
from dataclasses import dataclass
from typing import Any
from . import rc, desktop, appstream
from .rc import RcRenderOpts
from .desktop import DesktopRenderOpts
from .appstream import AppStreamRenderOpts
_SUBCMD_DEST = "metadata"
@dataclass(frozen=True)
class RenderOpts:
opts: RcRenderOpts | DesktopRenderOpts | AppStreamRenderOpts
"""The option of one of subcommand."""
class _Metadata(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(
_Metadata.Rc.value, help="Render Windows RC resource files."
)
rc.register(subcmd)
subcmd = cmds.add_parser(
_Metadata.Desktop.value, help="Render freedesktop desktop entry files."
)
desktop.register(subcmd)
subcmd = cmds.add_parser(
_Metadata.Appstream.value, help="Render AppStream metainfo XML files."
)
appstream.register(subcmd)
def parse(args: dict[str, Any]) -> RenderOpts:
match _Metadata(args[_SUBCMD_DEST]):
case _Metadata.Rc:
opts = RenderOpts(rc.parse(args))
case _Metadata.Desktop:
opts = RenderOpts(desktop.parse(args))
case _Metadata.Appstream:
opts = RenderOpts(appstream.parse(args))
case _:
raise ValueError(f"unhandled render command: {args[_SUBCMD_DEST]}")
return opts
def run(opts: RenderOpts) -> None:
match opts.opts:
case RcRenderOpts() as rc_opts:
rc.run(rc_opts)
case DesktopRenderOpts() as desktop_opts:
desktop.run(desktop_opts)
case AppStreamRenderOpts() as appstream_opts:
appstream.run(appstream_opts)
case _:
raise RuntimeError(f"unhandled render options: {opts.opts!r}")
@@ -2,14 +2,14 @@ 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
from ..pofile import resolve_glob_files, update_po_files
_POT_DEST = "pot"
_PO_DEST = "po"
@dataclass(frozen=True)
class RcUpdateOpts:
class UpdateOpts:
in_pot: Path
"""The path to the input POT template file."""
in_po: list[Path]
@@ -40,9 +40,9 @@ def register(parser: ArgumentParser) -> None:
)
def parse(args: dict[str, Any]) -> RcUpdateOpts:
return RcUpdateOpts(args[_POT_DEST], args[_PO_DEST])
def parse(args: dict[str, Any]) -> UpdateOpts:
return UpdateOpts(args[_POT_DEST], args[_PO_DEST])
def run(opts: RcUpdateOpts) -> None:
def run(opts: UpdateOpts) -> None:
update_po_files(opts.in_pot, resolve_glob_files(opts.in_po))