lints 0.1.0

Writes [lints.rust] to stdout such that all lints are denied or allowed.
use super::{
    ExitCode,
    io::{self, Write as _},
};
use std::ffi::OsString;
/// Error from parsing CLI arguments.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum E {
    /// No arguments exist including the name of the program which is assumed to be the first argument.
    NoArgs,
    /// The contained string is not an argument that is supported.
    UnknownArg(OsString),
}
impl E {
    /// Writes `self` to `stderr`.
    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)
    }
}
/// The command passed.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum Cmd {
    /// `"allow"` all lints.
    ///
    /// The first contained `bool` is `true` iff all lint groups and individual lints should be `allow`ed
    /// instead of just `"warnings"` lint group and `deny`-by-default lints.
    /// The second contained `bool` is `true` iff lint groups are allowed to contain undefined lints.
    Allow(bool, bool),
    /// `"deny"`.
    ///
    /// The first contained `bool` is `true` iff all individual lints should be `deny`ed instead of just
    /// the lint groups and `allow`-by-default lints that are not part of any group.
    /// The second contained `bool` is `true` iff lint groups are allowed to contain undefined lints.
    Deny(bool, bool),
    /// `"help"`.
    Help,
    /// `"version"`.
    Version,
}
/// Options we allow.
#[derive(Clone, Copy)]
enum Opt {
    /// No option.
    None,
    /// `"--all"`.
    All,
    /// `"--allow-undefined-lints"`.
    AllowUndefined,
    /// `"--all"` and `"--allow-undefined-lints"`.
    AllAndAllowUndefined,
}
impl Cmd {
    /// Extracts options from `opts`.
    fn get_opts<I: Iterator<Item = OsString>>(
        mut opts: I,
        opt: Opt,
        arg: OsString,
    ) -> Result<Opt, E> {
        /// `b"--all"`.
        const ALL: &[u8] = b"--all".as_slice();
        /// `b"--allow-undefined-lints"`.
        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))
        })
    }
    /// Parses the CLI arguments.
    #[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()))
        );
    }
}