| 1 | 1 | | //! Greetings. |
| 2 | 2 | | |
| 3 | 3 | | /// Parsed command-line options. |
| 4 | | − | #[derive(Debug, Default, PartialEq, Eq)] |
| 4 | + | #[derive(Debug, PartialEq, Eq)] |
| 5 | 5 | | pub struct Options { |
| 6 | 6 | | /// The name to greet, if one was given. |
| 7 | 7 | | pub name: Option<String>, |
| ⋯ |
| 9 | 9 | | pub shout: bool, |
| 10 | 10 | | /// Whether to print the crate version and exit (`--version`). |
| 11 | 11 | | pub version: bool, |
| 12 | + | /// How many times to print the greeting (`--times N`). |
| 13 | + | pub times: usize, |
| 14 | + | } |
| 15 | + | |
| 16 | + | impl Default for Options { |
| 17 | + | fn default() -> Self { |
| 18 | + | Options { |
| 19 | + | name: None, |
| 20 | + | shout: false, |
| 21 | + | version: false, |
| 22 | + | times: 1, |
| 23 | + | } |
| 24 | + | } |
| 12 | 25 | | } |
| 13 | 26 | | |
| 14 | 27 | | /// Parses command-line arguments (excluding the program name). |
| 15 | 28 | | /// |
| 16 | | − | /// `--shout` sets [`Options::shout`] and `--version` sets |
| 17 | | − | /// [`Options::version`]; other arguments starting with `--` are |
| 18 | | − | /// treated as flags and skipped. The name is the first remaining argument. |
| 19 | | − | pub fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Options { |
| 29 | + | /// `--shout` sets [`Options::shout`], `--version` sets |
| 30 | + | /// [`Options::version`], and `--times N` sets [`Options::times`] (the |
| 31 | + | /// next argument is consumed as the value); other arguments starting |
| 32 | + | /// with `--` are treated as flags and skipped. The name is the first |
| 33 | + | /// remaining argument. |
| 34 | + | /// |
| 35 | + | /// Returns an error message if `--times` has a missing or non-numeric value. |
| 36 | + | pub fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Options, String> { |
| 20 | 37 | | let mut options = Options::default(); |
| 21 | | − | for arg in args { |
| 38 | + | let mut args = args.into_iter(); |
| 39 | + | while let Some(arg) = args.next() { |
| 22 | 40 | | if arg == "--shout" { |
| 23 | 41 | | options.shout = true; |
| 24 | 42 | | } else if arg == "--version" { |
| 25 | 43 | | options.version = true; |
| 44 | + | } else if arg == "--times" { |
| 45 | + | let value = args |
| 46 | + | .next() |
| 47 | + | .ok_or_else(|| "--times requires a value".to_owned())?; |
| 48 | + | options.times = value |
| 49 | + | .parse() |
| 50 | + | .map_err(|_| format!("--times value must be a number, got {value:?}"))?; |
| 26 | 51 | | } else if !arg.starts_with("--") && options.name.is_none() { |
| 27 | 52 | | options.name = Some(arg); |
| 28 | 53 | | } |
| 29 | 54 | | } |
| 30 | | − | options |
| 55 | + | Ok(options) |
| 31 | 56 | | } |
| 32 | 57 | | |
| 33 | 58 | | /// `text` upper-cased. |
| ⋯ |
| 59 | 84 | | |
| 60 | 85 | | #[test] |
| 61 | 86 | | fn parse_args_no_args() { |
| 62 | | − | assert_eq!(parse_args(args(&[])).name, None); |
| 87 | + | assert_eq!(parse_args(args(&[])).unwrap().name, None); |
| 63 | 88 | | } |
| 64 | 89 | | |
| 65 | 90 | | #[test] |
| 66 | 91 | | fn parse_args_name() { |
| 67 | | − | assert_eq!(parse_args(args(&["Ada"])).name.as_deref(), Some("Ada")); |
| 92 | + | assert_eq!(parse_args(args(&["Ada"])).unwrap().name.as_deref(), Some("Ada")); |
| 68 | 93 | | } |
| 69 | 94 | | |
| 70 | 95 | | #[test] |
| 71 | 96 | | fn parse_args_skips_leading_flag() { |
| 72 | 97 | | assert_eq!( |
| 73 | | − | parse_args(args(&["--unknown", "Ada"])).name.as_deref(), |
| 98 | + | parse_args(args(&["--unknown", "Ada"])).unwrap().name.as_deref(), |
| 74 | 99 | | Some("Ada") |
| 75 | 100 | | ); |
| 76 | 101 | | } |
| 77 | 102 | | |
| 78 | 103 | | #[test] |
| 79 | 104 | | fn parse_args_shout_defaults_off() { |
| 80 | | − | assert!(!parse_args(args(&["Ada"])).shout); |
| 105 | + | assert!(!parse_args(args(&["Ada"])).unwrap().shout); |
| 81 | 106 | | } |
| 82 | 107 | | |
| 83 | 108 | | #[test] |
| 84 | 109 | | fn parse_args_shout_before_name() { |
| 85 | | − | let options = parse_args(args(&["--shout", "Ada"])); |
| 110 | + | let options = parse_args(args(&["--shout", "Ada"])).unwrap(); |
| 86 | 111 | | assert!(options.shout); |
| 87 | 112 | | assert_eq!(options.name.as_deref(), Some("Ada")); |
| 88 | 113 | | } |
| 89 | 114 | | |
| 90 | 115 | | #[test] |
| 91 | 116 | | fn parse_args_shout_after_name() { |
| 92 | | − | let options = parse_args(args(&["Ada", "--shout"])); |
| 117 | + | let options = parse_args(args(&["Ada", "--shout"])).unwrap(); |
| 93 | 118 | | assert!(options.shout); |
| 94 | 119 | | assert_eq!(options.name.as_deref(), Some("Ada")); |
| 95 | 120 | | } |
| 96 | 121 | | |
| 97 | 122 | | #[test] |
| 98 | 123 | | fn parse_args_shout_alone() { |
| 99 | | − | let options = parse_args(args(&["--shout"])); |
| 124 | + | let options = parse_args(args(&["--shout"])).unwrap(); |
| 100 | 125 | | assert!(options.shout); |
| 101 | 126 | | assert_eq!(options.name, None); |
| 102 | 127 | | } |
| 103 | 128 | | |
| 104 | 129 | | #[test] |
| 105 | 130 | | fn parse_args_version_defaults_off() { |
| 106 | | − | assert!(!parse_args(args(&["Ada"])).version); |
| 131 | + | assert!(!parse_args(args(&["Ada"])).unwrap().version); |
| 107 | 132 | | } |
| 108 | 133 | | |
| 109 | 134 | | #[test] |
| 110 | 135 | | fn parse_args_version() { |
| 111 | | − | let options = parse_args(args(&["--version"])); |
| 136 | + | let options = parse_args(args(&["--version"])).unwrap(); |
| 112 | 137 | | assert!(options.version); |
| 113 | 138 | | assert_eq!(options.name, None); |
| 114 | 139 | | } |
| 115 | 140 | | |
| 116 | 141 | | #[test] |
| 142 | + | fn parse_args_times_defaults_to_one() { |
| 143 | + | assert_eq!(parse_args(args(&["Ada"])).unwrap().times, 1); |
| 144 | + | } |
| 145 | + | |
| 146 | + | #[test] |
| 147 | + | fn parse_args_times_valid() { |
| 148 | + | let options = parse_args(args(&["--times", "3", "Ada"])).unwrap(); |
| 149 | + | assert_eq!(options.times, 3); |
| 150 | + | assert_eq!(options.name.as_deref(), Some("Ada")); |
| 151 | + | } |
| 152 | + | |
| 153 | + | #[test] |
| 154 | + | fn parse_args_times_missing_value() { |
| 155 | + | assert!(parse_args(args(&["--times"])).is_err()); |
| 156 | + | } |
| 157 | + | |
| 158 | + | #[test] |
| 159 | + | fn parse_args_times_non_number() { |
| 160 | + | assert!(parse_args(args(&["--times", "abc", "Ada"])).is_err()); |
| 161 | + | } |
| 162 | + | |
| 163 | + | #[test] |
| 117 | 164 | | fn shout_upper_cases_greeting() { |
| 118 | 165 | | assert_eq!(shout(&greeting("Ada")), "HELLO, ADA!"); |
| 119 | 166 | | } |