diff --git a/src/interactive/mod.rs b/src/interactive/mod.rs new file mode 100644 index 0000000..3a5e27d --- /dev/null +++ b/src/interactive/mod.rs @@ -0,0 +1,120 @@ +use tokio::io::{self, AsyncWriteExt, BufReader}; +use anyhow::Result; +use crate::generator::MessageGenerator; + +pub async fn run_interactive_mode(mut generator: MessageGenerator) -> Result { + let types = vec!["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"]; + + generator.set_type(prompt_type(&types).await?); + generator.set_scope_opt(prompt_scope().await?); + generator.set_description(prompt_description().await?); + generator.set_body_opt(prompt_body().await?); + let is_breaking = prompt_breaking().await?; + generator.set_breaking(is_breaking); + if is_breaking { + generator.set_breaking_description(prompt_breaking_description().await?); + } + + println!("\nGenerated commit message:"); + println!("{}", generator.format_message()); + + Ok(generator) +} + +async fn prompt_type(types: &[&str]) -> Result { + let mut stdout = io::stdout(); + + println!("Select commit type:"); + for (i, t) in types.iter().enumerate() { + println!(" {} - {}", i, t); + } + print!("Enter number (0-{}): ", types.len() - 1); + stdout.flush().await?; + + let mut input = String::new(); + let mut stdin = BufReader::new(io::stdin()); + stdin.read_line(&mut input).await?; + + let idx: Result = input.trim().parse(); + match idx { + Ok(i) if i < types.len() => Ok(types[i].to_string()), + Ok(_) => { + eprintln!("Invalid selection, using default 'chore'"); + Ok("chore".to_string()) + } + Err(_) => { + eprintln!("Invalid input, using default 'chore'"); + Ok("chore".to_string()) + } + } +} + +async fn prompt_scope() -> Result> { + let mut stdout = io::stdout(); + let mut stdin = BufReader::new(io::stdin()); + + print!("Scope (optional): "); + stdout.flush().await?; + + let mut input = String::new(); + stdin.read_line(&mut input).await?; + let trimmed = input.trim().to_string(); + if trimmed.is_empty() { + Ok(None) + } else { + Ok(Some(trimmed)) + } +} + +async fn prompt_description() -> Result { + let mut stdout = io::stdout(); + let mut stdin = BufReader::new(io::stdin()); + + print!("Commit description: "); + stdout.flush().await?; + + let mut input = String::new(); + stdin.read_line(&mut input).await?; + Ok(input.trim().to_string()) +} + +async fn prompt_body() -> Result> { + let mut stdout = io::stdout(); + let mut stdin = BufReader::new(io::stdin()); + + print!("Extended description (optional, leave empty to skip): "); + stdout.flush().await?; + + let mut input = String::new(); + stdin.read_line(&mut input).await?; + let trimmed = input.trim().to_string(); + if trimmed.is_empty() { + Ok(None) + } else { + Ok(Some(trimmed)) + } +} + +async fn prompt_breaking() -> Result { + let mut stdout = io::stdout(); + let mut stdin = BufReader::new(io::stdin()); + + print!("Is this a breaking change? [y/N]: "); + stdout.flush().await?; + + let mut input = String::new(); + stdin.read_line(&mut input).await?; + Ok(input.trim().eq_ignore_ascii_case("y")) +} + +async fn prompt_breaking_description() -> Result { + let mut stdout = io::stdout(); + let mut stdin = BufReader::new(io::stdin()); + + print!("Describe the breaking change: "); + stdout.flush().await?; + + let mut input = String::new(); + stdin.read_line(&mut input).await?; + Ok(input.trim().to_string()) +}