Marco Ricci commited on 2026-08-16 16:10:07
Zeige 1 geänderte Dateien mit 88 Einfügungen und 0 Löschungen.
| ... | ... |
@@ -68,6 +68,17 @@ CLISubcommand: TypeAlias = Union[ |
| 68 | 68 |
] |
| 69 | 69 |
|
| 70 | 70 |
|
| 71 |
+def _serializer( |
|
| 72 |
+ arg: CLIOption |
|
| 73 |
+ | CLIOptionGroup |
|
| 74 |
+ | CLIArgument |
|
| 75 |
+ | CLITerminalSubcommand |
|
| 76 |
+ | CLICompositeSubcommand, |
|
| 77 |
+ /, |
|
| 78 |
+) -> dict[str, Any]: |
|
| 79 |
+ return arg.serialize() |
|
| 80 |
+ |
|
| 81 |
+ |
|
| 71 | 82 |
@dataclasses.dataclass(frozen=True) |
| 72 | 83 |
class CLIOption: |
| 73 | 84 |
"""A CLI option.""" |
| ... | ... |
@@ -111,6 +122,24 @@ class CLIOption: |
| 111 | 122 |
eager=self.eager, |
| 112 | 123 |
) |
| 113 | 124 |
|
| 125 |
+ def serialize(self) -> dict[str, Any]: |
|
| 126 |
+ return {
|
|
| 127 |
+ "names": self.names, |
|
| 128 |
+ "help": str(self.help), |
|
| 129 |
+ "has_argument": self.has_argument, |
|
| 130 |
+ "eager": self.eager, |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ def usage(self) -> str: |
|
| 134 |
+ name = "/".join(sorted(self.names, key=len)[:1]) |
|
| 135 |
+ if self.has_argument and self.eager: |
|
| 136 |
+ return f"[{name} ARG]!"
|
|
| 137 |
+ if self.has_argument: |
|
| 138 |
+ return f"[{name} ARG]"
|
|
| 139 |
+ if self.eager: |
|
| 140 |
+ return f"[{name}]!"
|
|
| 141 |
+ return f"[{name}]"
|
|
| 142 |
+ |
|
| 114 | 143 |
|
| 115 | 144 |
@dataclasses.dataclass(frozen=True) |
| 116 | 145 |
class CLIOptionGroup: |
| ... | ... |
@@ -123,6 +152,17 @@ class CLIOptionGroup: |
| 123 | 152 |
epilog: TrStr = "" |
| 124 | 153 |
"""The epilog.""" |
| 125 | 154 |
|
| 155 |
+ def serialize(self) -> dict[str, Any]: |
|
| 156 |
+ return {
|
|
| 157 |
+ "options": tuple(map(_serializer, self.options)), |
|
| 158 |
+ "title": str(self.title), |
|
| 159 |
+ "epilog": str(self.epilog), |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ def usage(self) -> str: |
|
| 163 |
+ opts = sorted(self.options, key=lambda opt: 0 if opt.eager else 1) |
|
| 164 |
+ return " ".join(opt.usage() for opt in opts) |
|
| 165 |
+ |
|
| 126 | 166 |
|
| 127 | 167 |
@dataclasses.dataclass(frozen=True) |
| 128 | 168 |
class CLIArgument: |
| ... | ... |
@@ -133,6 +173,17 @@ class CLIArgument: |
| 133 | 173 |
choices: tuple[str, ...] | None = None |
| 134 | 174 |
"""The list of possible values, if applicable.""" |
| 135 | 175 |
|
| 176 |
+ def serialize(self) -> dict[str, Any]: |
|
| 177 |
+ return {
|
|
| 178 |
+ "name": self.name, |
|
| 179 |
+ "choices": tuple(self.choices) |
|
| 180 |
+ if self.choices is not None |
|
| 181 |
+ else None, |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ def usage(self) -> str: |
|
| 185 |
+ return self.name |
|
| 186 |
+ |
|
| 136 | 187 |
|
| 137 | 188 |
_canon_map_cache: MutableMapping[ |
| 138 | 189 |
CLISubcommand, Mapping[str | None, CLIOption | CLISubcommand] |
| ... | ... |
@@ -296,6 +347,24 @@ class CLITerminalSubcommand: |
| 296 | 347 |
_canon_map_cache[self] = calculate_canon_map(self) |
| 297 | 348 |
return _canon_map_cache[self] |
| 298 | 349 |
|
| 350 |
+ def serialize(self) -> dict[str, Any]: |
|
| 351 |
+ return {
|
|
| 352 |
+ "names": self.names, |
|
| 353 |
+ "contents": tuple( |
|
| 354 |
+ tuple(map(_serializer, content)) for content in self.contents |
|
| 355 |
+ ), |
|
| 356 |
+ "prolog": tuple(map(str, self.prolog)), |
|
| 357 |
+ "epilog": tuple(map(str, self.epilog)), |
|
| 358 |
+ } |
|
| 359 |
+ |
|
| 360 |
+ def usage(self) -> str: |
|
| 361 |
+ tokens = ( |
|
| 362 |
+ ["/".join(sorted(self.names, key=len))] |
|
| 363 |
+ + [group.usage() for group in self.contents[0]] |
|
| 364 |
+ + [arg.usage() for arg in self.contents[1]] |
|
| 365 |
+ ) |
|
| 366 |
+ return " ".join(tokens) |
|
| 367 |
+ |
|
| 299 | 368 |
|
| 300 | 369 |
@dataclasses.dataclass(frozen=True) |
| 301 | 370 |
class CLICompositeSubcommand: |
| ... | ... |
@@ -334,6 +403,25 @@ class CLICompositeSubcommand: |
| 334 | 403 |
_canon_map_cache[self] = calculate_canon_map(self) |
| 335 | 404 |
return _canon_map_cache[self] |
| 336 | 405 |
|
| 406 |
+ def serialize(self) -> dict[str, Any]: |
|
| 407 |
+ return {
|
|
| 408 |
+ "names": self.names, |
|
| 409 |
+ "contents": ( |
|
| 410 |
+ tuple(map(_serializer, self.contents[0])), |
|
| 411 |
+ tuple(map(_serializer, self.contents[1])), |
|
| 412 |
+ self.contents[2], |
|
| 413 |
+ ), |
|
| 414 |
+ "prolog": tuple(map(str, self.prolog)), |
|
| 415 |
+ "epilog": tuple(map(str, self.epilog)), |
|
| 416 |
+ } |
|
| 417 |
+ |
|
| 418 |
+ def usage(self) -> str: |
|
| 419 |
+ tokens = ["/".join(sorted(self.names, key=len))] |
|
| 420 |
+ tokens.extend(group.usage() for group in self.contents[0]) |
|
| 421 |
+ canon_subcommand_names = [cmd.names[0] for cmd in self.contents[1]] |
|
| 422 |
+ tokens.append("{{{}}}".format("|".join(canon_subcommand_names)))
|
|
| 423 |
+ return " ".join(tokens) |
|
| 424 |
+ |
|
| 337 | 425 |
|
| 338 | 426 |
ParsedCommandLineToken: TypeAlias = tuple[str, ...] |
| 339 | 427 |
ParsedCommandLineSection: TypeAlias = tuple[ParsedCommandLineToken, ...] |
| 340 | 428 |