Skip to main content

rusty_pee/
cli.rs

1//! Command-line interface — clap derive `Cli` struct.
2
3use clap::Parser;
4
5#[derive(Parser, Debug)]
6#[command(
7    name = "rusty-pee",
8    version,
9    about = "Fan stdin out to N concurrent shell-spawned children.",
10    long_about = "A Rust port of moreutils `pee`. Reads stdin once and writes \
11                  every byte to each child's stdin in argv order; aggregates \
12                  exit codes; surfaces failures cleanly."
13)]
14pub struct Cli {
15    /// Buffer each child's stdout and emit in argv order after all children
16    /// exit (Default mode only). Without this flag, children inherit the
17    /// parent's stdout and their outputs interleave nondeterministically.
18    #[arg(long)]
19    pub capture: bool,
20
21    /// Enable strict moreutils-compat mode.
22    #[arg(long, conflicts_with = "no_strict")]
23    pub strict: bool,
24
25    /// Explicitly disable strict mode (overrides env + argv[0]).
26    #[arg(long = "no-strict")]
27    pub no_strict: bool,
28
29    /// Positional command strings — each spawned via the platform shell.
30    #[arg(trailing_var_arg = true)]
31    pub commands: Vec<String>,
32
33    /// Subcommand (currently only `completions`).
34    #[command(subcommand)]
35    pub command: Option<Subcommand>,
36}
37
38#[derive(clap::Subcommand, Debug)]
39pub enum Subcommand {
40    /// Emit shell completion scripts (Default mode only).
41    Completions { shell: clap_complete::Shell },
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use clap::CommandFactory;
48
49    #[test]
50    fn cli_command_factory_compiles() {
51        let cmd = Cli::command();
52        assert_eq!(cmd.get_name(), "rusty-pee");
53    }
54
55    #[test]
56    fn parse_no_args() {
57        let cli = Cli::try_parse_from(["rusty-pee"]).unwrap();
58        assert!(cli.commands.is_empty());
59        assert!(!cli.capture);
60        assert!(!cli.strict);
61        assert!(!cli.no_strict);
62    }
63
64    #[test]
65    fn parse_capture_flag() {
66        let cli = Cli::try_parse_from(["rusty-pee", "--capture", "cat"]).unwrap();
67        assert!(cli.capture);
68        assert_eq!(cli.commands, vec!["cat"]);
69    }
70
71    #[test]
72    fn parse_strict_conflicts_with_no_strict() {
73        let result = Cli::try_parse_from(["rusty-pee", "--strict", "--no-strict"]);
74        assert!(result.is_err());
75    }
76
77    #[test]
78    fn parse_two_commands() {
79        let cli = Cli::try_parse_from(["rusty-pee", "wc -l", "grep foo"]).unwrap();
80        assert_eq!(cli.commands, vec!["wc -l", "grep foo"]);
81    }
82}