Skip to main content

safe_chains/registry/
build.rs

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}