Add a --version flag that prints the crate version and exits #26
Approved by g1t
Note
Running on Claude Sonnet 5.5.
Prompt
You are a coding agent working in the git repository checked out in the current directory.
Issue #21: Add a --version flag that prints the crate version and exits
Add
pub version: booltoOptionsinsrc/lib.rs, set by--versioninparse_args. Insrc/main.rs, when set, printenv!("CARGO_PKG_VERSION")(e.g.0.1.0) and exit with status 0 without printing a greeting. Add a unit test for parsing the flag, and an integration test intests/cli.rsthat runs the binary (useenv!("CARGO_BIN_EXE_hello")withstd::process::Command) and asserts stdout equals the crate version.These commands must pass when you are done. Run them if the tools are installed:
- cargo test
- cargo run -- --version
Make the change and keep it focused on the issue. Commit your work with a clear message. Do not push; that is done for you. Finish with a short summary of what you changed and why. It becomes the description of your pull request, so write it for a reviewer: plain sentences, no headings, no emoji, no checklists, and nothing about whether anything was committed or pushed. Say what you did not verify.
Bashcat src/lib.rs src/main.rs; ls tests; cat Cargo.toml
cat src/lib.rs src/main.rs; ls tests; cat Cargo.tomlresult→ //! Greetings.
//! Greetings. /// Parsed command-line options. #[derive(Debug, Default, PartialEq, Eq)] pub struct Options { /// The name to greet, if one was given. pub name: Option<String>, /// Whether to upper-case the greeting (`--shout`). pub shout: bool, } /// Parses command-line arguments (excluding the program name). /// /// `--shout` sets [`Options::shout`]; other arguments starting with `--` are /// treated as flags and skipped. The name is the first remaining argument. pub fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Options { let mut options = Options::default(); for arg in args { if arg == "--shout" { options.shout = true; } else if !arg.starts_with("--") && options.name.is_none() { options.name = Some(arg); } } options } /// `text` upper-cased. pub fn shout(text: &str) -> String { text.to_uppercase() } /// The greeting for `name`. pub fn greeting(name: &str) -> String { let trimmed = name.trim(); let display_name = if trimmed.is_empty() { "world" } else { trimmed }; format!("Hello, {display_name}!") } /// The farewell for `name`. pub fn farewell(name: &str) -> String { let trimmed = name.trim(); let display_name = if trimmed.is_empty() { "world" } else { trimmed }; format!("Goodbye, {display_name}!") } #[cfg(test)] mod tests { use super::*; fn args(list: &[&str]) -> Vec<String> { list.iter().map(|s| s.to_string()).collect() } #[test] fn parse_args_no_args() { assert_eq!(parse_args(args(&[])).name, None); } #[test] fn parse_args_name() { assert_eq!(parse_args(args(&["Ada"])).name.as_deref(), Some("Ada")); } #[test] fn parse_args_skips_leading_flag() { assert_eq!( parse_args(args(&["--unknown", "Ada"])).name.as_deref(), Some("Ada") ); } #[test] fn parse_args_shout_defaults_off() { assert!(!parse_args(args(&["Ada"])).shout); } #[test] fn parse_args_shout_before_name() { let options = parse_args(args(&["--shout", "Ada"])); assert!(options.shout); assert_eq!(options.name.as_deref(), Some("Ada")); } #[test] fn parse_args_shout_after_name() { let options = parse_args(args(&["Ada", "--shout"])); assert!(options.shout); assert_eq!(options.name.as_deref(), Some("Ada")); } #[test] fn parse_args_shout_alone() { let options = parse_args(args(&["--shout"])); assert!(options.shout); assert_eq!(options.name, None); } #[test] fn shout_upper_cases_greeting() { assert_eq!(shout(&greeting("Ada")), "HELLO, ADA!"); } #[test] fn greets_by_name() { assert_eq!(greeting("Ada"), "Hello, Ada!"); } #[test] fn greets_world_with_empty_string() { assert_eq!(greeting(""), "Hello, world!"); } #[test] fn greets_world_with_whitespace_only() { assert_eq!(greeting(" "), "Hello, world!"); } #[test] fn trims_whitespace_from_name() { assert_eq!(greeting(" Ada "), "Hello, Ada!"); } #[test] fn says_goodbye_by_name() { assert_eq!(farewell("Ada"), "Goodbye, Ada!"); } #[test] fn says_goodbye_to_world_when_blank() { assert_eq!(farewell(" "), "Goodbye, world!"); } #[test] fn farewell_trims_whitespace() { assert_eq!(farewell(" Ada "), "Goodbye, Ada!"); } } use std::env; use hello::{greeting, parse_args, shout}; fn main() { let options = parse_args(env::args().skip(1)); // The name given, or whoever is signed in. let name = options .name .or_else(|| env::var("USER").ok()) .unwrap_or_else(|| "world".to_owned()); let message = greeting(&name); println!("{}", if options.shout { shout(&message) } else { message }); } ls: cannot access 'tests': No such file or directory [package] name = "hello" version = "0.1.0" edition = "2021" description = "Greets people from the command line." license = "MIT"Bashpython3 - <<'E'
python3 - <<'E' p='src/lib.rs' s=open(p).read() s=s.replace(" pub shout: bool,\n}"," pub shout: bool,\n /// Whether to print the crate version and exit (`--version`).\n pub version: bool,\n}",1) s=s.replace("/// `--shout` sets [`Options::shout`]; other","/// `--shout` sets [`Options::shout`] and `--version` sets\n/// [`Options::version`]; other",1) s=s.replace(""" options.shout = true; } else if""",""" options.shout = true; } else if arg == "--version" { options.version = true; } else if""",1) s=s.replace(""" #[test] fn shout_upper_cases""",""" #[test] fn parse_args_version_defaults_off() { assert!(!parse_args(args(&["Ada"])).version); } #[test] fn parse_args_version() { let options = parse_args(args(&["--version"])); assert!(options.version); assert_eq!(options.name, None); } #[test] fn shout_upper_cases""",1) open(p,'w').write(s) p='src/main.rs' s=open(p).read() s=s.replace(""" // The name given""",""" if options.version { println!("{}", env!("CARGO_PKG_VERSION")); return; } // The name given""",1) open(p,'w').write(s) E mkdir tests cat > tests/cli.rs <<'E' use std::process::Command; #[test] fn version_flag_prints_crate_version() { let output = Command::new(env!("CARGO_BIN_EXE_hello")) .arg("--version") .output() .unwrap(); assert!(output.status.success()); assert_eq!( String::from_utf8(output.stdout).unwrap(), format!("{}\n", env!("CARGO_PKG_VERSION")) ); } E cargo test 2>&1 | tail -15; cargo run -q -- --versionresult→ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/cli.rs (target/debug/deps/cli-87b2103cd7e46374) running 1 test test version_flag_prints_crate_version ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests hello running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 0.1.0Bashgit add -A && git commit -qm "Add --version flag that prints the crate version and exits" && git log --oneline | head -1
git add -A && git commit -qm "Add --version flag that prints the crate version and exits" && git log --oneline | head -1result→ a6d7a0f Add --version flag that prints the crate version and exits
a6d7a0f Add --version flag that prints the crate version and exitsg1t
I added a
--versionflag that prints the crate version and exits with status 0, without printing a greeting.Optionsinsrc/lib.rsgainsversion: bool, whichparse_argssets when it sees--version. Insrc/main.rs,mainprintsenv!("CARGO_PKG_VERSION")and returns early when the flag is set. This check runs before the name lookup, so--versionwins even if a name is also given.I added two unit tests for parsing the flag, covering the default-off case and the flag alone. A new integration test in
tests/cli.rsruns the binary with--versionand checks that it exits successfully and that stdout is the crate version followed by a newline.cargo testpasses, andcargo run -- --versionprints0.1.0. I did not check how--versioncombines with--shoutbeyond the code path described above.Note
This run cost $0.0520 over 4 turns.
Note
Pushed a6d7a0ff0753.