use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use crate::diff::DiffAlgorithmType;
#[derive(Parser)]
#[command(name = "watchdiff")]
#[command(author = "WatchDiff Team")]
#[command(version = "0.1.0")]
#[command(about = "A high-performance file watcher with beautiful TUI showing real-time diffs")]
#[command(long_about = "WatchDiff monitors file changes in real-time, respects .gitignore patterns, and displays beautiful diffs in a terminal user interface. Perfect for development workflow monitoring.")]
pub struct Cli {
#[arg(value_name = "PATH", help = "Path to watch (defaults to current directory)")]
pub path: Option<PathBuf>,
#[arg(short, long, default_value = "auto", help = "File watching mode")]
pub mode: WatchMode,
#[arg(long, default_value = "1000", help = "Maximum events to store")]
pub max_events: usize,
#[arg(short, long, help = "Enable verbose output")]
pub verbose: bool,
#[arg(long, help = "Disable colored output")]
pub no_color: bool,
#[arg(long, value_delimiter = ',', help = "File extensions to watch (e.g., rs,py,js)")]
pub extensions: Option<Vec<String>>,
#[arg(long, value_delimiter = ',', help = "Additional patterns to ignore")]
pub ignore: Option<Vec<String>>,
#[arg(long, default_value = "3", help = "Number of context lines in diffs")]
pub context: usize,
#[arg(long, default_value = "tui", help = "Output format")]
pub output: OutputFormat,
#[arg(long, default_value = "1000", help = "Polling interval in ms")]
pub poll_interval: u64,
#[arg(long, default_value = "myers", help = "Diff algorithm (myers, patience, lcs)")]
pub algorithm: DiffAlgorithmType,
#[arg(long, help = "Export patches to specified directory")]
pub export_dir: Option<PathBuf>,
}
#[derive(Debug, Clone, ValueEnum)]
pub enum WatchMode {
Auto,
Native,
Polling,
}
#[derive(Debug, Clone, ValueEnum)]
pub enum OutputFormat {
Tui,
Json,
Text,
Compact,
}
impl Cli {
pub fn get_watch_path(&self) -> PathBuf {
self.path.clone().unwrap_or_else(|| {
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
})
}
pub fn should_watch_extension(&self, path: &std::path::Path) -> bool {
if let Some(ref extensions) = self.extensions {
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
extensions.iter().any(|e| e.eq_ignore_ascii_case(ext))
} else {
false
}
} else {
true }
}
pub fn get_ignore_patterns(&self) -> Vec<String> {
self.ignore.clone().unwrap_or_default()
}
pub fn setup_logging(&self) {
let level = if self.verbose {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
tracing_subscriber::fmt()
.with_max_level(level)
.with_target(false)
.with_thread_ids(false)
.with_file(false)
.with_line_number(false)
.init();
}
pub fn validate(&self) -> Result<(), String> {
let path = self.get_watch_path();
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()));
}
if !path.is_dir() {
return Err(format!("Path is not a directory: {}", path.display()));
}
if self.max_events == 0 {
return Err("Max events must be greater than 0".to_string());
}
if self.poll_interval == 0 {
return Err("Poll interval must be greater than 0".to_string());
}
Ok(())
}
}
impl Default for Cli {
fn default() -> Self {
Self {
path: None,
mode: WatchMode::Auto,
max_events: 1000,
verbose: false,
no_color: false,
extensions: None,
ignore: None,
context: 3,
output: OutputFormat::Tui,
poll_interval: 1000,
algorithm: DiffAlgorithmType::Myers,
export_dir: None,
}
}
}