use super::Command;
use super::Identify;
use super::Invoke;
use super::Search;
use crate::common::syntax::parse_arguments;
use crate::common::syntax::Mode;
use crate::common::syntax::OptionOccurrence;
use crate::common::syntax::OptionSpec;
use crate::common::syntax::ParseError;
use thiserror::Error;
use yash_env::semantics::Field;
use yash_env::Env;
use yash_syntax::source::pretty::Message;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
CommonError(#[from] ParseError<'static>),
}
impl<'a> From<&'a Error> for Message<'a> {
fn from(error: &'a Error) -> Self {
match error {
Error::CommonError(error) => error.into(),
}
}
}
const OPTION_SPECS: &[OptionSpec] = &[
OptionSpec::new().short('p').long("path"),
OptionSpec::new().short('v').long("identify"),
OptionSpec::new().short('V').long("verbose-identify"),
];
pub fn interpret(
options: Vec<OptionOccurrence<'_>>,
operands: Vec<Field>,
) -> Result<Command, Error> {
let mut standard_path = false;
let mut verbose_identify = None;
for option in options {
match option.spec.get_short() {
Some('p') => standard_path = true,
Some('v') => verbose_identify = Some(false),
Some('V') => verbose_identify = Some(true),
_ => unreachable!("unhandled option: {:?}", option),
}
}
if let Some(verbose) = verbose_identify {
let mut search = Search::default_for_identify();
search.standard_path = standard_path;
let identify = Identify {
names: operands,
search,
verbose,
};
Ok(identify.into())
} else {
let mut search = Search::default_for_invoke();
search.standard_path = standard_path;
let fields = operands;
let invoke = Invoke { fields, search };
Ok(invoke.into())
}
}
pub fn parse(env: &Env, args: Vec<Field>) -> Result<Command, Error> {
let (options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?;
interpret(options, operands)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::command::Category;
use assert_matches::assert_matches;
use enumset::EnumSet;
#[test]
fn invoke_without_options() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["foo", "bar", "baz"]));
assert_matches!(result, Ok(Command::Invoke(invoke)) => {
assert_eq!(invoke.fields, Field::dummies(["foo", "bar", "baz"]));
assert_eq!(
invoke.search,
Search {
standard_path: false,
categories: Category::Builtin | Category::ExternalUtility
}
);
});
}
#[test]
fn invoke_with_p_option() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-p", "foo"]));
assert_matches!(result, Ok(Command::Invoke(invoke)) => {
assert_eq!(invoke.fields, Field::dummies(["foo"]));
assert_eq!(
invoke.search,
Search {
standard_path: true,
categories: Category::Builtin | Category::ExternalUtility
}
);
});
}
#[test]
fn identify_without_options() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-v", "foo"]));
assert_matches!(result, Ok(Command::Identify(identify)) => {
assert_eq!(identify.names, Field::dummies(["foo"]));
assert_eq!(
identify.search,
Search {
standard_path: false,
categories: EnumSet::all()
}
);
assert!(!identify.verbose);
});
}
#[test]
fn identify_with_p_option() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-v", "-p", "foo"]));
assert_matches!(result, Ok(Command::Identify(identify)) => {
assert_eq!(identify.names, Field::dummies(["foo"]));
assert_eq!(
identify.search,
Search {
standard_path: true,
categories: EnumSet::all()
}
);
assert!(!identify.verbose);
});
}
#[test]
fn verbosely_identify_without_options() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-V", "bar"]));
assert_matches!(result, Ok(Command::Identify(identify)) => {
assert_eq!(identify.names, Field::dummies(["bar"]));
assert_eq!(
identify.search,
Search {
standard_path: false,
categories: EnumSet::all()
}
);
assert!(identify.verbose);
});
}
#[test]
#[allow(non_snake_case)]
fn last_specified_option_wins_between_v_and_V() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-V", "-v", "baz"]));
assert_matches!(result, Ok(Command::Identify(identify)) => {
assert!(!identify.verbose);
});
let result = parse(&env, Field::dummies(["-v", "-V", "baz"]));
assert_matches!(result, Ok(Command::Identify(identify)) => {
assert!(identify.verbose);
});
}
}