yash_builtin/pwd/
syntax.rs1use super::Mode;
20use crate::common::syntax::OptionOccurrence;
21use crate::common::syntax::OptionSpec;
22use crate::common::syntax::parse_arguments;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::semantics::Field;
26use yash_env::source::pretty::Report;
27use yash_env::source::pretty::ReportType;
28use yash_env::source::pretty::Snippet;
29use yash_env::source::pretty::Span;
30use yash_env::source::pretty::SpanRole;
31use yash_env::source::pretty::add_span;
32
33#[derive(Clone, Debug, Eq, Error, PartialEq)]
35#[non_exhaustive]
36pub enum Error {
37 #[error(transparent)]
39 CommonError(#[from] crate::common::syntax::ParseError<'static>),
40
41 #[error("unexpected operand")]
43 UnexpectedOperands(Vec<Field>),
44}
45
46impl Error {
47 #[must_use]
49 pub fn to_report(&self) -> Report<'_> {
50 match self {
51 Self::CommonError(e) => e.to_report(),
52
53 Self::UnexpectedOperands(operands) => {
54 let mut report = Report::new();
55 report.r#type = ReportType::Error;
56 report.title = "unexpected operand".into();
57 report.snippets = Snippet::with_primary_span(
58 &operands[0].origin,
59 format!("{}: unexpected", operands[0]).into(),
60 );
61 for operand in &operands[1..] {
62 add_span(
63 &operand.origin.code,
64 Span {
65 range: operand.origin.byte_range(),
66 role: SpanRole::Primary {
67 label: format!("{}: unexpected", operand).into(),
68 },
69 },
70 &mut report.snippets,
71 );
72 }
73 report
74 }
75 }
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<Mode, Error>;
88
89const OPTION_SPECS: &[OptionSpec] = &[
90 OptionSpec::new().short('L').long("logical"),
91 OptionSpec::new().short('P').long("physical"),
92];
93
94fn mode_for_option(option: &OptionOccurrence) -> Mode {
95 match option.spec.get_short() {
96 Some('L') => Mode::Logical,
97 Some('P') => Mode::Physical,
98 _ => unreachable!(),
99 }
100}
101
102pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
104 let parser_mode = crate::common::syntax::Mode::with_env(env);
105 let (options, operands) = parse_arguments(OPTION_SPECS, parser_mode, args)?;
106
107 if !operands.is_empty() {
108 return Err(Error::UnexpectedOperands(operands));
109 }
110
111 Ok(options.last().map(mode_for_option).unwrap_or_default())
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn no_arguments() {
120 let env = Env::new_virtual();
121 let result = parse(&env, vec![]);
122 assert_eq!(result, Ok(Mode::Logical));
123 }
124
125 #[test]
126 fn logical_option() {
127 let env = Env::new_virtual();
128 let result = parse(&env, Field::dummies(["-L"]));
129 assert_eq!(result, Ok(Mode::Logical));
130 }
131
132 #[test]
133 fn physical_option() {
134 let env = Env::new_virtual();
135 let result = parse(&env, Field::dummies(["-P"]));
136 assert_eq!(result, Ok(Mode::Physical));
137 }
138
139 #[test]
140 fn last_option_wins() {
141 let env = Env::new_virtual();
142
143 let result = parse(&env, Field::dummies(["-L", "-P"]));
144 assert_eq!(result, Ok(Mode::Physical));
145
146 let result = parse(&env, Field::dummies(["-P", "-L"]));
147 assert_eq!(result, Ok(Mode::Logical));
148
149 let result = parse(&env, Field::dummies(["-LPL"]));
150 assert_eq!(result, Ok(Mode::Logical));
151
152 let result = parse(&env, Field::dummies(["-PLP"]));
153 assert_eq!(result, Ok(Mode::Physical));
154 }
155
156 #[test]
157 fn unexpected_operand() {
158 let env = Env::new_virtual();
159 let args = Field::dummies(["foo"]);
160 let result = parse(&env, args.clone());
161 assert_eq!(result, Err(Error::UnexpectedOperands(args)));
162 }
163
164 #[test]
165 fn unexpected_operands_after_options() {
166 let env = Env::new_virtual();
167 let args = Field::dummies(["-LP", "-L", "--", "one", "two"]);
168 let operands = args[3..].to_vec();
169 let result = parse(&env, args);
170 assert_eq!(result, Err(Error::UnexpectedOperands(operands)));
171 }
172}