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 }