1use std::collections::HashMap;
2
3use crate::policy::FlagStyle;
4use crate::verdict::SafetyLevel;
5
6use super::types::*;
7
8pub(super) fn build_policy(
9 standalone: Vec<String>,
10 valued: Vec<String>,
11 bare: Option<bool>,
12 max_positional: Option<usize>,
13 positional_style: Option<bool>,
14 numeric_dash: Option<bool>,
15) -> OwnedPolicy {
16 OwnedPolicy {
17 standalone,
18 valued,
19 bare: bare.unwrap_or(true),
20 max_positional,
21 flag_style: if positional_style.unwrap_or(false) {
22 FlagStyle::Positional
23 } else {
24 FlagStyle::Strict
25 },
26 numeric_dash: numeric_dash.unwrap_or(false),
27 }
28}
29
30fn allow_all_policy() -> OwnedPolicy {
31 OwnedPolicy {
32 standalone: Vec::new(),
33 valued: Vec::new(),
34 bare: true,
35 max_positional: None,
36 flag_style: FlagStyle::Positional,
37 numeric_dash: false,
38 }
39}
40
41pub(super) fn build_sub(toml: TomlSub) -> SubSpec {
42 if let Some(handler_name) = toml.handler {
43 return SubSpec {
44 name: toml.name,
45 kind: DispatchKind::Custom { handler_name },
46 };
47 }
48
49 if toml.allow_all.unwrap_or(false) {
50 return SubSpec {
51 name: toml.name,
52 kind: DispatchKind::Policy {
53 policy: allow_all_policy(),
54 level: toml.level.unwrap_or(TomlLevel::Inert).into(),
55 },
56 };
57 }
58
59 if let Some(sep) = toml.delegate_after {
60 return SubSpec {
61 name: toml.name,
62 kind: DispatchKind::DelegateAfterSeparator { separator: sep },
63 };
64 }
65
66 if let Some(skip) = toml.delegate_skip {
67 return SubSpec {
68 name: toml.name,
69 kind: DispatchKind::DelegateSkip { skip },
70 };
71 }
72
73 if !toml.sub.is_empty() {
74 return SubSpec {
75 name: toml.name,
76 kind: DispatchKind::Branching {
77 subs: toml.sub.into_iter().map(build_sub).collect(),
78 bare_flags: Vec::new(),
79 bare_ok: toml.nested_bare.unwrap_or(false),
80 pre_standalone: toml.standalone,
81 pre_valued: toml.valued,
82 first_arg: Vec::new(),
83 first_arg_level: SafetyLevel::Inert,
84 },
85 };
86 }
87
88 let policy = build_policy(
89 toml.standalone,
90 toml.valued,
91 toml.bare,
92 toml.max_positional,
93 toml.positional_style,
94 toml.numeric_dash,
95 );
96 let level: SafetyLevel = toml.level.unwrap_or(TomlLevel::Inert).into();
97
98 if !toml.write_flags.is_empty() {
99 return SubSpec {
100 name: toml.name,
101 kind: DispatchKind::WriteFlagged {
102 policy,
103 base_level: level,
104 write_flags: toml.write_flags,
105 },
106 };
107 }
108
109 if let Some(guard) = toml.guard {
110 let mut require_any = vec![guard];
111 if let Some(short) = toml.guard_short {
112 require_any.push(short);
113 }
114 return SubSpec {
115 name: toml.name,
116 kind: DispatchKind::RequireAny {
117 require_any,
118 policy,
119 level,
120 accept_bare_help: true,
121 },
122 };
123 }
124
125 if !toml.first_arg.is_empty() {
126 return SubSpec {
127 name: toml.name,
128 kind: DispatchKind::FirstArg {
129 patterns: toml.first_arg,
130 level,
131 },
132 };
133 }
134
135 if !toml.require_any.is_empty() {
136 return SubSpec {
137 name: toml.name,
138 kind: DispatchKind::RequireAny {
139 require_any: toml.require_any,
140 policy,
141 level,
142 accept_bare_help: false,
143 },
144 };
145 }
146
147 SubSpec {
148 name: toml.name,
149 kind: DispatchKind::Policy { policy, level },
150 }
151}
152
153pub(super) fn build_command(toml: TomlCommand) -> CommandSpec {
154 if let Some(handler_name) = toml.handler {
155 return CommandSpec {
156 name: toml.name,
157 aliases: toml.aliases,
158 url: toml.url,
159 kind: DispatchKind::Custom { handler_name },
160 };
161 }
162
163 if let Some(w) = toml.wrapper {
164 if !toml.sub.is_empty() || !toml.bare_flags.is_empty() {
165 let first_arg_level = toml.level.unwrap_or(TomlLevel::Inert).into();
166 return CommandSpec {
167 name: toml.name,
168 aliases: toml.aliases,
169 url: toml.url,
170 kind: DispatchKind::Branching {
171 bare_flags: toml.bare_flags,
172 subs: toml.sub.into_iter().map(build_sub).collect(),
173 pre_standalone: w.standalone,
174 pre_valued: w.valued,
175 bare_ok: toml.bare.unwrap_or(false),
176 first_arg: toml.first_arg,
177 first_arg_level,
178 },
179 };
180 }
181 return CommandSpec {
182 name: toml.name,
183 aliases: toml.aliases,
184 url: toml.url,
185 kind: DispatchKind::Wrapper {
186 standalone: w.standalone,
187 valued: w.valued,
188 positional_skip: w.positional_skip.unwrap_or(0),
189 separator: w.separator,
190 bare_ok: w.bare_ok.unwrap_or(false),
191 },
192 };
193 }
194
195 if !toml.sub.is_empty() || !toml.bare_flags.is_empty() {
196 let first_arg_level = toml.level.unwrap_or(TomlLevel::Inert).into();
197 return CommandSpec {
198 name: toml.name,
199 aliases: toml.aliases,
200 url: toml.url,
201 kind: DispatchKind::Branching {
202 bare_flags: toml.bare_flags,
203 subs: toml.sub.into_iter().map(build_sub).collect(),
204 pre_standalone: Vec::new(),
205 pre_valued: Vec::new(),
206 bare_ok: toml.bare.unwrap_or(false),
207 first_arg: toml.first_arg,
208 first_arg_level,
209 },
210 };
211 }
212
213 let policy = build_policy(
214 toml.standalone,
215 toml.valued,
216 toml.bare,
217 toml.max_positional,
218 toml.positional_style,
219 toml.numeric_dash,
220 );
221
222 let level = toml.level.unwrap_or(TomlLevel::Inert).into();
223
224 if !toml.first_arg.is_empty() {
225 return CommandSpec {
226 name: toml.name,
227 aliases: toml.aliases,
228 url: toml.url,
229 kind: DispatchKind::FirstArg {
230 patterns: toml.first_arg,
231 level,
232 },
233 };
234 }
235
236 if !toml.require_any.is_empty() {
237 return CommandSpec {
238 name: toml.name,
239 aliases: toml.aliases,
240 url: toml.url,
241 kind: DispatchKind::RequireAny {
242 require_any: toml.require_any,
243 policy,
244 level,
245 accept_bare_help: false,
246 },
247 };
248 }
249
250 CommandSpec {
251 name: toml.name,
252 aliases: toml.aliases,
253 url: toml.url,
254 kind: DispatchKind::Policy {
255 policy,
256 level,
257 },
258 }
259}
260
261pub fn load_toml(source: &str) -> Vec<CommandSpec> {
262 let file: TomlFile = match toml::from_str(source) {
263 Ok(f) => f,
264 Err(e) => {
265 let preview: String = source.chars().take(80).collect();
266 panic!("invalid TOML command definition: {e}\n source begins: {preview}");
267 }
268 };
269 file.command.into_iter().map(build_command).collect()
270}
271
272pub fn build_registry(specs: Vec<CommandSpec>) -> HashMap<String, CommandSpec> {
273 let mut map = HashMap::new();
274 for spec in specs {
275 for alias in &spec.aliases {
276 map.insert(alias.clone(), CommandSpec {
277 name: spec.name.clone(),
278 aliases: vec![],
279 url: spec.url.clone(),
280 kind: spec.kind.clone(),
281 });
282 }
283 map.insert(spec.name.clone(), spec);
284 }
285 map
286}