78 lines
2.0 KiB
Python
78 lines
2.0 KiB
Python
# engine/descriptor.py
|
|
"""Declarative action descriptors — successor to the bash action_metadata() heredoc.
|
|
|
|
Meta drives BOTH the help text and argparse parsing. Exit code 64 (EX_USAGE) on a
|
|
usage error, matching the bash engine.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import dataclass, field
|
|
from typing import List
|
|
|
|
|
|
class _Parser(argparse.ArgumentParser):
|
|
"""ArgumentParser that exits 64 (EX_USAGE) instead of 2 on a usage error."""
|
|
|
|
def error(self, message: str):
|
|
self.print_usage()
|
|
self.exit(64, f"{self.prog}: error: {message}\n")
|
|
|
|
|
|
@dataclass
|
|
class Arg:
|
|
"""A positional argument. required=False -> optional positional (default None)."""
|
|
name: str
|
|
help: str = ""
|
|
required: bool = True
|
|
|
|
def add_to(self, p: argparse.ArgumentParser) -> None:
|
|
if self.required:
|
|
p.add_argument(self.name, help=self.help)
|
|
else:
|
|
p.add_argument(self.name, nargs="?", default=None, help=self.help)
|
|
|
|
|
|
@dataclass
|
|
class Opt:
|
|
"""An optional value flag, e.g. --tier PATH, with a default."""
|
|
name: str
|
|
help: str = ""
|
|
default: object = None
|
|
|
|
def add_to(self, p: argparse.ArgumentParser) -> None:
|
|
p.add_argument(self.name, default=self.default, help=self.help)
|
|
|
|
|
|
@dataclass
|
|
class Flag:
|
|
"""A boolean flag, e.g. --dry-run (store_true)."""
|
|
name: str
|
|
help: str = ""
|
|
|
|
def add_to(self, p: argparse.ArgumentParser) -> None:
|
|
p.add_argument(self.name, action="store_true", help=self.help)
|
|
|
|
|
|
@dataclass
|
|
class Rest:
|
|
"""Greedy passthrough of all remaining argv (argparse.REMAINDER)."""
|
|
name: str
|
|
help: str = ""
|
|
|
|
def add_to(self, p: argparse.ArgumentParser) -> None:
|
|
p.add_argument(self.name, nargs=argparse.REMAINDER, help=self.help)
|
|
|
|
|
|
@dataclass
|
|
class Meta:
|
|
summary: str
|
|
args: List[object] = field(default_factory=list)
|
|
|
|
|
|
def build_parser(meta: Meta, prog: str) -> argparse.ArgumentParser:
|
|
p = _Parser(prog=prog, description=meta.summary)
|
|
for a in meta.args:
|
|
a.add_to(p)
|
|
return p
|