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 }