1use super::Command;
20use super::symbol::{ParseClausesError, parse_clauses};
21use crate::common::syntax::{Mode, OptionSpec, ParseError, parse_arguments};
22use std::num::ParseIntError;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::semantics::Field;
26use yash_env::source::pretty::{Report, ReportType, Snippet};
27
28#[derive(Clone, Debug, Eq, Error, PartialEq)]
29#[non_exhaustive]
30pub enum Error {
31 #[error(transparent)]
33 CommonError(#[from] ParseError<'static>),
34
35 #[error("too many operands")]
39 TooManyOperands(Vec<Field>),
40
41 #[error("invalid mask notation")]
43 InvalidNumericMode(Field, ParseIntError),
44
45 #[error("invalid mask notation")]
47 InvalidSymbolicMode(Field, ParseClausesError),
48}
49
50impl Error {
51 #[must_use]
53 pub fn to_report(&self) -> Report<'_> {
54 let snippets = match self {
55 Self::CommonError(e) => return e.to_report(),
56 Self::TooManyOperands(operands) => Snippet::with_primary_span(
57 &operands[1].origin,
58 format!("{}: unexpected operand", operands[1].value).into(),
59 ),
60 Self::InvalidNumericMode(operand, parse_int_error) => Snippet::with_primary_span(
61 &operand.origin,
62 format!("{operand}: {parse_int_error}").into(),
63 ),
64 Self::InvalidSymbolicMode(operand, parse_clauses_error) => Snippet::with_primary_span(
65 &operand.origin,
66 format!("{operand}: {parse_clauses_error}").into(),
67 ),
68 };
69 let mut report = Report::new();
70 report.r#type = ReportType::Error;
71 report.title = self.to_string().into();
72 report.snippets = snippets;
73 report
74 }
75}
76
77impl<'a> From<&'a Error> for Report<'a> {
78 #[inline]
79 fn from(error: &'a Error) -> Self {
80 error.to_report()
81 }
82}
83
84pub type Result = std::result::Result<Command, Error>;
86
87const OPTION_SPECS: &[OptionSpec] = &[OptionSpec::new().short('S')];
89
90pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
92 let (options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?;
93
94 match operands.len() {
95 0 => {
96 let symbolic = options.iter().any(|o| o.spec.get_short() == Some('S'));
97 Ok(Command::Show { symbolic })
98 }
99
100 1 => {
101 let field = { operands }.pop().unwrap();
102
103 if field.value.starts_with(|c: char| c.is_ascii_digit()) {
105 return match u16::from_str_radix(&field.value, 8) {
106 Ok(mask) => Ok(Command::set_from_raw_mask(mask)),
107 Err(e) => Err(Error::InvalidNumericMode(field, e)),
108 };
109 }
110
111 match parse_clauses(&field.value) {
112 Ok(clauses) => Ok(Command::Set(clauses)),
113 Err(e) => Err(Error::InvalidSymbolicMode(field, e)),
114 }
115 }
116
117 _ => Err(Error::TooManyOperands(operands)),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::umask::symbol::{Action, Clause, Operator, Permission, Who};
125 use assert_matches::assert_matches;
126
127 #[test]
128 fn no_arguments() {
129 let env = Env::new_virtual();
130 let result = parse(&env, vec![]);
131 assert_eq!(result, Ok(Command::Show { symbolic: false }));
132 }
133
134 #[test]
135 fn symbolic_option() {
136 let env = Env::new_virtual();
137 let result = parse(&env, Field::dummies(["-S"]));
138 assert_eq!(result, Ok(Command::Show { symbolic: true }));
139 }
140
141 #[test]
142 fn numeric_mask() {
143 let env = Env::new_virtual();
144 let args = Field::dummies(["022"]);
145 let result = parse(&env, args);
146 assert_eq!(
147 result,
148 Ok(Command::Set(vec![Clause {
149 who: Who { mask: 0o777 },
150 actions: vec![Action {
151 operator: Operator::Set,
152 permission: Permission::Literal {
153 mask: !0o022,
154 conditional_executable: false
155 },
156 }],
157 }]))
158 );
159 }
160
161 #[test]
162 fn symbolic_mask() {
163 let env = Env::new_virtual();
164 let args = Field::dummies(["u=rwx,go+r-w"]);
165 let result = parse(&env, args);
166 assert_eq!(
167 result,
168 Ok(Command::Set(vec![
169 Clause {
170 who: Who { mask: 0o700 },
171 actions: vec![Action {
172 operator: Operator::Set,
173 permission: Permission::Literal {
174 mask: 0o777,
175 conditional_executable: false
176 }
177 }]
178 },
179 Clause {
180 who: Who { mask: 0o077 },
181 actions: vec![
182 Action {
183 operator: Operator::Add,
184 permission: Permission::Literal {
185 mask: 0o444,
186 conditional_executable: false
187 }
188 },
189 Action {
190 operator: Operator::Remove,
191 permission: Permission::Literal {
192 mask: 0o222,
193 conditional_executable: false
194 }
195 }
196 ]
197 }
198 ]))
199 );
200 }
201
202 #[test]
203 fn too_many_operands() {
204 let env = Env::new_virtual();
205 let args = Field::dummies(["022", "002"]);
206 let result = parse(&env, args.clone());
207 assert_eq!(result, Err(Error::TooManyOperands(args)));
208 }
209
210 #[test]
211 fn operand_overrides_option() {
212 let env = Env::new_virtual();
214 let args = Field::dummies(["-S", "go=u"]);
215 let result = parse(&env, args);
216 assert_eq!(
217 result,
218 Ok(Command::Set(vec![Clause {
219 who: Who { mask: 0o077 },
220 actions: vec![Action {
221 operator: Operator::Set,
222 permission: Permission::CopyUser,
223 }],
224 }]))
225 );
226 }
227
228 #[test]
229 fn invalid_numeric_mask() {
230 let env = Env::new_virtual();
231 let arg = Field::dummy("02x2");
232 let result = parse(&env, vec![arg.clone()]);
233 assert_matches!(result, Err(Error::InvalidNumericMode(field, e)) => {
234 assert_eq!(field, arg);
235 assert_eq!(e.kind(), &std::num::IntErrorKind::InvalidDigit);
236 });
237 }
238
239 #[test]
240 fn numeric_mask_starting_with_plus() {
241 let env = Env::new_virtual();
242 let arg = Field::dummy("+022");
243 let result = parse(&env, vec![arg.clone()]);
244 assert_eq!(
245 result,
246 Err(Error::InvalidSymbolicMode(
247 arg,
248 ParseClausesError::InvalidChar('0')
249 ))
250 );
251 }
252
253 #[test]
254 fn invalid_option() {
255 let env = Env::new_virtual();
258 let arg = Field::dummy("-x");
259 let result = parse(&env, vec![arg.clone()]);
260 assert_eq!(
261 result,
262 Err(Error::CommonError(ParseError::UnknownShortOption('x', arg)))
263 );
264 }
265}