rpz

Response policy zone (RPZ) file generator.
git clone https://git.philomathiclife.com/repos/rpz
Log | Files | Refs | README

args.rs (9554B)


      1 use core::{
      2     error::Error,
      3     fmt::{self, Display, Formatter},
      4 };
      5 use rpz::file::AbsFilePath;
      6 use std::env::{self, Args};
      7 /// Unit tests.
      8 #[cfg(test)]
      9 mod tests;
     10 /// Error returned when parsing arguments passed to the application.
     11 #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
     12 pub(crate) enum ArgsErr {
     13     /// Error when no arguments were passed to the application.
     14     NoArgs,
     15     /// Error when `-f`/`--file` is not passed or is passed
     16     /// without a path to the file.
     17     ConfigPathNotPassed,
     18     /// Error when an invalid option is passed. The contained [`String`]
     19     /// is the value of the invalid option.
     20     InvalidOption(String),
     21     /// Some options when passed must be the only option passed.
     22     /// For such options, this is the error when other options are passed.
     23     MoreThanOneOption,
     24     /// Error when the passed path to the config file is not `-` nor an absolute file path to a file.
     25     InvalidConfigPath,
     26     /// Error when an option is passed more than once.
     27     DuplicateOption(&'static str),
     28     /// Error when the quiet and verbose options were passed.
     29     QuietAndVerbose,
     30 }
     31 impl Display for ArgsErr {
     32     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     33         match *self {
     34             Self::NoArgs => write!(f, "no arguments were passed, but at least two are required containing the option '-f' and its value which must be an absolute path to the config file"),
     35             Self::ConfigPathNotPassed => f.write_str("'-f' followed by '-' or the absolute file path to the config file was not passed"),
     36             Self::InvalidOption(ref arg) => write!(f, "{arg} is an invalid option. Only '-f'/'--file' followed by the absolute path to the config file, '-q'/'--quiet', '-h'/'--help', '-v'/'--verbose' and '-V'/'--version' are allowed"),
     37             Self::MoreThanOneOption => f.write_str("'-V'/'--version' or '-h'/'--help' was passed with other options; but when those options are passed, they must be the only one"),
     38             Self::InvalidConfigPath => write!(f, "an absolute file path to the config file or '-' was not passed"),
     39             Self::DuplicateOption(arg) => write!(f, "{arg} was passed more than once"),
     40             Self::QuietAndVerbose => f.write_str("'-q'/'--quiet' and '-v'/'--verbose' were both passed, but at most only one of them is allowed to be passed"),
     41         }
     42     }
     43 }
     44 impl Error for ArgsErr {}
     45 /// The location of the configuration file.
     46 #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
     47 pub(crate) enum ConfigPath {
     48     /// The config file is to be read from `stdin`.
     49     Stdin,
     50     /// The config file resides on the local file system.
     51     Path(AbsFilePath<false>),
     52 }
     53 /// The options passed to the application.
     54 #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
     55 pub(crate) enum Opts {
     56     /// Variant when no arguments were passed.
     57     None,
     58     /// Variant when the help argument was passed.
     59     Help,
     60     /// Variant when the version argument was passed.
     61     Version,
     62     /// Variant when the quiet argument was passed.
     63     Quiet,
     64     /// Variant when the verbose argument was passed.
     65     Verbose,
     66     /// Variant when the file argument with the path to the file was passed.
     67     Config(ConfigPath),
     68     /// Variant when the quiet argument and the file argument with the path to the file
     69     /// were passed.
     70     ConfigQuiet(ConfigPath),
     71     /// Variant when the verbose argument and the file argument with the path to the file
     72     /// were passed.
     73     ConfigVerbose(ConfigPath),
     74 }
     75 impl Opts {
     76     /// Returns `Opts` based on arguments passed to the application.
     77     #[expect(clippy::too_many_lines, reason = "this is fine")]
     78     pub(crate) fn from_args() -> Result<Self, ArgsErr> {
     79         /// Attempts to parse the next `Arg` into `-` or an absolute
     80         /// path to a file.
     81         fn get_path(args: &mut Args) -> Result<ConfigPath, ArgsErr> {
     82             args.next()
     83                 .map_or(Err(ArgsErr::ConfigPathNotPassed), |path| {
     84                     if path == "-" {
     85                         Ok(ConfigPath::Stdin)
     86                     } else {
     87                         AbsFilePath::<false>::from_string(path)
     88                             .map_or(Err(ArgsErr::InvalidConfigPath), |config| {
     89                                 Ok(ConfigPath::Path(config))
     90                             })
     91                     }
     92                 })
     93         }
     94         let mut args = env::args();
     95         if args.next().is_some() {
     96             let mut opts = Self::None;
     97             while let Some(arg) = args.next() {
     98                 match arg.as_str() {
     99                     "-h" | "--help" => match opts {
    100                         Self::None => {
    101                             opts = Self::Help;
    102                         }
    103                         Self::Help => return Err(ArgsErr::DuplicateOption("-h/--help")),
    104                         Self::Verbose
    105                         | Self::Quiet
    106                         | Self::Version
    107                         | Self::Config(_)
    108                         | Self::ConfigQuiet(_)
    109                         | Self::ConfigVerbose(_) => return Err(ArgsErr::MoreThanOneOption),
    110                     },
    111                     "-V" | "--version" => match opts {
    112                         Self::None => {
    113                             opts = Self::Version;
    114                         }
    115                         Self::Version => return Err(ArgsErr::DuplicateOption("-V/--version")),
    116                         Self::Verbose
    117                         | Self::Quiet
    118                         | Self::Help
    119                         | Self::Config(_)
    120                         | Self::ConfigQuiet(_)
    121                         | Self::ConfigVerbose(_) => return Err(ArgsErr::MoreThanOneOption),
    122                     },
    123                     "-f" | "--file" => match opts {
    124                         Self::None => {
    125                             opts = Self::Config(get_path(&mut args)?);
    126                         }
    127                         Self::Quiet => {
    128                             opts = Self::ConfigQuiet(get_path(&mut args)?);
    129                         }
    130                         Self::Verbose => {
    131                             opts = Self::ConfigVerbose(get_path(&mut args)?);
    132                         }
    133                         Self::Config(_) | Self::ConfigQuiet(_) | Self::ConfigVerbose(_) => {
    134                             return Err(ArgsErr::DuplicateOption("-f/--file"));
    135                         }
    136                         Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
    137                     },
    138                     "-q" | "--quiet" => match opts {
    139                         Self::None => {
    140                             opts = Self::Quiet;
    141                         }
    142                         Self::Config(path) => {
    143                             opts = Self::ConfigQuiet(path);
    144                         }
    145                         Self::Quiet | Self::ConfigQuiet(_) => {
    146                             return Err(ArgsErr::DuplicateOption("-q/--quiet"));
    147                         }
    148                         Self::Verbose | Self::ConfigVerbose(_) => {
    149                             return Err(ArgsErr::QuietAndVerbose);
    150                         }
    151                         Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
    152                     },
    153                     "-v" | "--verbose" => match opts {
    154                         Self::None => {
    155                             opts = Self::Verbose;
    156                         }
    157                         Self::Config(path) => {
    158                             opts = Self::ConfigVerbose(path);
    159                         }
    160                         Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose),
    161                         Self::Verbose | Self::ConfigVerbose(_) => {
    162                             return Err(ArgsErr::DuplicateOption("-v/--verbose"));
    163                         }
    164                         Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
    165                     },
    166                     "-fq" | "-qf" => match opts {
    167                         Self::None => {
    168                             opts = Self::ConfigQuiet(get_path(&mut args)?);
    169                         }
    170                         Self::Config(_) | Self::ConfigQuiet(_) => {
    171                             return Err(ArgsErr::DuplicateOption("-f/--file"));
    172                         }
    173                         Self::Quiet => return Err(ArgsErr::DuplicateOption("-q/--quiet")),
    174                         Self::Verbose | Self::ConfigVerbose(_) => {
    175                             return Err(ArgsErr::QuietAndVerbose);
    176                         }
    177                         Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
    178                     },
    179                     "-fv" | "-vf" => match opts {
    180                         Self::None => {
    181                             opts = Self::ConfigVerbose(get_path(&mut args)?);
    182                         }
    183                         Self::Config(_) | Self::ConfigVerbose(_) => {
    184                             return Err(ArgsErr::DuplicateOption("-f/--file"));
    185                         }
    186                         Self::Verbose => return Err(ArgsErr::DuplicateOption("-v/--verbose")),
    187                         Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose),
    188                         Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
    189                     },
    190                     _ => return Err(ArgsErr::InvalidOption(arg)),
    191                 }
    192             }
    193             Ok(opts)
    194         } else {
    195             Err(ArgsErr::NoArgs)
    196         }
    197     }
    198 }