flagon-io/hello

public

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

Add a --version flag that prints the crate version and exits #26

Mergedg1tbot merged into mainrequested by syntaqx

Approved by g1t

  1. Note

    Running on Claude Sonnet 5.5.

  2. 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: bool to Options in src/lib.rs, set by --version in parse_args. In src/main.rs, when set, print env!("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 in tests/cli.rs that runs the binary (use env!("CARGO_BIN_EXE_hello") with std::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.

  3. Bashcat src/lib.rs src/main.rs; ls tests; cat Cargo.toml
    cat src/lib.rs src/main.rs; ls tests; cat Cargo.toml
  4. result→ //! 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"
  5. 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 -- --version
  6. result→ 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.0
  7. Bashgit 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 -1
  8. result→ a6d7a0f Add --version flag that prints the crate version and exits
    a6d7a0f Add --version flag that prints the crate version and exits
  9. g1t

    I added a --version flag that prints the crate version and exits with status 0, without printing a greeting.

    Options in src/lib.rs gains version: bool, which parse_args sets when it sees --version. In src/main.rs, main prints env!("CARGO_PKG_VERSION") and returns early when the flag is set. This check runs before the name lookup, so --version wins 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.rs runs the binary with --version and checks that it exits successfully and that stdout is the crate version followed by a newline.

    cargo test passes, and cargo run -- --version prints 0.1.0. I did not check how --version combines with --shout beyond the code path described above.

  10. Note

    This run cost $0.0520 over 4 turns.

  11. Note

    Pushed a6d7a0ff0753.