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