use thiserror::Error;
use yash_env::builtin::getopts::GetoptsState;
use yash_env::builtin::getopts::Origin;
#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
pub enum Error {
#[error("arguments are different from the previous call")]
DifferentArgs,
#[error("$OPTIND has been modified externally")]
DifferentOptind,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GetoptsStateRef<'a, I> {
pub args: I,
pub origin: Origin,
pub optind: &'a str,
}
impl<'a> From<&'a GetoptsState> for GetoptsStateRef<'a, std::slice::Iter<'a, String>> {
fn from(state: &'a GetoptsState) -> Self {
GetoptsStateRef {
args: state.args.iter(),
origin: state.origin,
optind: &state.optind,
}
}
}
impl<I> GetoptsStateRef<'_, I> {
#[must_use]
pub fn into_state(self) -> GetoptsState
where
I: IntoIterator,
I::Item: Into<String>,
{
GetoptsState {
args: self.args.into_iter().map(Into::into).collect(),
origin: self.origin,
optind: self.optind.into(),
}
}
pub fn verify<'a, S, J>(self, previous: S) -> Result<Option<Self>, Error>
where
I: IntoIterator,
S: Into<GetoptsStateRef<'a, J>>,
J: IntoIterator,
I::Item: PartialEq<J::Item>,
{
if self.optind == "1" {
return Ok(Some(self));
}
let previous = previous.into();
if self.origin != previous.origin {
return Err(Error::DifferentArgs);
}
if self.args.into_iter().ne(previous.args) {
return Err(Error::DifferentArgs);
}
if self.optind != previous.optind {
return Err(Error::DifferentOptind);
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn verify_with_optind_1() {
let left = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "1".into(),
};
let right = GetoptsState {
args: vec!["-x".into(), "-y".into()],
origin: Origin::PositionalParams,
optind: "2".into(),
};
let result = GetoptsStateRef::from(&left).verify(&right);
assert_matches!(result, Ok(Some(state_ref)) => {
assert_eq!(state_ref.into_state(), left);
});
}
#[test]
fn verify_with_same_states() {
let state = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "2".into(),
};
let result = GetoptsStateRef::from(&state).verify(&state);
assert_matches!(result, Ok(None));
}
#[test]
fn verify_with_different_args() {
let left = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "2".into(),
};
let right = GetoptsState {
args: vec!["-a".into(), "-c".into()],
origin: Origin::DirectArgs,
optind: "2".into(),
};
let result = GetoptsStateRef::from(&left).verify(&right);
assert_matches!(result, Err(Error::DifferentArgs));
}
#[test]
fn verify_with_different_origins() {
let left = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "2".into(),
};
let right = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::PositionalParams,
optind: "2".into(),
};
let result = GetoptsStateRef::from(&left).verify(&right);
assert_matches!(result, Err(Error::DifferentArgs));
}
#[test]
fn verify_with_different_optind_values() {
let left = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "2".into(),
};
let right = GetoptsState {
args: vec!["-a".into(), "-b".into()],
origin: Origin::DirectArgs,
optind: "3".into(),
};
let result = GetoptsStateRef::from(&left).verify(&right);
assert_matches!(result, Err(Error::DifferentOptind));
}
}