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))]
#[derive(Clone, Copy)]
pub(crate) enum Cmd {
Allow(Opts),
Deny(Opts),
Help,
Version,
}
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Clone, Copy, Default)]
pub(crate) struct Opts {
pub all_lints: bool,
pub allow_undefined_lints: bool,
pub read_stdin: bool,
}
impl Opts {
fn add<I: Iterator<Item = OsString>>(
&mut self,
mut opts: I,
arg: OsString,
) -> Option<OsString> {
const DASH: u8 = b'-';
const ALL: &[u8] = b"--all".as_slice();
const ALLOW_UNDEFINED_LINTS: &[u8] = b"--allow-undefined-lints".as_slice();
match arg.as_encoded_bytes() {
&[DASH] => {
if self.read_stdin {
Err(())
} else {
self.read_stdin = true;
Ok(())
}
}
ALL => {
if self.all_lints || self.read_stdin {
Err(())
} else {
self.all_lints = true;
Ok(())
}
}
ALLOW_UNDEFINED_LINTS => {
if self.allow_undefined_lints || self.read_stdin {
Err(())
} else {
self.allow_undefined_lints = true;
Ok(())
}
}
_ => Err(()),
}
.map_or(Some(arg), |()| {
opts.next().and_then(|opt| self.add(opts, opt))
})
}
}
impl Cmd {
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_else(
|| Ok(Self::Deny(Opts::default())),
|cmd| match cmd.as_encoded_bytes() {
b"allow" => args.next().map_or_else(
|| Ok(Self::Allow(Opts::default())),
|arg| {
let mut opts = Opts::default();
opts.add(args, arg)
.map_or(Ok(Self::Allow(opts)), |opt| Err(E::UnknownArg(opt)))
},
),
b"deny" => args.next().map_or_else(
|| Ok(Self::Deny(Opts::default())),
|arg| {
let mut opts = Opts::default();
opts.add(args, arg)
.map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
},
),
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))),
_ => {
let mut opts = Opts::default();
opts.add(args, cmd)
.map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
}
},
)
})
}
}
#[cfg(test)]
mod tests {
use super::{Cmd, E, Opts};
#[expect(clippy::too_many_lines, reason = "a lot of permutations to test")]
#[test]
fn successes() {
assert_eq!(
Cmd::try_from_args(["".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into()]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "-".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--all".into(), "-".into()]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: false,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "--allow-undefined-lints".into(), "-".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: true,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"--all".into(),
"--allow-undefined-lints".into(),
"-".into()
]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into()]),
Ok(Cmd::Allow(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--all".into()]),
Ok(Cmd::Allow(Opts {
all_lints: true,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Allow(Opts {
all_lints: false,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "-".into()]),
Ok(Cmd::Allow(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"allow".into(),
"--allow-undefined-lints".into(),
"--all".into()
]),
Ok(Cmd::Allow(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "allow".into(), "--all".into(), "-".into(),]),
Ok(Cmd::Allow(Opts {
all_lints: true,
allow_undefined_lints: false,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"allow".into(),
"--all".into(),
"--allow-undefined-lints".into(),
"-".into(),
]),
Ok(Cmd::Allow(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--all".into()]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: false,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "--allow-undefined-lints".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args(["".into(), "deny".into(), "-".into()]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: false,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"deny".into(),
"--all".into(),
"--allow-undefined-lints".into()
]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: false,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"deny".into(),
"--allow-undefined-lints".into(),
"-".into(),
]),
Ok(Cmd::Deny(Opts {
all_lints: false,
allow_undefined_lints: true,
read_stdin: true,
}))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"deny".into(),
"--allow-undefined-lints".into(),
"--all".into(),
"-".into(),
]),
Ok(Cmd::Deny(Opts {
all_lints: true,
allow_undefined_lints: true,
read_stdin: 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()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "-".into(), "deny".into(),]),
Err(E::UnknownArg("deny".into()))
);
assert_eq!(
Cmd::try_from_args(["".into(), "-".into(), "--all".into(),]),
Err(E::UnknownArg("--all".into()))
);
assert_eq!(
Cmd::try_from_args([
"".into(),
"allow".into(),
"-".into(),
"--allow-undefined-lints".into()
]),
Err(E::UnknownArg("--allow-undefined-lints".into()))
);
}
}