lints

`rustc` lints.
git clone https://git.philomathiclife.com/repos/lints
Log | Files | Refs | README

args.rs (14535B)


      1 use super::{
      2     ExitCode,
      3     io::{self, Write as _},
      4 };
      5 use std::ffi::OsString;
      6 /// Error from parsing CLI arguments.
      7 #[cfg_attr(test, derive(Debug, PartialEq))]
      8 pub(crate) enum E {
      9     /// No arguments exist including the name of the program which is assumed to be the first argument.
     10     NoArgs,
     11     /// The contained string is not an argument that is supported.
     12     UnknownArg(OsString),
     13 }
     14 impl E {
     15     /// Writes `self` to `stderr`.
     16     pub(crate) fn into_exit_code(self) -> ExitCode {
     17         let mut stderr = io::stderr().lock();
     18         match self {
     19             Self::NoArgs => writeln!(
     20                 stderr,
     21                 "No arguments were passed including the first argument which is assumed to be the name of the program. See lints help for more information."
     22             ),
     23             Self::UnknownArg(arg) => writeln!(
     24                 stderr,
     25                 "Unrecognized argument '{}' was passed. See lints help for more information.",
     26                 arg.display()
     27             ),
     28         }.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
     29     }
     30 }
     31 /// The command passed.
     32 #[cfg_attr(test, derive(Debug, PartialEq))]
     33 #[derive(Clone, Copy)]
     34 pub(crate) enum Cmd {
     35     /// `"allow"` all lints.
     36     Allow(Opts),
     37     /// `"deny"`.
     38     Deny(Opts),
     39     /// `"help"`.
     40     Help,
     41     /// `"version"`.
     42     Version,
     43 }
     44 /// Options to process lints.
     45 #[cfg_attr(test, derive(Debug, PartialEq))]
     46 #[derive(Clone, Copy, Default)]
     47 pub(crate) struct Opts {
     48     /// `--all` was passed.
     49     pub all_lints: bool,
     50     /// `--allow-undefined-lints` was passed.
     51     pub allow_undefined_lints: bool,
     52     /// `-` was passed.
     53     pub read_stdin: bool,
     54 }
     55 impl Opts {
     56     /// Add options from `opts` to `self`.
     57     ///
     58     /// Returns `None` iff successful; otherwise returns the problematic argument.
     59     fn add<I: Iterator<Item = OsString>>(
     60         &mut self,
     61         mut opts: I,
     62         arg: OsString,
     63     ) -> Option<OsString> {
     64         /// `b'-'`.
     65         const DASH: u8 = b'-';
     66         /// `b"--all"`.
     67         const ALL: &[u8] = b"--all".as_slice();
     68         /// `b"--allow-undefined-lints"`.
     69         const ALLOW_UNDEFINED_LINTS: &[u8] = b"--allow-undefined-lints".as_slice();
     70         match arg.as_encoded_bytes() {
     71             &[DASH] => {
     72                 if self.read_stdin {
     73                     Err(())
     74                 } else {
     75                     self.read_stdin = true;
     76                     Ok(())
     77                 }
     78             }
     79             ALL => {
     80                 if self.all_lints || self.read_stdin {
     81                     Err(())
     82                 } else {
     83                     self.all_lints = true;
     84                     Ok(())
     85                 }
     86             }
     87             ALLOW_UNDEFINED_LINTS => {
     88                 if self.allow_undefined_lints || self.read_stdin {
     89                     Err(())
     90                 } else {
     91                     self.allow_undefined_lints = true;
     92                     Ok(())
     93                 }
     94             }
     95             _ => Err(()),
     96         }
     97         .map_or(Some(arg), |()| {
     98             opts.next().and_then(|opt| self.add(opts, opt))
     99         })
    100     }
    101 }
    102 impl Cmd {
    103     /// Parses the CLI arguments.
    104     pub(crate) fn try_from_args<I: IntoIterator<Item = OsString>>(iter: I) -> Result<Self, E> {
    105         let mut args = iter.into_iter();
    106         args.next().ok_or(E::NoArgs).and_then(|_| {
    107             args.next().map_or_else(
    108                 || Ok(Self::Deny(Opts::default())),
    109                 |cmd| match cmd.as_encoded_bytes() {
    110                     b"allow" => args.next().map_or_else(
    111                         || Ok(Self::Allow(Opts::default())),
    112                         |arg| {
    113                             let mut opts = Opts::default();
    114                             opts.add(args, arg)
    115                                 .map_or(Ok(Self::Allow(opts)), |opt| Err(E::UnknownArg(opt)))
    116                         },
    117                     ),
    118                     b"deny" => args.next().map_or_else(
    119                         || Ok(Self::Deny(Opts::default())),
    120                         |arg| {
    121                             let mut opts = Opts::default();
    122                             opts.add(args, arg)
    123                                 .map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
    124                         },
    125                     ),
    126                     b"help" => args
    127                         .next()
    128                         .map_or(Ok(Self::Help), |opt| Err(E::UnknownArg(opt))),
    129                     b"version" => args
    130                         .next()
    131                         .map_or(Ok(Self::Version), |opt| Err(E::UnknownArg(opt))),
    132                     _ => {
    133                         let mut opts = Opts::default();
    134                         opts.add(args, cmd)
    135                             .map_or(Ok(Self::Deny(opts)), |opt| Err(E::UnknownArg(opt)))
    136                     }
    137                 },
    138             )
    139         })
    140     }
    141 }
    142 #[cfg(test)]
    143 mod tests {
    144     use super::{Cmd, E, Opts};
    145     #[expect(clippy::too_many_lines, reason = "a lot of permutations to test")]
    146     #[test]
    147     fn successes() {
    148         assert_eq!(
    149             Cmd::try_from_args(["".into()]),
    150             Ok(Cmd::Deny(Opts {
    151                 all_lints: false,
    152                 allow_undefined_lints: false,
    153                 read_stdin: false,
    154             }))
    155         );
    156         assert_eq!(
    157             Cmd::try_from_args(["".into(), "--all".into()]),
    158             Ok(Cmd::Deny(Opts {
    159                 all_lints: true,
    160                 allow_undefined_lints: false,
    161                 read_stdin: false,
    162             }))
    163         );
    164         assert_eq!(
    165             Cmd::try_from_args(["".into(), "--allow-undefined-lints".into()]),
    166             Ok(Cmd::Deny(Opts {
    167                 all_lints: false,
    168                 allow_undefined_lints: true,
    169                 read_stdin: false,
    170             }))
    171         );
    172         assert_eq!(
    173             Cmd::try_from_args(["".into(), "-".into()]),
    174             Ok(Cmd::Deny(Opts {
    175                 all_lints: false,
    176                 allow_undefined_lints: false,
    177                 read_stdin: true,
    178             }))
    179         );
    180         assert_eq!(
    181             Cmd::try_from_args(["".into(), "--all".into(), "--allow-undefined-lints".into()]),
    182             Ok(Cmd::Deny(Opts {
    183                 all_lints: true,
    184                 allow_undefined_lints: true,
    185                 read_stdin: false,
    186             }))
    187         );
    188         assert_eq!(
    189             Cmd::try_from_args(["".into(), "--all".into(), "-".into()]),
    190             Ok(Cmd::Deny(Opts {
    191                 all_lints: true,
    192                 allow_undefined_lints: false,
    193                 read_stdin: true,
    194             }))
    195         );
    196         assert_eq!(
    197             Cmd::try_from_args(["".into(), "--allow-undefined-lints".into(), "-".into()]),
    198             Ok(Cmd::Deny(Opts {
    199                 all_lints: false,
    200                 allow_undefined_lints: true,
    201                 read_stdin: true,
    202             }))
    203         );
    204         assert_eq!(
    205             Cmd::try_from_args([
    206                 "".into(),
    207                 "--all".into(),
    208                 "--allow-undefined-lints".into(),
    209                 "-".into()
    210             ]),
    211             Ok(Cmd::Deny(Opts {
    212                 all_lints: true,
    213                 allow_undefined_lints: true,
    214                 read_stdin: true,
    215             }))
    216         );
    217         assert_eq!(
    218             Cmd::try_from_args(["".into(), "allow".into()]),
    219             Ok(Cmd::Allow(Opts {
    220                 all_lints: false,
    221                 allow_undefined_lints: false,
    222                 read_stdin: false,
    223             }))
    224         );
    225         assert_eq!(
    226             Cmd::try_from_args(["".into(), "allow".into(), "--all".into()]),
    227             Ok(Cmd::Allow(Opts {
    228                 all_lints: true,
    229                 allow_undefined_lints: false,
    230                 read_stdin: false,
    231             }))
    232         );
    233         assert_eq!(
    234             Cmd::try_from_args(["".into(), "allow".into(), "--allow-undefined-lints".into()]),
    235             Ok(Cmd::Allow(Opts {
    236                 all_lints: false,
    237                 allow_undefined_lints: true,
    238                 read_stdin: false,
    239             }))
    240         );
    241         assert_eq!(
    242             Cmd::try_from_args(["".into(), "allow".into(), "-".into()]),
    243             Ok(Cmd::Allow(Opts {
    244                 all_lints: false,
    245                 allow_undefined_lints: false,
    246                 read_stdin: true,
    247             }))
    248         );
    249         assert_eq!(
    250             Cmd::try_from_args([
    251                 "".into(),
    252                 "allow".into(),
    253                 "--allow-undefined-lints".into(),
    254                 "--all".into()
    255             ]),
    256             Ok(Cmd::Allow(Opts {
    257                 all_lints: true,
    258                 allow_undefined_lints: true,
    259                 read_stdin: false,
    260             }))
    261         );
    262         assert_eq!(
    263             Cmd::try_from_args(["".into(), "allow".into(), "--all".into(), "-".into(),]),
    264             Ok(Cmd::Allow(Opts {
    265                 all_lints: true,
    266                 allow_undefined_lints: false,
    267                 read_stdin: true,
    268             }))
    269         );
    270         assert_eq!(
    271             Cmd::try_from_args([
    272                 "".into(),
    273                 "allow".into(),
    274                 "--all".into(),
    275                 "--allow-undefined-lints".into(),
    276                 "-".into(),
    277             ]),
    278             Ok(Cmd::Allow(Opts {
    279                 all_lints: true,
    280                 allow_undefined_lints: true,
    281                 read_stdin: true,
    282             }))
    283         );
    284         assert_eq!(
    285             Cmd::try_from_args(["".into(), "deny".into()]),
    286             Ok(Cmd::Deny(Opts {
    287                 all_lints: false,
    288                 allow_undefined_lints: false,
    289                 read_stdin: false,
    290             }))
    291         );
    292         assert_eq!(
    293             Cmd::try_from_args(["".into(), "deny".into(), "--all".into()]),
    294             Ok(Cmd::Deny(Opts {
    295                 all_lints: true,
    296                 allow_undefined_lints: false,
    297                 read_stdin: false,
    298             }))
    299         );
    300         assert_eq!(
    301             Cmd::try_from_args(["".into(), "deny".into(), "--allow-undefined-lints".into()]),
    302             Ok(Cmd::Deny(Opts {
    303                 all_lints: false,
    304                 allow_undefined_lints: true,
    305                 read_stdin: false,
    306             }))
    307         );
    308         assert_eq!(
    309             Cmd::try_from_args(["".into(), "deny".into(), "-".into()]),
    310             Ok(Cmd::Deny(Opts {
    311                 all_lints: false,
    312                 allow_undefined_lints: false,
    313                 read_stdin: true,
    314             }))
    315         );
    316         assert_eq!(
    317             Cmd::try_from_args([
    318                 "".into(),
    319                 "deny".into(),
    320                 "--all".into(),
    321                 "--allow-undefined-lints".into()
    322             ]),
    323             Ok(Cmd::Deny(Opts {
    324                 all_lints: true,
    325                 allow_undefined_lints: true,
    326                 read_stdin: false,
    327             }))
    328         );
    329         assert_eq!(
    330             Cmd::try_from_args([
    331                 "".into(),
    332                 "deny".into(),
    333                 "--allow-undefined-lints".into(),
    334                 "-".into(),
    335             ]),
    336             Ok(Cmd::Deny(Opts {
    337                 all_lints: false,
    338                 allow_undefined_lints: true,
    339                 read_stdin: true,
    340             }))
    341         );
    342         assert_eq!(
    343             Cmd::try_from_args([
    344                 "".into(),
    345                 "deny".into(),
    346                 "--allow-undefined-lints".into(),
    347                 "--all".into(),
    348                 "-".into(),
    349             ]),
    350             Ok(Cmd::Deny(Opts {
    351                 all_lints: true,
    352                 allow_undefined_lints: true,
    353                 read_stdin: true,
    354             }))
    355         );
    356         assert_eq!(
    357             Cmd::try_from_args(["".into(), "help".into()]),
    358             Ok(Cmd::Help)
    359         );
    360         assert_eq!(
    361             Cmd::try_from_args(["doesn't matter what this is".into(), "version".into()]),
    362             Ok(Cmd::Version)
    363         );
    364     }
    365     #[test]
    366     fn errs() {
    367         assert_eq!(Cmd::try_from_args([]), Err(E::NoArgs));
    368         assert_eq!(
    369             Cmd::try_from_args(["".into(), "".into()]),
    370             Err(E::UnknownArg("".into()))
    371         );
    372         assert_eq!(
    373             Cmd::try_from_args(["".into(), "foo".into()]),
    374             Err(E::UnknownArg("foo".into()))
    375         );
    376         assert_eq!(
    377             Cmd::try_from_args(["".into(), "help".into(), "version".into()]),
    378             Err(E::UnknownArg("version".into()))
    379         );
    380         assert_eq!(
    381             Cmd::try_from_args(["".into(), "version".into(), "allow".into()]),
    382             Err(E::UnknownArg("allow".into()))
    383         );
    384         assert_eq!(
    385             Cmd::try_from_args(["".into(), "allow".into(), "allow".into()]),
    386             Err(E::UnknownArg("allow".into()))
    387         );
    388         assert_eq!(
    389             Cmd::try_from_args(["".into(), "deny".into(), "allow".into()]),
    390             Err(E::UnknownArg("allow".into()))
    391         );
    392         assert_eq!(
    393             Cmd::try_from_args(["".into(), "--all".into(), "allow".into()]),
    394             Err(E::UnknownArg("allow".into()))
    395         );
    396         assert_eq!(
    397             Cmd::try_from_args(["".into(), "--allow-undefined-lints".into(), "deny".into()]),
    398             Err(E::UnknownArg("deny".into()))
    399         );
    400         assert_eq!(
    401             Cmd::try_from_args(["".into(), "deny".into(), "--all".into(), "".into()]),
    402             Err(E::UnknownArg("".into()))
    403         );
    404         assert_eq!(
    405             Cmd::try_from_args([
    406                 "".into(),
    407                 "--all".into(),
    408                 "--allow-undefined-lints".into(),
    409                 "--all".into()
    410             ]),
    411             Err(E::UnknownArg("--all".into()))
    412         );
    413         assert_eq!(
    414             Cmd::try_from_args([
    415                 "".into(),
    416                 "deny".into(),
    417                 "--all".into(),
    418                 "--allow-undefined-lints".into(),
    419                 "foo".into()
    420             ]),
    421             Err(E::UnknownArg("foo".into()))
    422         );
    423         assert_eq!(
    424             Cmd::try_from_args(["".into(), "-".into(), "deny".into(),]),
    425             Err(E::UnknownArg("deny".into()))
    426         );
    427         assert_eq!(
    428             Cmd::try_from_args(["".into(), "-".into(), "--all".into(),]),
    429             Err(E::UnknownArg("--all".into()))
    430         );
    431         assert_eq!(
    432             Cmd::try_from_args([
    433                 "".into(),
    434                 "allow".into(),
    435                 "-".into(),
    436                 "--allow-undefined-lints".into()
    437             ]),
    438             Err(E::UnknownArg("--allow-undefined-lints".into()))
    439         );
    440     }
    441 }