bash-classify 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bash_classify/__init__.py +14 -0
- bash_classify/__main__.py +3 -0
- bash_classify/classifier.py +244 -0
- bash_classify/cli.py +110 -0
- bash_classify/commands/apt.yaml +23 -0
- bash_classify/commands/awk.yaml +5 -0
- bash_classify/commands/basename.yaml +5 -0
- bash_classify/commands/bash.yaml +18 -0
- bash_classify/commands/brew.yaml +25 -0
- bash_classify/commands/builtin.yaml +6 -0
- bash_classify/commands/cargo.yaml +29 -0
- bash_classify/commands/cat.yaml +5 -0
- bash_classify/commands/chgrp.yaml +8 -0
- bash_classify/commands/chmod.yaml +8 -0
- bash_classify/commands/chown.yaml +8 -0
- bash_classify/commands/column.yaml +5 -0
- bash_classify/commands/comm.yaml +5 -0
- bash_classify/commands/command.yaml +10 -0
- bash_classify/commands/cp.yaml +5 -0
- bash_classify/commands/curl.yaml +28 -0
- bash_classify/commands/cut.yaml +5 -0
- bash_classify/commands/date.yaml +8 -0
- bash_classify/commands/df.yaml +5 -0
- bash_classify/commands/diff.yaml +5 -0
- bash_classify/commands/dig.yaml +5 -0
- bash_classify/commands/dirname.yaml +5 -0
- bash_classify/commands/docker.yaml +152 -0
- bash_classify/commands/du.yaml +5 -0
- bash_classify/commands/echo.yaml +5 -0
- bash_classify/commands/env.yaml +14 -0
- bash_classify/commands/expand.yaml +5 -0
- bash_classify/commands/false.yaml +5 -0
- bash_classify/commands/file.yaml +5 -0
- bash_classify/commands/find.yaml +27 -0
- bash_classify/commands/fmt.yaml +5 -0
- bash_classify/commands/fold.yaml +5 -0
- bash_classify/commands/free.yaml +5 -0
- bash_classify/commands/gawk.yaml +5 -0
- bash_classify/commands/git.yaml +441 -0
- bash_classify/commands/go.yaml +38 -0
- bash_classify/commands/grep.yaml +5 -0
- bash_classify/commands/groups.yaml +5 -0
- bash_classify/commands/gunzip.yaml +5 -0
- bash_classify/commands/gzip.yaml +5 -0
- bash_classify/commands/head.yaml +5 -0
- bash_classify/commands/helm.yaml +127 -0
- bash_classify/commands/hostname.yaml +5 -0
- bash_classify/commands/id.yaml +5 -0
- bash_classify/commands/install.yaml +5 -0
- bash_classify/commands/join.yaml +5 -0
- bash_classify/commands/journalctl.yaml +12 -0
- bash_classify/commands/jq.yaml +5 -0
- bash_classify/commands/kill.yaml +5 -0
- bash_classify/commands/killall.yaml +5 -0
- bash_classify/commands/kubectl.yaml +190 -0
- bash_classify/commands/less.yaml +5 -0
- bash_classify/commands/ln.yaml +5 -0
- bash_classify/commands/ls.yaml +5 -0
- bash_classify/commands/make.yaml +19 -0
- bash_classify/commands/md5sum.yaml +5 -0
- bash_classify/commands/mkdir.yaml +5 -0
- bash_classify/commands/more.yaml +5 -0
- bash_classify/commands/mv.yaml +5 -0
- bash_classify/commands/netstat.yaml +5 -0
- bash_classify/commands/nice.yaml +9 -0
- bash_classify/commands/nl.yaml +5 -0
- bash_classify/commands/node.yaml +5 -0
- bash_classify/commands/nohup.yaml +6 -0
- bash_classify/commands/npm.yaml +65 -0
- bash_classify/commands/nslookup.yaml +5 -0
- bash_classify/commands/paste.yaml +5 -0
- bash_classify/commands/ping.yaml +5 -0
- bash_classify/commands/pip.yaml +29 -0
- bash_classify/commands/pip3.yaml +29 -0
- bash_classify/commands/pkill.yaml +5 -0
- bash_classify/commands/poetry.yaml +74 -0
- bash_classify/commands/printenv.yaml +5 -0
- bash_classify/commands/printf.yaml +5 -0
- bash_classify/commands/ps.yaml +5 -0
- bash_classify/commands/pwd.yaml +5 -0
- bash_classify/commands/python.yaml +5 -0
- bash_classify/commands/python3.yaml +5 -0
- bash_classify/commands/read.yaml +5 -0
- bash_classify/commands/readlink.yaml +5 -0
- bash_classify/commands/realpath.yaml +5 -0
- bash_classify/commands/rev.yaml +5 -0
- bash_classify/commands/rm.yaml +5 -0
- bash_classify/commands/rsync.yaml +10 -0
- bash_classify/commands/scp.yaml +5 -0
- bash_classify/commands/sed.yaml +12 -0
- bash_classify/commands/seq.yaml +5 -0
- bash_classify/commands/sh.yaml +12 -0
- bash_classify/commands/sha1sum.yaml +5 -0
- bash_classify/commands/sha256sum.yaml +5 -0
- bash_classify/commands/sort.yaml +8 -0
- bash_classify/commands/ss.yaml +5 -0
- bash_classify/commands/ssh.yaml +39 -0
- bash_classify/commands/stat.yaml +5 -0
- bash_classify/commands/strace.yaml +18 -0
- bash_classify/commands/sudo.yaml +22 -0
- bash_classify/commands/systemctl.yaml +56 -0
- bash_classify/commands/tac.yaml +5 -0
- bash_classify/commands/tail.yaml +5 -0
- bash_classify/commands/tar.yaml +22 -0
- bash_classify/commands/tee.yaml +5 -0
- bash_classify/commands/terraform.yaml +68 -0
- bash_classify/commands/test.yaml +5 -0
- bash_classify/commands/time.yaml +6 -0
- bash_classify/commands/timeout.yaml +15 -0
- bash_classify/commands/top.yaml +5 -0
- bash_classify/commands/touch.yaml +5 -0
- bash_classify/commands/tr.yaml +5 -0
- bash_classify/commands/traceroute.yaml +5 -0
- bash_classify/commands/true.yaml +5 -0
- bash_classify/commands/type.yaml +5 -0
- bash_classify/commands/uname.yaml +5 -0
- bash_classify/commands/unexpand.yaml +5 -0
- bash_classify/commands/uniq.yaml +5 -0
- bash_classify/commands/unzip.yaml +7 -0
- bash_classify/commands/uptime.yaml +5 -0
- bash_classify/commands/uv.yaml +117 -0
- bash_classify/commands/wc.yaml +5 -0
- bash_classify/commands/wget.yaml +5 -0
- bash_classify/commands/whereis.yaml +5 -0
- bash_classify/commands/which.yaml +5 -0
- bash_classify/commands/whoami.yaml +5 -0
- bash_classify/commands/xargs.yaml +25 -0
- bash_classify/commands/xmllint.yaml +5 -0
- bash_classify/commands/yes.yaml +5 -0
- bash_classify/commands/yq.yaml +8 -0
- bash_classify/commands/zip.yaml +5 -0
- bash_classify/commands/zsh.yaml +12 -0
- bash_classify/database.py +257 -0
- bash_classify/matcher.py +702 -0
- bash_classify/models.py +148 -0
- bash_classify/parser.py +525 -0
- bash_classify/py.typed +0 -0
- bash_classify-0.1.0.dist-info/METADATA +147 -0
- bash_classify-0.1.0.dist-info/RECORD +142 -0
- bash_classify-0.1.0.dist-info/WHEEL +4 -0
- bash_classify-0.1.0.dist-info/entry_points.txt +2 -0
- bash_classify-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# $schema: ../../../schemas/command.schema.json
|
|
2
|
+
command: uv
|
|
3
|
+
description: "An extremely fast Python package manager"
|
|
4
|
+
strict: false
|
|
5
|
+
subcommands:
|
|
6
|
+
run:
|
|
7
|
+
classification: DANGEROUS
|
|
8
|
+
strict: false
|
|
9
|
+
init:
|
|
10
|
+
classification: WRITE
|
|
11
|
+
strict: false
|
|
12
|
+
add:
|
|
13
|
+
classification: WRITE
|
|
14
|
+
strict: false
|
|
15
|
+
remove:
|
|
16
|
+
classification: WRITE
|
|
17
|
+
strict: false
|
|
18
|
+
version:
|
|
19
|
+
classification: READONLY
|
|
20
|
+
strict: false
|
|
21
|
+
sync:
|
|
22
|
+
classification: WRITE
|
|
23
|
+
strict: false
|
|
24
|
+
lock:
|
|
25
|
+
classification: WRITE
|
|
26
|
+
strict: false
|
|
27
|
+
export:
|
|
28
|
+
classification: READONLY
|
|
29
|
+
strict: false
|
|
30
|
+
tree:
|
|
31
|
+
classification: READONLY
|
|
32
|
+
strict: false
|
|
33
|
+
format:
|
|
34
|
+
classification: WRITE
|
|
35
|
+
strict: false
|
|
36
|
+
tool:
|
|
37
|
+
strict: false
|
|
38
|
+
subcommands:
|
|
39
|
+
install:
|
|
40
|
+
classification: WRITE
|
|
41
|
+
strict: false
|
|
42
|
+
uninstall:
|
|
43
|
+
classification: WRITE
|
|
44
|
+
strict: false
|
|
45
|
+
list:
|
|
46
|
+
classification: READONLY
|
|
47
|
+
run:
|
|
48
|
+
classification: DANGEROUS
|
|
49
|
+
strict: false
|
|
50
|
+
dir:
|
|
51
|
+
classification: READONLY
|
|
52
|
+
python:
|
|
53
|
+
strict: false
|
|
54
|
+
subcommands:
|
|
55
|
+
install:
|
|
56
|
+
classification: WRITE
|
|
57
|
+
strict: false
|
|
58
|
+
uninstall:
|
|
59
|
+
classification: WRITE
|
|
60
|
+
strict: false
|
|
61
|
+
list:
|
|
62
|
+
classification: READONLY
|
|
63
|
+
find:
|
|
64
|
+
classification: READONLY
|
|
65
|
+
strict: false
|
|
66
|
+
dir:
|
|
67
|
+
classification: READONLY
|
|
68
|
+
pin:
|
|
69
|
+
classification: WRITE
|
|
70
|
+
strict: false
|
|
71
|
+
pip:
|
|
72
|
+
strict: false
|
|
73
|
+
subcommands:
|
|
74
|
+
install:
|
|
75
|
+
classification: WRITE
|
|
76
|
+
strict: false
|
|
77
|
+
uninstall:
|
|
78
|
+
classification: WRITE
|
|
79
|
+
strict: false
|
|
80
|
+
list:
|
|
81
|
+
classification: READONLY
|
|
82
|
+
strict: false
|
|
83
|
+
show:
|
|
84
|
+
classification: READONLY
|
|
85
|
+
strict: false
|
|
86
|
+
freeze:
|
|
87
|
+
classification: READONLY
|
|
88
|
+
check:
|
|
89
|
+
classification: READONLY
|
|
90
|
+
compile:
|
|
91
|
+
classification: WRITE
|
|
92
|
+
strict: false
|
|
93
|
+
venv:
|
|
94
|
+
classification: WRITE
|
|
95
|
+
strict: false
|
|
96
|
+
build:
|
|
97
|
+
classification: WRITE
|
|
98
|
+
strict: false
|
|
99
|
+
publish:
|
|
100
|
+
classification: DANGEROUS
|
|
101
|
+
strict: false
|
|
102
|
+
cache:
|
|
103
|
+
strict: false
|
|
104
|
+
subcommands:
|
|
105
|
+
clean:
|
|
106
|
+
classification: WRITE
|
|
107
|
+
prune:
|
|
108
|
+
classification: WRITE
|
|
109
|
+
dir:
|
|
110
|
+
classification: READONLY
|
|
111
|
+
self:
|
|
112
|
+
strict: false
|
|
113
|
+
subcommands:
|
|
114
|
+
update:
|
|
115
|
+
classification: WRITE
|
|
116
|
+
help:
|
|
117
|
+
classification: READONLY
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# $schema: ../../../schemas/command.schema.json
|
|
2
|
+
command: xargs
|
|
3
|
+
description: "Build and execute command lines from standard input"
|
|
4
|
+
classification: READONLY
|
|
5
|
+
delegates_to:
|
|
6
|
+
mode: rest_are_argv
|
|
7
|
+
options:
|
|
8
|
+
-I: {takes_value: true}
|
|
9
|
+
--replace: {takes_value: true, aliases: [-I]}
|
|
10
|
+
-L: {takes_value: true}
|
|
11
|
+
--max-lines: {takes_value: true, aliases: [-L]}
|
|
12
|
+
-n: {takes_value: true}
|
|
13
|
+
--max-args: {takes_value: true, aliases: [-n]}
|
|
14
|
+
-P: {takes_value: true}
|
|
15
|
+
--max-procs: {takes_value: true, aliases: [-P]}
|
|
16
|
+
-d: {takes_value: true}
|
|
17
|
+
--delimiter: {takes_value: true, aliases: [-d]}
|
|
18
|
+
-0: {}
|
|
19
|
+
--null: {}
|
|
20
|
+
--no-run-if-empty: {}
|
|
21
|
+
-r: {}
|
|
22
|
+
-t: {}
|
|
23
|
+
--verbose: {}
|
|
24
|
+
-p: {}
|
|
25
|
+
--interactive: {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# $schema: ../../../schemas/command.schema.json
|
|
2
|
+
command: zsh
|
|
3
|
+
description: "Z shell command interpreter"
|
|
4
|
+
classification: DANGEROUS
|
|
5
|
+
delegates_to:
|
|
6
|
+
mode: flag_value_is_expression
|
|
7
|
+
flag: -c
|
|
8
|
+
options:
|
|
9
|
+
-c: {takes_value: true}
|
|
10
|
+
-e: {}
|
|
11
|
+
-x: {}
|
|
12
|
+
-i: {}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Command database loading from YAML files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from .models import Classification, CommandDef, DelegationConfig, DelegationMode, OptionDef
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_default_commands_dir() -> Path:
|
|
15
|
+
"""Return the default commands directory (bundled with the package)."""
|
|
16
|
+
return Path(__file__).parent / "commands"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_user_commands_dir() -> Path:
|
|
20
|
+
"""Get the user's custom commands directory."""
|
|
21
|
+
config_dir = os.environ.get("BASH_CLASSIFY_CONFIG_DIR")
|
|
22
|
+
if config_dir:
|
|
23
|
+
return Path(config_dir) / "commands"
|
|
24
|
+
return Path.home() / ".config" / "bash-classify" / "commands"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CommandDatabase(dict[str, CommandDef]):
|
|
28
|
+
"""Lazy-loading command database that behaves as a dict.
|
|
29
|
+
|
|
30
|
+
YAML files are only parsed when a specific command is first accessed.
|
|
31
|
+
From the outside, this is indistinguishable from a regular dict.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, builtin_dir: Path, user_dir: Path | None = None):
|
|
35
|
+
super().__init__()
|
|
36
|
+
# Map command name -> yaml file path (cheap: just filename listing)
|
|
37
|
+
self._files: dict[str, Path] = {}
|
|
38
|
+
|
|
39
|
+
# Index built-in files
|
|
40
|
+
if builtin_dir.is_dir():
|
|
41
|
+
for yaml_file in builtin_dir.glob("*.yaml"):
|
|
42
|
+
name = _command_name_from_file(yaml_file)
|
|
43
|
+
self._files[name] = yaml_file
|
|
44
|
+
|
|
45
|
+
# Index user files (override built-in)
|
|
46
|
+
if user_dir and user_dir.is_dir():
|
|
47
|
+
for yaml_file in user_dir.glob("*.yaml"):
|
|
48
|
+
name = _command_name_from_file(yaml_file)
|
|
49
|
+
self._files[name] = yaml_file # user overrides built-in
|
|
50
|
+
|
|
51
|
+
def __getitem__(self, key: str) -> CommandDef:
|
|
52
|
+
try:
|
|
53
|
+
return super().__getitem__(key)
|
|
54
|
+
except KeyError:
|
|
55
|
+
pass
|
|
56
|
+
if key not in self._files:
|
|
57
|
+
raise KeyError(key)
|
|
58
|
+
# Lazy load and cache in the underlying dict
|
|
59
|
+
command_def = _load_command_file(self._files[key])
|
|
60
|
+
super().__setitem__(key, command_def)
|
|
61
|
+
return command_def
|
|
62
|
+
|
|
63
|
+
def get(self, key: str, default: CommandDef | None = None) -> CommandDef | None: # type: ignore[override]
|
|
64
|
+
try:
|
|
65
|
+
return self[key]
|
|
66
|
+
except KeyError:
|
|
67
|
+
return default
|
|
68
|
+
|
|
69
|
+
def __contains__(self, key: object) -> bool:
|
|
70
|
+
return super().__contains__(key) or key in self._files
|
|
71
|
+
|
|
72
|
+
def __iter__(self) -> Iterator[str]:
|
|
73
|
+
return iter(self._files)
|
|
74
|
+
|
|
75
|
+
def __len__(self) -> int:
|
|
76
|
+
return len(self._files)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _command_name_from_file(yaml_file: Path) -> str:
|
|
80
|
+
"""Extract command name from YAML filename.
|
|
81
|
+
|
|
82
|
+
Handles special cases: true.yaml, false.yaml, yes.yaml where
|
|
83
|
+
the stem would be parsed as boolean by YAML but the filename is fine.
|
|
84
|
+
"""
|
|
85
|
+
return yaml_file.stem
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _load_command_file(yaml_file: Path) -> CommandDef:
|
|
89
|
+
"""Parse a single YAML command file into a CommandDef."""
|
|
90
|
+
try:
|
|
91
|
+
with open(yaml_file) as f:
|
|
92
|
+
data = yaml.safe_load(f)
|
|
93
|
+
|
|
94
|
+
if data is None:
|
|
95
|
+
raise ValueError("Empty YAML file")
|
|
96
|
+
|
|
97
|
+
if not isinstance(data, dict):
|
|
98
|
+
raise ValueError(f"Expected a YAML mapping, got {type(data).__name__}")
|
|
99
|
+
|
|
100
|
+
command_name = _yaml_str(data["command"])
|
|
101
|
+
return _parse_command_def(data, command_name)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise ValueError(f"Error loading {yaml_file}: {e}") from e
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def load_database(commands_dir: Path | None = None) -> CommandDatabase:
|
|
107
|
+
"""Load command database with lazy per-command loading.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
commands_dir: Explicit commands directory. When provided,
|
|
111
|
+
user overrides are NOT loaded.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A CommandDatabase mapping command names to their CommandDef definitions.
|
|
115
|
+
"""
|
|
116
|
+
builtin_dir = commands_dir or get_default_commands_dir()
|
|
117
|
+
user_dir = None
|
|
118
|
+
if commands_dir is None:
|
|
119
|
+
candidate = get_user_commands_dir()
|
|
120
|
+
if candidate.is_dir():
|
|
121
|
+
user_dir = candidate
|
|
122
|
+
return CommandDatabase(builtin_dir, user_dir)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _load_commands_from_dir(commands_dir: Path) -> dict[str, CommandDef]:
|
|
126
|
+
"""Load all command definitions from YAML files in the given directory.
|
|
127
|
+
|
|
128
|
+
This eagerly loads all commands at once. Useful for schema validation
|
|
129
|
+
and tests that need to iterate all commands.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
commands_dir: Path to directory containing YAML command definitions.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
A dict mapping command names to their CommandDef definitions.
|
|
136
|
+
"""
|
|
137
|
+
database: dict[str, CommandDef] = {}
|
|
138
|
+
|
|
139
|
+
for yaml_file in sorted(commands_dir.glob("*.yaml")):
|
|
140
|
+
try:
|
|
141
|
+
with open(yaml_file) as f:
|
|
142
|
+
data = yaml.safe_load(f)
|
|
143
|
+
|
|
144
|
+
if data is None:
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
if not isinstance(data, dict):
|
|
148
|
+
raise ValueError(f"Expected a YAML mapping, got {type(data).__name__}")
|
|
149
|
+
|
|
150
|
+
command_name = _yaml_str(data["command"]) # YAML parses true/false as booleans
|
|
151
|
+
command_def = _parse_command_def(data, command_name)
|
|
152
|
+
database[command_name] = command_def
|
|
153
|
+
except Exception as e:
|
|
154
|
+
raise ValueError(f"Error loading {yaml_file}: {e}") from e
|
|
155
|
+
|
|
156
|
+
return database
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _yaml_str(value: object) -> str:
|
|
160
|
+
"""Convert a YAML value to string, handling booleans correctly.
|
|
161
|
+
|
|
162
|
+
YAML parses bare `true`/`false` as Python booleans, but we need them as
|
|
163
|
+
lowercase strings (e.g. for the `true` and `false` commands).
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(value, bool):
|
|
166
|
+
return "true" if value else "false"
|
|
167
|
+
return str(value)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _parse_command_def(data: dict, command_name: str) -> CommandDef:
|
|
171
|
+
"""Parse a raw YAML dict into a CommandDef structure."""
|
|
172
|
+
classification = _parse_classification(data.get("classification"))
|
|
173
|
+
|
|
174
|
+
global_options = _parse_options(data.get("global_options", {}))
|
|
175
|
+
options = _parse_options(data.get("options", {}))
|
|
176
|
+
subcommands = _parse_subcommands(data.get("subcommands", {}))
|
|
177
|
+
delegates_to = _parse_delegation_config(data.get("delegates_to"))
|
|
178
|
+
strict = data.get("strict", True)
|
|
179
|
+
|
|
180
|
+
return CommandDef(
|
|
181
|
+
command=command_name,
|
|
182
|
+
classification=classification,
|
|
183
|
+
global_options=global_options,
|
|
184
|
+
subcommands=subcommands,
|
|
185
|
+
options=options,
|
|
186
|
+
strict=strict,
|
|
187
|
+
delegates_to=delegates_to,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _parse_classification(value: str | None) -> Classification | None:
|
|
192
|
+
"""Parse a classification string into a Classification enum."""
|
|
193
|
+
if value is None:
|
|
194
|
+
return None
|
|
195
|
+
return Classification(value)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _parse_options(raw: dict | None) -> dict[str, OptionDef]:
|
|
199
|
+
"""Parse an options map, expanding aliases so each alias maps to the same OptionDef."""
|
|
200
|
+
if not raw:
|
|
201
|
+
return {}
|
|
202
|
+
|
|
203
|
+
options: dict[str, OptionDef] = {}
|
|
204
|
+
|
|
205
|
+
for name, props in raw.items():
|
|
206
|
+
if props is None:
|
|
207
|
+
props = {}
|
|
208
|
+
|
|
209
|
+
option_def = OptionDef(
|
|
210
|
+
takes_value=props.get("takes_value", False),
|
|
211
|
+
aliases=props.get("aliases", []),
|
|
212
|
+
overrides=_parse_classification(props.get("overrides")),
|
|
213
|
+
captures_directory=props.get("captures_directory", False),
|
|
214
|
+
delegates_to=_parse_delegation_config(props.get("delegates_to")),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Store under the primary name
|
|
218
|
+
options[name] = option_def
|
|
219
|
+
|
|
220
|
+
# Expand aliases so lookup works by any name
|
|
221
|
+
for alias in option_def.aliases:
|
|
222
|
+
options[alias] = option_def
|
|
223
|
+
|
|
224
|
+
return options
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _parse_subcommands(raw: dict | None) -> dict[str, CommandDef]:
|
|
228
|
+
"""Parse a subcommands map recursively."""
|
|
229
|
+
if not raw:
|
|
230
|
+
return {}
|
|
231
|
+
|
|
232
|
+
subcommands: dict[str, CommandDef] = {}
|
|
233
|
+
|
|
234
|
+
for name, props in raw.items():
|
|
235
|
+
if props is None:
|
|
236
|
+
props = {}
|
|
237
|
+
subcommands[name] = _parse_command_def(props, name)
|
|
238
|
+
|
|
239
|
+
return subcommands
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _parse_delegation_config(raw: dict | None) -> DelegationConfig | None:
|
|
243
|
+
"""Parse a delegates_to configuration."""
|
|
244
|
+
if not raw:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
mode = DelegationMode(raw["mode"])
|
|
248
|
+
min_class = _parse_classification(raw.get("min_classification"))
|
|
249
|
+
|
|
250
|
+
return DelegationConfig(
|
|
251
|
+
mode=mode,
|
|
252
|
+
separator=raw.get("separator"),
|
|
253
|
+
terminator=raw.get("terminator"),
|
|
254
|
+
flag=raw.get("flag"),
|
|
255
|
+
strip_assignments=raw.get("strip_assignments", False),
|
|
256
|
+
min_classification=min_class,
|
|
257
|
+
)
|