1use clap::{Parser, Subcommand};
4use std::io::{self, BufRead, Write};
5use thiserror::Error;
6
7#[derive(Debug, Parser)]
9#[command(name = "rust-bucket")]
10#[command(about = "Rust-first project bootstrapper for AI-first engineering")]
11#[command(version)]
12pub struct Cli {
13 #[command(subcommand)]
14 pub command: Commands,
15}
16
17#[derive(Debug, Subcommand)]
19pub enum Commands {
20 Apply {
22 #[arg(long)]
24 force: bool,
25 },
26}
27
28#[derive(Debug, Error)]
30pub enum CliError {
31 #[error("IO error: {0}")]
33 Io(#[from] io::Error),
34
35 #[error("Invalid input: {0}")]
37 InvalidInput(String),
38}
39
40pub fn prompt_test_timeout() -> Result<u32, CliError> {
50 let stdout = io::stdout();
51 let mut handle = stdout.lock();
52
53 write!(handle, "Enter test timeout in seconds (default: 120): ")?;
54 handle.flush()?;
55
56 let stdin = io::stdin();
57 let mut line = String::new();
58 stdin.lock().read_line(&mut line)?;
59
60 let trimmed = line.trim();
61
62 if trimmed.is_empty() {
64 return Ok(120);
65 }
66
67 let timeout = trimmed.parse::<u32>().map_err(|_| {
69 CliError::InvalidInput(format!("'{}' is not a valid positive integer", trimmed))
70 })?;
71
72 if timeout == 0 {
74 return Err(CliError::InvalidInput(
75 "Timeout must be a positive integer (greater than 0)".to_string(),
76 ));
77 }
78
79 Ok(timeout)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_cli_parsing() {
88 let cli = Cli::parse_from(["rust-bucket", "apply"]);
90 match cli.command {
91 Commands::Apply { force } => assert!(!force),
92 }
93
94 let cli = Cli::parse_from(["rust-bucket", "apply", "--force"]);
96 match cli.command {
97 Commands::Apply { force } => assert!(force),
98 }
99 }
100
101 #[test]
102 fn test_version_flag() {
103 let result = Cli::try_parse_from(["rust-bucket", "--version"]);
105 assert!(result.is_err());
108 let err = result.unwrap_err();
109 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
110 }
111}