1use super::Command;
20use super::Mode;
21use crate::common::syntax::OptionSpec;
22use crate::common::syntax::parse_arguments;
23use std::collections::VecDeque;
24use thiserror::Error;
25use yash_env::Env;
26use yash_env::semantics::Field;
27use yash_env::source::Location;
28use yash_env::source::pretty::{Report, ReportType, Snippet};
29
30#[derive(Clone, Debug, Eq, Error, PartialEq)]
32#[non_exhaustive]
33pub enum Error {
34 #[error(transparent)]
36 CommonError(#[from] crate::common::syntax::ParseError<'static>),
37
38 #[error("-e option must be used with -P (and not -L)")]
42 EnsurePwdNotPhysical(Location),
43
44 #[error("empty operand")]
46 EmptyOperand(Field),
47
48 #[error("unexpected operand")]
52 UnexpectedOperands(Vec<Field>),
53}
54
55impl Error {
56 #[must_use]
58 pub fn to_report(&self) -> Report<'_> {
59 let (location, label) = match self {
60 Self::CommonError(e) => return e.to_report(),
61 Self::EnsurePwdNotPhysical(location) => {
62 (location, "-e option must be used with -P".into())
63 }
64 Self::EmptyOperand(operand) => (&operand.origin, "empty operand".into()),
65 Self::UnexpectedOperands(operands) => (
66 &operands[0].origin,
67 format!("{}: unexpected operand", operands[0].value).into(),
68 ),
69 };
70
71 let mut report = Report::new();
72 report.r#type = ReportType::Error;
73 report.title = self.to_string().into();
74 report.snippets = Snippet::with_primary_span(location, label);
75 report
76 }
77}
78
79impl<'a> From<&'a Error> for Report<'a> {
80 #[inline]
81 fn from(error: &'a Error) -> Self {
82 error.to_report()
83 }
84}
85
86pub type Result = std::result::Result<Command, Error>;
88
89const OPTION_SPECS: &[OptionSpec] = &[
90 OptionSpec::new().short('e').long("ensure-pwd"),
91 OptionSpec::new().short('L').long("logical"),
92 OptionSpec::new().short('P').long("physical"),
93];
94
95pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
97 let parser_mode = crate::common::syntax::Mode::with_env(env);
98 let (options, operands) = parse_arguments(OPTION_SPECS, parser_mode, args)?;
99
100 let mut ensure_pwd_option_location = None;
101 let mut mode = Mode::default();
102 for option in options {
103 match option.spec.get_short() {
104 Some('e') => ensure_pwd_option_location = Some(option.location),
105 Some('L') => mode = Mode::Logical,
106 Some('P') => mode = Mode::Physical,
107 _ => unreachable!(),
108 }
109 }
110
111 let ensure_pwd = match (ensure_pwd_option_location, mode) {
112 (Some(_), Mode::Physical) => true,
113 (Some(location), _) => return Err(Error::EnsurePwdNotPhysical(location)),
114 (None, _) => false,
115 };
116
117 let mut operands = VecDeque::from(operands);
118 let operand = operands.pop_front();
119 if !operands.is_empty() {
120 return Err(Error::UnexpectedOperands(operands.into()));
121 }
122
123 let operand = match operand {
124 Some(operand) if operand.value.is_empty() => return Err(Error::EmptyOperand(operand)),
125 operand => operand,
126 };
127 Ok(Command {
128 mode,
129 ensure_pwd,
130 operand,
131 })
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn no_arguments() {
140 let env = Env::new_virtual();
141 let result = parse(&env, vec![]);
142 assert_eq!(
143 result,
144 Ok(Command {
145 mode: Mode::Logical,
146 ensure_pwd: false,
147 operand: None,
148 })
149 );
150 }
151
152 #[test]
153 fn logical_option() {
154 let env = Env::new_virtual();
155 let result = parse(&env, Field::dummies(["-L"]));
156 assert_eq!(
157 result,
158 Ok(Command {
159 mode: Mode::Logical,
160 ensure_pwd: false,
161 operand: None,
162 })
163 );
164 }
165
166 #[test]
167 fn physical_option() {
168 let env = Env::new_virtual();
169 let result = parse(&env, Field::dummies(["-P"]));
170 assert_eq!(
171 result,
172 Ok(Command {
173 mode: Mode::Physical,
174 ensure_pwd: false,
175 operand: None,
176 })
177 );
178 }
179
180 #[test]
181 fn last_option_wins() {
182 let env = Env::new_virtual();
183
184 let result = parse(&env, Field::dummies(["-L", "-P"]));
185 assert_eq!(result.unwrap().mode, Mode::Physical);
186
187 let result = parse(&env, Field::dummies(["-P", "-L"]));
188 assert_eq!(result.unwrap().mode, Mode::Logical);
189
190 let result = parse(&env, Field::dummies(["-L", "-P", "-L"]));
191 assert_eq!(result.unwrap().mode, Mode::Logical);
192
193 let result = parse(&env, Field::dummies(["-PLP"]));
194 assert_eq!(result.unwrap().mode, Mode::Physical);
195 }
196
197 #[test]
198 fn ensure_pwd_option_with_physical_option() {
199 let env = Env::new_virtual();
200
201 let result = parse(&env, Field::dummies(["-e", "-P"]));
202 assert!(result.unwrap().ensure_pwd);
203
204 let result = parse(&env, Field::dummies(["-P", "-e"]));
205 assert!(result.unwrap().ensure_pwd);
206
207 let result = parse(&env, Field::dummies(["-eLP"]));
208 assert!(result.unwrap().ensure_pwd);
209 }
210
211 #[test]
212 fn with_operand() {
213 let env = Env::new_virtual();
214 let operand = Field::dummy("foo/bar");
215 let result = parse(&env, vec![operand.clone()]);
216 assert_eq!(
217 result,
218 Ok(Command {
219 mode: Mode::default(),
220 ensure_pwd: false,
221 operand: Some(operand),
222 })
223 );
224 }
225
226 #[test]
227 fn option_and_operand() {
228 let env = Env::new_virtual();
229 let operand = Field::dummy("foo/bar");
230 let args = vec![Field::dummy("-L"), Field::dummy("--"), operand.clone()];
231 let result = parse(&env, args);
232 assert_eq!(
233 result,
234 Ok(Command {
235 mode: Mode::Logical,
236 ensure_pwd: false,
237 operand: Some(operand),
238 })
239 );
240 }
241
242 #[test]
243 fn ensure_pwd_option_with_logical_option() {
244 let env = Env::new_virtual();
245 let e = Field::dummy("-e");
246
247 let result = parse(&env, vec![Field::dummy("-L"), e.clone()]);
248 assert_eq!(result, Err(Error::EnsurePwdNotPhysical(e.origin.clone())));
249
250 let result = parse(&env, vec![e.clone()]);
251 assert_eq!(result, Err(Error::EnsurePwdNotPhysical(e.origin)));
252 }
253
254 #[test]
255 fn empty_operand() {
256 let env = Env::new_virtual();
257 let operand = Field::dummy("");
258 let result = parse(&env, vec![operand.clone()]);
259 assert_eq!(result, Err(Error::EmptyOperand(operand)));
260 }
261
262 #[test]
263 fn unexpected_operand() {
264 let env = Env::new_virtual();
265 let operand1 = Field::dummy("foo");
266 let operand2 = Field::dummy("bar");
267 let result = parse(&env, vec![operand1, operand2.clone()]);
268 assert_eq!(result, Err(Error::UnexpectedOperands(vec![operand2])));
269 }
270
271 #[test]
272 fn unexpected_operands_after_options() {
273 let env = Env::new_virtual();
274 let args = Field::dummies(["-LP", "-L", "--", "one", "two", "three"]);
275 let extra_operands = args[4..].to_vec();
276 let result = parse(&env, args);
277 assert_eq!(result, Err(Error::UnexpectedOperands(extra_operands)));
278 }
279}