use super::{
ExitCode,
io::{self, Write as _},
};
use std::ffi::OsString;
#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum E {
NoArgs,
UnknownArg(OsString),
}
impl E {
pub(crate) fn into_exit_code(self) -> ExitCode {
let mut stderr = io::stderr().lock();
match self {
Self::NoArgs => writeln!(
stderr,
"No arguments were passed including the first argument which is assumed to be the name of the program. See lints help for more information."
),
Self::UnknownArg(arg) => writeln!(
stderr,
"Unrecognized argument '{}' was passed. See lints help for more information.",
arg.display()
),
}.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
}
}
#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum Cmd {
Allow(bool, bool),
Deny(bool, bool),
Help,
Version,
}
#[derive(Clone, Copy)]
enum Opt {
None,
All,
AllowUndefined,
AllAndAllowUndefined,
}
impl Cmd {
fn get_opts<I: Iterator<Item = OsString>>(
mut opts: I,
opt: Opt,
arg: OsString,
) -> Result<Opt, E> {
const ALL: &[u8] = b"--all".as_slice();
const ALLOW_UNDEFINED_LINTS: &[u8] = b"--allow-undefined-lints".as_slice();
match arg.as_encoded_bytes() {
ALL => match opt {
Opt::None => Ok(Opt::All),
Opt::All | Opt::AllAndAllowUndefined => Err(E::UnknownArg(arg)),
Opt::AllowUndefined => Ok(Opt::AllAndAllowUndefined),
},
ALLOW_UNDEFINED_LINTS => match opt {
Opt::None => Ok(Opt::AllowUndefined),
Opt::AllowUndefined | Opt::AllAndAllowUndefined => Err(E::UnknownArg(arg)),
Opt::All => Ok(Opt::AllAndAllowUndefined),
},
_ => Err(E::UnknownArg(arg)),
}
.and_then(|opt2| {
opts.next()
.map_or(Ok(opt2), |nxt| Self::get_opts(opts, opt2, nxt))
})
}
#[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
pub(crate) fn try_from_args<I: IntoIterator<Item = OsString>>(iter: I) -> Result<Self, E> {
let mut args = iter.into_iter();
args.next().ok_or(E::NoArgs).and_then(|_| {
args.next().map_or(Ok(Self::Deny(false, false)), |cmd| {
match cmd.as_encoded_bytes() {
b"allow" => args.next().map_or(Ok(Self::Allow(false, false)), |arg| {
Self::get_opts(args, Opt::None, arg).map(|opt| match opt {
Opt::None => unreachable!("bug in args::Cmd::get_ops"),
Opt::All => Self::Allow(true, false),
Opt::AllowUndefined => Self::Allow(false, true),
Opt::AllAndAllowUndefined => Self::Allow(true, true),
})
}),
b"deny" => args.next().map_or(Ok(Self::Deny(false, false)), |arg| {
Self::get_opts(args, Opt::None, arg).map(|opt| match opt {
Opt::None => unreachable!("bug in args::Cmd::get_ops"),
Opt::All => Self::Deny(true, false),
Opt::AllowUndefined => Self::Deny(false, true),
Opt::AllAndAllowUndefined => Self::Deny(true, true),
})
}),
b"help" => args
.next()
.map_or(Ok(Self::Help), |opt| Err(E::UnknownArg(opt))),
b"version" => args
.next()
.map_or(Ok(Self::Version), |opt| Err(E::UnknownArg(opt))),
_ => Self::get_opts(args, Opt::None, cmd).map(|opt| match opt {
Opt::None => unreachable!("bug in args::Cmd::get_ops"),
Opt::All => Self::Deny(true, false),
Opt::AllowUndefined => Self::Deny(false, true),
Opt::AllAndAllowUndefined => Self::Deny(true, true),
}),
}
})
})
}
}
#[cfg(test)]
mod tests {
use super::{Cmd, E};
#[test]
fn successes() {
assert_eq!(Cmd::try_from_args(["".into()]), Ok(Cmd::Deny(false, false)));
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into()]),
Ok(Cmd::Deny(true, false))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(false, true))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(true, true))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into()]),
Ok(Cmd::Allow(false, false))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--all".into()]),
Ok(Cmd::Allow(true, false))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Allow(false, true))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"allow".into(),
"--allow-undefined-lints".into(),
"--all".into()
]),
Ok(Cmd::Allow(true, true))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into()]),
Ok(Cmd::Deny(false, false))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--all".into()]),
Ok(Cmd::Deny(true, false))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(false, true))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"deny".into(),
"--all".into(),
"--allow-undefined-lints".into()
]),
Ok(Cmd::Deny(true, true))
);
assert_eq!(
Cmd::try_from_args(["".into(), "help".into()]),
Ok(Cmd::Help)
);
assert_eq!(
Cmd::try_from_args(["doesn't matter what this is".into(), "version".into()]),
Ok(Cmd::Version)
);
}
#[test]
fn errs() {
assert_eq!(Cmd::try_from_args([]), Err(E::NoArgs));
assert_eq!(
Cmd::try_from_args(["".into(), "".into()]),
Err(E::UnknownArg("".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "foo".into()]),
Err(E::UnknownArg("foo".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "help".into(), "version".into()]),
Err(E::UnknownArg("version".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "version".into(), "allow".into()]),
Err(E::UnknownArg("allow".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "allow".into()]),
Err(E::UnknownArg("allow".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "allow".into()]),
Err(E::UnknownArg("allow".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into(), "allow".into()]),
Err(E::UnknownArg("allow".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--allow-undefined-lints".into(), "deny".into()]),
Err(E::UnknownArg("deny".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--all".into(), "".into()]),
Err(E::UnknownArg("".into()))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"--all".into(),
"--allow-undefined-lints".into(),
"--all".into()
]),
Err(E::UnknownArg("--all".into()))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"deny".into(),
"--all".into(),
"--allow-undefined-lints".into(),
"foo".into()
]),
Err(E::UnknownArg("foo".into()))
);
}
}