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