flagon-io/hello

public

Greets people from the command line. The sample repository for g1t.

Commit

Add --times N flag that prints the greeting N times

g1t agentcommitted Parenta6d7a0fBrowse files
3 files+109−190/3 viewed
+63−16
11 //! Greetings.
22
33 /// Parsed command-line options.
4−#[derive(Debug, Default, PartialEq, Eq)]
4+#[derive(Debug, PartialEq, Eq)]
55 pub struct Options {
66 /// The name to greet, if one was given.
77 pub name: Option<String>,
99 pub shout: bool,
1010 /// Whether to print the crate version and exit (`--version`).
1111 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+ }
1225 }
1326
1427 /// Parses command-line arguments (excluding the program name).
1528 ///
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> {
2037 let mut options = Options::default();
21− for arg in args {
38+ let mut args = args.into_iter();
39+ while let Some(arg) = args.next() {
2240 if arg == "--shout" {
2341 options.shout = true;
2442 } else if arg == "--version" {
2543 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:?}"))?;
2651 } else if !arg.starts_with("--") && options.name.is_none() {
2752 options.name = Some(arg);
2853 }
2954 }
30− options
55+ Ok(options)
3156 }
3257
3358 /// `text` upper-cased.
5984
6085 #[test]
6186 fn parse_args_no_args() {
62− assert_eq!(parse_args(args(&[])).name, None);
87+ assert_eq!(parse_args(args(&[])).unwrap().name, None);
6388 }
6489
6590 #[test]
6691 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"));
6893 }
6994
7095 #[test]
7196 fn parse_args_skips_leading_flag() {
7297 assert_eq!(
73− parse_args(args(&["--unknown", "Ada"])).name.as_deref(),
98+ parse_args(args(&["--unknown", "Ada"])).unwrap().name.as_deref(),
7499 Some("Ada")
75100 );
76101 }
77102
78103 #[test]
79104 fn parse_args_shout_defaults_off() {
80− assert!(!parse_args(args(&["Ada"])).shout);
105+ assert!(!parse_args(args(&["Ada"])).unwrap().shout);
81106 }
82107
83108 #[test]
84109 fn parse_args_shout_before_name() {
85− let options = parse_args(args(&["--shout", "Ada"]));
110+ let options = parse_args(args(&["--shout", "Ada"])).unwrap();
86111 assert!(options.shout);
87112 assert_eq!(options.name.as_deref(), Some("Ada"));
88113 }
89114
90115 #[test]
91116 fn parse_args_shout_after_name() {
92− let options = parse_args(args(&["Ada", "--shout"]));
117+ let options = parse_args(args(&["Ada", "--shout"])).unwrap();
93118 assert!(options.shout);
94119 assert_eq!(options.name.as_deref(), Some("Ada"));
95120 }
96121
97122 #[test]
98123 fn parse_args_shout_alone() {
99− let options = parse_args(args(&["--shout"]));
124+ let options = parse_args(args(&["--shout"])).unwrap();
100125 assert!(options.shout);
101126 assert_eq!(options.name, None);
102127 }
103128
104129 #[test]
105130 fn parse_args_version_defaults_off() {
106− assert!(!parse_args(args(&["Ada"])).version);
131+ assert!(!parse_args(args(&["Ada"])).unwrap().version);
107132 }
108133
109134 #[test]
110135 fn parse_args_version() {
111− let options = parse_args(args(&["--version"]));
136+ let options = parse_args(args(&["--version"])).unwrap();
112137 assert!(options.version);
113138 assert_eq!(options.name, None);
114139 }
115140
116141 #[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]
117164 fn shout_upper_cases_greeting() {
118165 assert_eq!(shout(&greeting("Ada")), "HELLO, ADA!");
119166 }
+12−3
1−use std::env;
1+use std::{env, process};
22
33 use hello::{greeting, parse_args, shout};
44
55 fn main() {
6− let options = parse_args(env::args().skip(1));
6+ let options = match parse_args(env::args().skip(1)) {
7+ Ok(options) => options,
8+ Err(message) => {
9+ eprintln!("error: {message}");
10+ process::exit(2);
11+ }
12+ };
713 if options.version {
814 println!("{}", env!("CARGO_PKG_VERSION"));
915 return;
1420 .or_else(|| env::var("USER").ok())
1521 .unwrap_or_else(|| "world".to_owned());
1622 let message = greeting(&name);
17− println!("{}", if options.shout { shout(&message) } else { message });
23+ let line = if options.shout { shout(&message) } else { message };
24+ for _ in 0..options.times {
25+ println!("{line}");
26+ }
1827 }
+34−0
1212 format!("{}\n", env!("CARGO_PKG_VERSION"))
1313 );
1414 }
15+
16+fn run(args: &[&str]) -> std::process::Output {
17+ Command::new(env!("CARGO_BIN_EXE_hello"))
18+ .args(args)
19+ .output()
20+ .unwrap()
21+}
22+
23+#[test]
24+fn times_prints_greeting_repeatedly() {
25+ let output = run(&["--times", "3", "Ada"]);
26+ assert!(output.status.success());
27+ assert_eq!(
28+ String::from_utf8(output.stdout).unwrap(),
29+ "Hello, Ada!\n".repeat(3)
30+ );
31+}
32+
33+#[test]
34+fn times_combines_with_shout() {
35+ let output = run(&["--times", "2", "--shout", "Ada"]);
36+ assert!(output.status.success());
37+ assert_eq!(
38+ String::from_utf8(output.stdout).unwrap(),
39+ "HELLO, ADA!\n".repeat(2)
40+ );
41+}
42+
43+#[test]
44+fn times_with_bad_value_fails() {
45+ let output = run(&["--times", "abc"]);
46+ assert!(!output.status.success());
47+ assert!(!output.stderr.is_empty());
48+}