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
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}