| use std::io::{IsTerminal, Read}; |
| use std::panic; |
| use std::path::PathBuf; |
|
|
| use anyhow::{Context, Result}; |
| use clap::Parser; |
| use forge_api::ForgeAPI; |
| use forge_config::ForgeConfig; |
| use forge_domain::TitleFormat; |
| use forge_main::{Cli, Sandbox, TitleDisplayExt, TopLevelCommand, UI, tracker}; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[cfg(windows)] |
| fn enable_stdout_vt_processing() { |
| use windows_sys::Win32::System::Console::{ |
| ENABLE_VIRTUAL_TERMINAL_PROCESSING, GetConsoleMode, GetStdHandle, STD_OUTPUT_HANDLE, |
| SetConsoleMode, |
| }; |
| unsafe { |
| let handle = GetStdHandle(STD_OUTPUT_HANDLE); |
| let mut mode = 0; |
| if GetConsoleMode(handle, &mut mode) != 0 { |
| let _ = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); |
| } |
| } |
| } |
|
|
| #[tokio::main] |
| async fn main() { |
| if let Err(err) = run().await { |
| eprintln!("{}", TitleFormat::error(format!("{err}")).display()); |
| if let Some(cause) = err.chain().nth(1) { |
| eprintln!("{cause}"); |
| } |
| std::process::exit(1); |
| } |
| } |
|
|
| async fn run() -> Result<()> { |
| |
| |
| |
| |
| |
| #[cfg(windows)] |
| { |
| let _ = enable_ansi_support::enable_ansi_support(); |
| enable_stdout_vt_processing(); |
| } |
|
|
| |
| |
| |
| let _ = rustls::crypto::ring::default_provider().install_default(); |
|
|
| |
| panic::set_hook(Box::new(|panic_info| { |
| let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { |
| s.to_string() |
| } else if let Some(s) = panic_info.payload().downcast_ref::<String>() { |
| s.clone() |
| } else { |
| "Unexpected error occurred".to_string() |
| }; |
|
|
| println!("{}", TitleFormat::error(message.to_string()).display()); |
| tracker::error_blocking(message); |
| std::process::exit(1); |
| })); |
|
|
| |
| let mut cli = Cli::parse(); |
|
|
| |
| |
| let is_select = matches!(cli.subcommands, Some(TopLevelCommand::Select(_))); |
| if !is_select && !std::io::stdin().is_terminal() { |
| let mut stdin_content = String::new(); |
| std::io::stdin().read_to_string(&mut stdin_content)?; |
| let trimmed_content = stdin_content.trim(); |
| if !trimmed_content.is_empty() { |
| cli.piped_input = Some(trimmed_content.to_string()); |
| } |
| } |
|
|
| |
| |
| let config = |
| ForgeConfig::read().context("Failed to read Forge configuration from .forge.toml")?; |
|
|
| |
| let cwd: PathBuf = match (&cli.sandbox, &cli.directory) { |
| (Some(sandbox), Some(cli)) => { |
| let mut sandbox = Sandbox::new(sandbox).create()?; |
| sandbox.push(cli); |
| sandbox |
| } |
| (Some(sandbox), _) => Sandbox::new(sandbox).create()?, |
| (_, Some(cli)) => match cli.canonicalize() { |
| Ok(cwd) => cwd, |
| Err(_) => panic!("Invalid path: {}", cli.display()), |
| }, |
| (_, _) => std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), |
| }; |
|
|
| let mut ui = UI::init(cli, config, move |config| { |
| ForgeAPI::init(cwd.clone(), config) |
| })?; |
| ui.run().await; |
|
|
| Ok(()) |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use forge_main::TopLevelCommand; |
| use pretty_assertions::assert_eq; |
|
|
| use super::*; |
|
|
| #[test] |
| fn test_stdin_detection_logic() { |
| |
| |
| |
|
|
| |
| let cli_with_prompt = Cli::parse_from(["forge", "--prompt", "existing prompt"]); |
| let original_prompt = cli_with_prompt.prompt.clone(); |
|
|
| |
| assert_eq!(original_prompt, Some("existing prompt".to_string())); |
|
|
| |
| let cli_no_prompt = Cli::parse_from(["forge"]); |
| assert_eq!(cli_no_prompt.prompt, None); |
| assert_eq!(cli_no_prompt.piped_input, None); |
| } |
|
|
| #[test] |
| fn test_cli_parsing_with_short_flag() { |
| |
| let cli_with_short_prompt = Cli::parse_from(["forge", "-p", "short flag prompt"]); |
| assert_eq!( |
| cli_with_short_prompt.prompt, |
| Some("short flag prompt".to_string()) |
| ); |
| } |
|
|
| #[test] |
| fn test_cli_parsing_other_flags_work_with_piping() { |
| |
| let cli_with_flags = Cli::parse_from(["forge", "--verbose"]); |
| assert_eq!(cli_with_flags.prompt, None); |
| assert_eq!(cli_with_flags.verbose, true); |
| } |
|
|
| #[test] |
| fn test_commit_command_diff_field_initially_none() { |
| |
| let cli = Cli::parse_from(["forge", "commit", "--preview"]); |
| if let Some(TopLevelCommand::Commit(commit_group)) = cli.subcommands { |
| assert_eq!(commit_group.preview, true); |
| assert_eq!(commit_group.diff, None); |
| } else { |
| panic!("Expected Commit command"); |
| } |
| } |
| } |
|
|