yash_builtin/unset/
syntax.rs1use crate::common::syntax::ConflictingOptionError;
20use crate::common::syntax::OptionSpec;
21use crate::common::syntax::parse_arguments;
22use thiserror::Error;
23use yash_env::Env;
24use yash_env::semantics::Field;
25use yash_env::source::pretty::Report;
26
27use super::Command;
28use super::Mode;
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(transparent)]
40 ConflictingOption(#[from] ConflictingOptionError<'static>),
41 }
43
44impl Error {
45 pub fn to_report(&self) -> Report<'_> {
47 match self {
48 Error::CommonError(inner) => inner.to_report(),
49 Error::ConflictingOption(inner) => inner.to_report(),
50 }
51 }
52}
53
54impl<'a> From<&'a Error> for Report<'a> {
55 #[inline]
56 fn from(error: &'a Error) -> Self {
57 error.to_report()
58 }
59}
60
61pub type Result = std::result::Result<Command, Error>;
63
64const OPTION_SPECS: &[OptionSpec] = &[
65 OptionSpec::new().short('f').long("functions"),
66 OptionSpec::new().short('v').long("variables"),
67];
68
69pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
71 let parser_mode = crate::common::syntax::Mode::with_env(env);
72 let (options, operands) = parse_arguments(OPTION_SPECS, parser_mode, args)?;
73
74 let f_option = options.iter().position(|o| o.spec.get_short() == Some('f'));
76 let v_option = options.iter().position(|o| o.spec.get_short() == Some('v'));
77 let mode = match (f_option, v_option) {
78 (None, None) => Mode::default(),
79 (None, Some(_)) => Mode::Variables,
80 (Some(_), None) => Mode::Functions,
81 (Some(f_pos), Some(v_pos)) => {
82 return Err(ConflictingOptionError::pick_from_indexes(options, [f_pos, v_pos]).into());
83 }
84 };
85
86 let names = operands;
87 Ok(Command { mode, names })
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use assert_matches::assert_matches;
94
95 #[test]
96 fn no_arguments_non_posix() {
97 let env = Env::new_virtual();
98 let result = parse(&env, vec![]);
99 assert_eq!(
100 result,
101 Ok(Command {
102 mode: Mode::Variables,
103 names: vec![],
104 })
105 );
106 }
107
108 #[test]
112 fn v_option() {
113 let env = Env::new_virtual();
114 let result = parse(&env, Field::dummies(["-v"]));
115 assert_eq!(
116 result,
117 Ok(Command {
118 mode: Mode::Variables,
119 names: vec![],
120 })
121 );
122
123 let result = parse(&env, Field::dummies(["-vv", "--variables"]));
125 assert_eq!(
126 result,
127 Ok(Command {
128 mode: Mode::Variables,
129 names: vec![],
130 })
131 );
132 }
133
134 #[test]
135 fn f_option() {
136 let env = Env::new_virtual();
137 let result = parse(&env, Field::dummies(["-f"]));
138 assert_eq!(
139 result,
140 Ok(Command {
141 mode: Mode::Functions,
142 names: vec![],
143 })
144 );
145
146 let result = parse(&env, Field::dummies(["-ff", "--functions"]));
148 assert_eq!(
149 result,
150 Ok(Command {
151 mode: Mode::Functions,
152 names: vec![],
153 })
154 );
155 }
156
157 #[test]
158 fn v_and_f_option() {
159 let env = Env::new_virtual();
161 let args = Field::dummies(["-fv"]);
162 let result = parse(&env, args.clone());
163 assert_matches!(result, Err(Error::ConflictingOption(error)) => {
164 let short_options = error
165 .options()
166 .iter()
167 .map(|o| o.spec.get_short())
168 .collect::<Vec<_>>();
169 assert_eq!(short_options, [Some('f'), Some('v')], "{error:?}");
170 });
171 }
172
173 #[test]
174 fn operands() {
175 let env = Env::new_virtual();
176 let args = Field::dummies(["foo", "bar"]);
177 let result = parse(&env, args.clone());
178 assert_eq!(
179 result,
180 Ok(Command {
181 mode: Mode::Variables,
182 names: args,
183 })
184 );
185 }
186}