use crate::common::syntax::ConflictingOptionError;
use crate::common::syntax::OptionSpec;
use crate::common::syntax::parse_arguments;
use thiserror::Error;
use yash_env::Env;
use yash_env::semantics::Field;
use yash_env::source::pretty::Report;
use super::Command;
use super::Mode;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
CommonError(#[from] crate::common::syntax::ParseError<'static>),
#[error(transparent)]
ConflictingOption(#[from] ConflictingOptionError<'static>),
}
impl Error {
pub fn to_report(&self) -> Report<'_> {
match self {
Error::CommonError(inner) => inner.to_report(),
Error::ConflictingOption(inner) => inner.to_report(),
}
}
}
impl<'a> From<&'a Error> for Report<'a> {
#[inline]
fn from(error: &'a Error) -> Self {
error.to_report()
}
}
pub type Result = std::result::Result<Command, Error>;
const OPTION_SPECS: &[OptionSpec] = &[
OptionSpec::new().short('f').long("functions"),
OptionSpec::new().short('v').long("variables"),
];
pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
let parser_mode = crate::common::syntax::Mode::with_env(env);
let (options, operands) = parse_arguments(OPTION_SPECS, parser_mode, args)?;
let f_option = options.iter().position(|o| o.spec.get_short() == Some('f'));
let v_option = options.iter().position(|o| o.spec.get_short() == Some('v'));
let mode = match (f_option, v_option) {
(None, None) => Mode::default(),
(None, Some(_)) => Mode::Variables,
(Some(_), None) => Mode::Functions,
(Some(f_pos), Some(v_pos)) => {
return Err(ConflictingOptionError::pick_from_indexes(options, [f_pos, v_pos]).into());
}
};
let names = operands;
Ok(Command { mode, names })
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn no_arguments_non_posix() {
let env = Env::new_virtual();
let result = parse(&env, vec![]);
assert_eq!(
result,
Ok(Command {
mode: Mode::Variables,
names: vec![],
})
);
}
#[test]
fn v_option() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-v"]));
assert_eq!(
result,
Ok(Command {
mode: Mode::Variables,
names: vec![],
})
);
let result = parse(&env, Field::dummies(["-vv", "--variables"]));
assert_eq!(
result,
Ok(Command {
mode: Mode::Variables,
names: vec![],
})
);
}
#[test]
fn f_option() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-f"]));
assert_eq!(
result,
Ok(Command {
mode: Mode::Functions,
names: vec![],
})
);
let result = parse(&env, Field::dummies(["-ff", "--functions"]));
assert_eq!(
result,
Ok(Command {
mode: Mode::Functions,
names: vec![],
})
);
}
#[test]
fn v_and_f_option() {
let env = Env::new_virtual();
let args = Field::dummies(["-fv"]);
let result = parse(&env, args.clone());
assert_matches!(result, Err(Error::ConflictingOption(error)) => {
let short_options = error
.options()
.iter()
.map(|o| o.spec.get_short())
.collect::<Vec<_>>();
assert_eq!(short_options, [Some('f'), Some('v')], "{error:?}");
});
}
#[test]
fn operands() {
let env = Env::new_virtual();
let args = Field::dummies(["foo", "bar"]);
let result = parse(&env, args.clone());
assert_eq!(
result,
Ok(Command {
mode: Mode::Variables,
names: args,
})
);
}
}