use super::Command;
use crate::common::syntax::Mode;
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::Location;
use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
pub const OPTION_SPECS: &[OptionSpec] = &[OptionSpec::new().short('a')];
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum Error {
#[error(transparent)]
CommonError(#[from] crate::common::syntax::ParseError<'static>),
#[error("`-a` cannot be used with operands")]
ConflictingOptionAndOperand {
option_location: Location,
operand_location: Location,
},
#[error("no option or operand specified")]
MissingArgument,
}
impl Error {
#[must_use]
pub fn to_report(&self) -> Report<'_> {
let (title, snippets) = match self {
Self::CommonError(e) => return e.to_report(),
Self::ConflictingOptionAndOperand {
option_location,
operand_location,
} => ("`-a` cannot be used with operands", {
let mut snippets =
Snippet::with_primary_span(option_location, "`-a` specified here".into());
add_span(
&operand_location.code,
Span {
range: operand_location.byte_range(),
role: SpanRole::Primary {
label: "operand specified here".into(),
},
},
&mut snippets,
);
snippets
}),
Self::MissingArgument => ("no option or operand specified", vec![]),
};
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = title.into();
report.snippets = snippets;
report
}
}
impl<'a> From<&'a Error> for Report<'a> {
#[inline]
fn from(error: &'a Error) -> Self {
error.to_report()
}
}
pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result<Command, Error> {
let mode = Mode::with_env(env);
let (mut options, operands) = parse_arguments(OPTION_SPECS, mode, args)?;
for option in &options {
debug_assert_eq!(option.spec.get_short(), Some('a'));
}
match (options.pop(), operands.is_empty()) {
(None, true) => Err(Error::MissingArgument),
(None, false) => Ok(Command::Remove(operands)),
(Some(_), true) => Ok(Command::RemoveAll),
(Some(option), false) => Err(Error::ConflictingOptionAndOperand {
option_location: option.location,
operand_location: { operands }.swap_remove(0).origin,
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_option() {
let env = Env::new_virtual();
let result = parse(&env, Field::dummies(["-a"]));
assert_eq!(result, Ok(Command::RemoveAll));
}
#[test]
fn operands() {
let env = Env::new_virtual();
let operands = Field::dummies(["foo", "bar"]);
let result = parse(&env, operands.clone());
assert_eq!(result, Ok(Command::Remove(operands)));
}
#[test]
fn missing_arguments() {
let env = Env::new_virtual();
let result = parse(&env, vec![]);
assert_eq!(result, Err(Error::MissingArgument));
}
#[test]
fn option_and_operand() {
let env = Env::new_virtual();
let args = Field::dummies(["-a", "foo"]);
let result = parse(&env, args);
assert_eq!(
result,
Err(Error::ConflictingOptionAndOperand {
option_location: Location::dummy("-a"),
operand_location: Location::dummy("foo"),
}),
);
}
}