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
41fn filter_candidates(subs: Vec<TomlSub>) -> impl Iterator<Item = TomlSub> {
42 subs.into_iter().filter(|s| !s.candidate.unwrap_or(false))
43}
44
45pub(super) fn build_sub(toml: TomlSub) -> SubSpec {
46 if let Some(handler_name) = toml.handler {
47 return SubSpec {
48 name: toml.name,
49 kind: DispatchKind::Custom { handler_name, doc_body: toml.doc_body },
50 };
51 }
52
53 if toml.allow_all.unwrap_or(false) {
54 return SubSpec {
55 name: toml.name,
56 kind: DispatchKind::Policy {
57 policy: allow_all_policy(),
58 level: toml.level.unwrap_or(TomlLevel::Inert).into(),
59 },
60 };
61 }
62
63 if let Some(sep) = toml.delegate_after {
64 return SubSpec {
65 name: toml.name,
66 kind: DispatchKind::DelegateAfterSeparator { separator: sep },
67 };
68 }
69
70 if let Some(skip) = toml.delegate_skip {
71 return SubSpec {
72 name: toml.name,
73 kind: DispatchKind::DelegateSkip { skip },
74 };
75 }
76
77 if !toml.sub.is_empty() {
78 return SubSpec {
79 name: toml.name,
80 kind: DispatchKind::Branching {
81 subs: filter_candidates(toml.sub).map(build_sub).collect(),
82 bare_flags: Vec::new(),
83 bare_ok: toml.nested_bare.unwrap_or(false),
84 pre_standalone: toml.standalone,
85 pre_valued: toml.valued,
86 first_arg: Vec::new(),
87 first_arg_level: SafetyLevel::Inert,
88 },
89 };
90 }
91
92 let policy = build_policy(
93 toml.standalone,
94 toml.valued,
95 toml.bare,
96 toml.max_positional,
97 toml.positional_style,
98 toml.numeric_dash,
99 );
100 let level: SafetyLevel = toml.level.unwrap_or(TomlLevel::Inert).into();
101
102 if !toml.write_flags.is_empty() {
103 return SubSpec {
104 name: toml.name,
105 kind: DispatchKind::WriteFlagged {
106 policy,
107 base_level: level,
108 write_flags: toml.write_flags,
109 },
110 };
111 }
112
113 if let Some(guard) = toml.guard {
114 let mut require_any = vec![guard];
115 if let Some(short) = toml.guard_short {
116 require_any.push(short);
117 }
118 return SubSpec {
119 name: toml.name,
120 kind: DispatchKind::RequireAny {
121 require_any,
122 policy,
123 level,
124 accept_bare_help: true,
125 },
126 };
127 }
128
129 if !toml.first_arg.is_empty() {
130 return SubSpec {
131 name: toml.name,
132 kind: DispatchKind::FirstArg {
133 patterns: toml.first_arg,
134 level,
135 },
136 };
137 }
138
139 if !toml.require_any.is_empty() {
140 return SubSpec {
141 name: toml.name,
142 kind: DispatchKind::RequireAny {
143 require_any: toml.require_any,
144 policy,
145 level,
146 accept_bare_help: false,
147 },
148 };
149 }
150
151 SubSpec {
152 name: toml.name,
153 kind: DispatchKind::Policy { policy, level },
154 }
155}
156
157#[allow(clippy::too_many_lines)]
158pub(super) fn build_command(toml: TomlCommand, category: &str) -> CommandSpec {
159 let cat = category.to_string();
160 let desc = toml.description.unwrap_or_default();
161 let researched_version = toml.researched_version;
162 if let Some(handler_name) = toml.handler {
163 return CommandSpec {
164 name: toml.name,
165 description: desc,
166 aliases: toml.aliases,
167 url: toml.url,
168 category: cat,
169 researched_version,
170 kind: DispatchKind::Custom { handler_name, doc_body: toml.doc_body },
171 };
172 }
173
174 if let Some(w) = toml.wrapper {
175 if !toml.sub.is_empty() || !toml.bare_flags.is_empty() {
176 let first_arg_level = toml.level.unwrap_or(TomlLevel::Inert).into();
177 return CommandSpec {
178 name: toml.name,
179 description: desc,
180 aliases: toml.aliases,
181 url: toml.url,
182 category: cat,
183 researched_version,
184 kind: DispatchKind::Branching {
185 bare_flags: toml.bare_flags,
186 subs: filter_candidates(toml.sub).map(build_sub).collect(),
187 pre_standalone: w.standalone,
188 pre_valued: w.valued,
189 bare_ok: toml.bare.unwrap_or(false),
190 first_arg: toml.first_arg,
191 first_arg_level,
192 },
193 };
194 }
195 return CommandSpec {
196 name: toml.name,
197 description: desc,
198 aliases: toml.aliases,
199 url: toml.url,
200 category: cat,
201 researched_version,
202 kind: DispatchKind::Wrapper {
203 standalone: w.standalone,
204 valued: w.valued,
205 positional_skip: w.positional_skip.unwrap_or(0),
206 separator: w.separator,
207 bare_ok: w.bare_ok.unwrap_or(false),
208 },
209 };
210 }
211
212 if !toml.sub.is_empty() || !toml.bare_flags.is_empty() {
213 let first_arg_level = toml.level.unwrap_or(TomlLevel::Inert).into();
214 return CommandSpec {
215 name: toml.name,
216 description: desc,
217 aliases: toml.aliases,
218 url: toml.url,
219 category: cat,
220 researched_version,
221 kind: DispatchKind::Branching {
222 bare_flags: toml.bare_flags,
223 subs: filter_candidates(toml.sub).map(build_sub).collect(),
224 pre_standalone: Vec::new(),
225 pre_valued: Vec::new(),
226 bare_ok: toml.bare.unwrap_or(false),
227 first_arg: toml.first_arg,
228 first_arg_level,
229 },
230 };
231 }
232
233 let policy = build_policy(
234 toml.standalone,
235 toml.valued,
236 toml.bare,
237 toml.max_positional,
238 toml.positional_style,
239 toml.numeric_dash,
240 );
241
242 let level = toml.level.unwrap_or(TomlLevel::Inert).into();
243
244 if !toml.first_arg.is_empty() {
245 return CommandSpec {
246 name: toml.name,
247 description: desc,
248 aliases: toml.aliases,
249 url: toml.url,
250 category: cat,
251 researched_version,
252 kind: DispatchKind::FirstArg {
253 patterns: toml.first_arg,
254 level,
255 },
256 };
257 }
258
259 if !toml.write_flags.is_empty() {
260 return CommandSpec {
261 name: toml.name,
262 description: desc,
263 aliases: toml.aliases,
264 url: toml.url,
265 category: cat,
266 researched_version,
267 kind: DispatchKind::WriteFlagged {
268 policy,
269 base_level: level,
270 write_flags: toml.write_flags,
271 },
272 };
273 }
274
275 if !toml.require_any.is_empty() {
276 return CommandSpec {
277 name: toml.name,
278 description: desc,
279 aliases: toml.aliases,
280 url: toml.url,
281 category: cat,
282 researched_version,
283 kind: DispatchKind::RequireAny {
284 require_any: toml.require_any,
285 policy,
286 level,
287 accept_bare_help: false,
288 },
289 };
290 }
291
292 CommandSpec {
293 name: toml.name,
294 description: desc,
295 aliases: toml.aliases,
296 url: toml.url,
297 category: cat,
298 researched_version,
299 kind: DispatchKind::Policy {
300 policy,
301 level,
302 },
303 }
304}
305
306pub fn load_toml(source: &str, category: &str) -> Vec<CommandSpec> {
307 let file: TomlFile = match toml::from_str(source) {
308 Ok(f) => f,
309 Err(e) => {
310 let preview: String = source.chars().take(80).collect();
311 panic!("invalid TOML command definition: {e}\n source begins: {preview}");
312 }
313 };
314 file.command.into_iter()
315 .filter(|cmd| !cmd.candidate.unwrap_or(false))
316 .map(|cmd| build_command(cmd, category))
317 .collect()
318}
319
320pub fn build_registry(specs: Vec<CommandSpec>) -> HashMap<String, CommandSpec> {
321 let mut map = HashMap::new();
322 for spec in specs {
323 for alias in &spec.aliases {
324 map.insert(alias.clone(), CommandSpec {
325 name: spec.name.clone(),
326 description: spec.description.clone(),
327 aliases: vec![],
328 url: spec.url.clone(),
329 category: spec.category.clone(),
330 researched_version: spec.researched_version.clone(),
331 kind: spec.kind.clone(),
332 });
333 }
334 map.insert(spec.name.clone(), spec);
335 }
336 map
337}