1#[derive(Debug, PartialEq)]
2pub(crate) struct VisudoOptions {
3 pub(crate) file: Option<String>,
4 pub(crate) owner: bool,
5 pub(crate) perms: bool,
6 pub(crate) action: VisudoAction,
7}
8
9impl Default for VisudoOptions {
10 fn default() -> Self {
11 Self {
12 file: None,
13 owner: false,
14 perms: false,
15 action: VisudoAction::Run,
16 }
17 }
18}
19
20#[derive(Debug, PartialEq)]
21pub(crate) enum VisudoAction {
22 Help,
23 Version,
24 Check,
25 Run,
26}
27
28type OptionSetter = fn(&mut VisudoOptions, Option<String>) -> Result<(), String>;
29
30struct VisudoOption {
31 short: char,
32 long: &'static str,
33 takes_argument: bool,
34 set: OptionSetter,
35}
36
37impl VisudoOptions {
38 const VISUDO_OPTIONS: &'static [VisudoOption] = &[
39 VisudoOption {
40 short: 'c',
41 long: "check",
42 takes_argument: false,
43 set: |options, _| {
44 options.action = VisudoAction::Check;
45 Ok(())
46 },
47 },
48 VisudoOption {
49 short: 'f',
50 long: "file",
51 takes_argument: true,
52 set: |options, argument| {
53 options.file = Some(argument.ok_or("option requires an argument -- 'f'")?);
54 Ok(())
55 },
56 },
57 VisudoOption {
58 short: 'h',
59 long: "help",
60 takes_argument: false,
61 set: |options, _| {
62 options.action = VisudoAction::Help;
63 Ok(())
64 },
65 },
66 VisudoOption {
67 short: 'I',
68 long: "no-includes",
69 takes_argument: false,
70 set: |_, _| Ok(()),
71 },
73 VisudoOption {
74 short: 'q',
75 long: "quiet",
76 takes_argument: false,
77 set: |_, _| Ok(()),
78 },
80 VisudoOption {
81 short: 's',
82 long: "strict",
83 takes_argument: false,
84 set: |_, _| Ok(()),
85 },
87 VisudoOption {
88 short: 'V',
89 long: "version",
90 takes_argument: false,
91 set: |options, _| {
92 options.action = VisudoAction::Version;
93 Ok(())
94 },
95 },
96 VisudoOption {
97 short: 'O',
98 long: "owner",
99 takes_argument: false,
100 set: |options, _| {
101 options.owner = true;
102 Ok(())
103 },
104 },
105 VisudoOption {
106 short: 'P',
107 long: "perms",
108 takes_argument: false,
109 set: |options, _| {
110 options.perms = true;
111 Ok(())
112 },
113 },
114 ];
115
116 pub(crate) fn from_env() -> Result<VisudoOptions, String> {
117 let args = std::env::args().collect();
118
119 Self::parse_arguments(args)
120 }
121
122 pub(crate) fn parse_arguments(arguments: Vec<String>) -> Result<VisudoOptions, String> {
124 let mut options: VisudoOptions = VisudoOptions::default();
125 let mut arg_iter = arguments.into_iter().skip(1);
126
127 while let Some(arg) = arg_iter.next() {
128 if arg.starts_with("--") {
130 if let Some((key, value)) = arg.split_once('=') {
132 if let Some(option) = Self::VISUDO_OPTIONS.iter().find(|o| o.long == &key[2..])
134 {
135 if option.takes_argument {
137 (option.set)(&mut options, Some(value.to_string()))?;
138 } else {
139 Err(format!("'--{}' does not take any arguments", option.long))?;
140 }
141 } else {
142 Err(format!("unrecognized option '{arg}'"))?;
143 }
144 } else if let Some(option) =
146 Self::VISUDO_OPTIONS.iter().find(|o| o.long == &arg[2..])
147 {
148 if option.takes_argument {
150 let next_arg = arg_iter.next();
151 (option.set)(&mut options, next_arg)?;
152 } else {
153 (option.set)(&mut options, None)?;
154 }
155 } else {
156 Err(format!("unrecognized option '{arg}'"))?;
157 }
158 } else if arg.starts_with('-') && arg != "-" {
159 for (n, char) in arg.trim_start_matches('-').chars().enumerate() {
161 if let Some(option) = Self::VISUDO_OPTIONS.iter().find(|o| o.short == char) {
163 if option.takes_argument {
165 let rest = arg[(n + 2)..].trim().to_string();
166 let next_arg = if rest.is_empty() {
167 arg_iter.next()
168 } else {
169 Some(rest)
170 };
171 (option.set)(&mut options, next_arg)?;
172 break;
174 } else {
175 (option.set)(&mut options, None)?;
177 }
178 } else {
179 Err(format!("unrecognized option '{char}'"))?;
180 }
181 }
182 } else {
183 if options.file.is_none() {
186 options.file = Some(arg);
187 }
188 }
189 }
190
191 Ok(options)
192 }
193}