flagon-io/hello

public

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

Commit

Merge #53: Add a --lang option (en, es, fr) to the greeter

g1t merge queuecommitted Parentsf7e69cdff5df87Browse files
3 files+130−70/3 viewed
+89−5
11 //! Greetings.
22
3+/// A supported greeting language.
4+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5+pub enum Lang {
6+ #[default]
7+ En,
8+ Es,
9+ Fr,
10+}
11+
12+impl Lang {
13+ /// Parses a language code (`en`, `es` or `fr`).
14+ pub fn parse(code: &str) -> Option<Lang> {
15+ match code {
16+ "en" => Some(Lang::En),
17+ "es" => Some(Lang::Es),
18+ "fr" => Some(Lang::Fr),
19+ _ => None,
20+ }
21+ }
22+}
23+
324 /// Parsed command-line options.
425 #[derive(Debug, PartialEq, Eq)]
526 pub struct Options {
1132 pub version: bool,
1233 /// How many times to print the greeting (`--times N`).
1334 pub times: usize,
35+ /// The greeting language (`--lang CODE`).
36+ pub lang: Lang,
1437 }
1538
1639 impl Default for Options {
2043 shout: false,
2144 version: false,
2245 times: 1,
46+ lang: Lang::En,
2347 }
2448 }
2549 }
2852 ///
2953 /// `--shout` sets [`Options::shout`], `--version` sets
3054 /// [`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.
55+/// next argument is consumed as the value), and `--lang CODE` sets
56+/// [`Options::lang`] (likewise consuming the next argument); other
57+/// arguments starting with `--` are treated as flags and skipped. The
58+/// name is the first remaining argument.
3459 ///
35−/// Returns an error message if `--times` has a missing or non-numeric value.
60+/// Returns an error message if `--times` has a missing or non-numeric
61+/// value, or if `--lang` has a missing or unsupported value.
3662 pub fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Options, String> {
3763 let mut options = Options::default();
3864 let mut args = args.into_iter();
4874 options.times = value
4975 .parse()
5076 .map_err(|_| format!("--times value must be a number, got {value:?}"))?;
77+ } else if arg == "--lang" {
78+ let value = args
79+ .next()
80+ .ok_or_else(|| "--lang requires a value".to_owned())?;
81+ options.lang = Lang::parse(&value).ok_or_else(|| {
82+ format!("--lang value must be one of en, es, fr, got {value:?}")
83+ })?;
5184 } else if !arg.starts_with("--") && options.name.is_none() {
5285 options.name = Some(arg);
5386 }
6295
6396 /// The greeting for `name`.
6497 pub fn greeting(name: &str) -> String {
98+ greeting_in(name, Lang::En)
99+}
100+
101+/// The greeting for `name` in `lang`.
102+pub fn greeting_in(name: &str, lang: Lang) -> String {
65103 let trimmed = name.trim();
66104 let display_name = if trimmed.is_empty() { "world" } else { trimmed };
67− format!("Hi, {display_name}!")
105+ match lang {
106+ Lang::En => format!("Hi, {display_name}!"),
107+ Lang::Es => format!("¡Hola, {display_name}!"),
108+ Lang::Fr => format!("Bonjour, {display_name} !"),
109+ }
68110 }
69111
70112 /// The farewell for `name`.
161203 }
162204
163205 #[test]
206+ fn parse_args_lang_defaults_to_en() {
207+ assert_eq!(parse_args(args(&["Ada"])).unwrap().lang, Lang::En);
208+ }
209+
210+ #[test]
211+ fn parse_args_lang_valid() {
212+ let options = parse_args(args(&["--lang", "es", "Ana"])).unwrap();
213+ assert_eq!(options.lang, Lang::Es);
214+ assert_eq!(options.name.as_deref(), Some("Ana"));
215+ assert_eq!(parse_args(args(&["--lang", "fr"])).unwrap().lang, Lang::Fr);
216+ }
217+
218+ #[test]
219+ fn parse_args_lang_missing_value() {
220+ assert_eq!(
221+ parse_args(args(&["--lang"])).unwrap_err(),
222+ "--lang requires a value"
223+ );
224+ }
225+
226+ #[test]
227+ fn parse_args_lang_unknown() {
228+ assert_eq!(
229+ parse_args(args(&["--lang", "de"])).unwrap_err(),
230+ "--lang value must be one of en, es, fr, got \"de\""
231+ );
232+ }
233+
234+ #[test]
235+ fn greeting_in_each_language() {
236+ assert_eq!(greeting_in("Ada", Lang::En), "Hi, Ada!");
237+ assert_eq!(greeting_in("Ana", Lang::Es), "¡Hola, Ana!");
238+ assert_eq!(greeting_in("Luc", Lang::Fr), "Bonjour, Luc !");
239+ }
240+
241+ #[test]
242+ fn greeting_in_trims_and_falls_back_to_world() {
243+ assert_eq!(greeting_in(" Ana ", Lang::Es), "¡Hola, Ana!");
244+ assert_eq!(greeting_in(" ", Lang::Fr), "Bonjour, world !");
245+ }
246+
247+ #[test]
164248 fn shout_upper_cases_greeting() {
165249 assert_eq!(shout(&greeting("Ada")), "HI, ADA!");
166250 }
+2−2
11 use std::{env, process};
22
3−use hello::{greeting, parse_args, shout};
3+use hello::{greeting_in, parse_args, shout};
44
55 fn main() {
66 let options = match parse_args(env::args().skip(1)) {
1919 .name
2020 .or_else(|| env::var("USER").ok())
2121 .unwrap_or_else(|| "world".to_owned());
22− let message = greeting(&name);
22+ let message = greeting_in(&name, options.lang);
2323 let line = if options.shout { shout(&message) } else { message };
2424 for _ in 0..options.times {
2525 println!("{line}");
+39−0
4646 assert!(!output.status.success());
4747 assert!(!output.stderr.is_empty());
4848 }
49+
50+#[test]
51+fn lang_es_greets_in_spanish() {
52+ let output = run(&["--lang", "es", "Ana"]);
53+ assert!(output.status.success());
54+ assert_eq!(String::from_utf8(output.stdout).unwrap(), "¡Hola, Ana!\n");
55+}
56+
57+#[test]
58+fn lang_fr_greets_in_french() {
59+ let output = run(&["--lang", "fr", "Luc"]);
60+ assert!(output.status.success());
61+ assert_eq!(String::from_utf8(output.stdout).unwrap(), "Bonjour, Luc !\n");
62+}
63+
64+#[test]
65+fn lang_en_is_the_default() {
66+ let output = run(&["--lang", "en", "Ada"]);
67+ assert!(output.status.success());
68+ assert_eq!(String::from_utf8(output.stdout).unwrap(), "Hi, Ada!\n");
69+ let output = run(&["Ada"]);
70+ assert_eq!(String::from_utf8(output.stdout).unwrap(), "Hi, Ada!\n");
71+}
72+
73+#[test]
74+fn lang_unknown_fails_naming_supported_languages() {
75+ let output = run(&["--lang", "de", "Ada"]);
76+ assert_eq!(output.status.code(), Some(2));
77+ let stderr = String::from_utf8(output.stderr).unwrap();
78+ assert!(stderr.contains("en, es, fr"), "stderr was {stderr:?}");
79+}
80+
81+#[test]
82+fn lang_missing_value_fails() {
83+ let output = run(&["--lang"]);
84+ assert_eq!(output.status.code(), Some(2));
85+ let stderr = String::from_utf8(output.stderr).unwrap();
86+ assert!(stderr.contains("--lang requires a value"), "stderr was {stderr:?}");
87+}