1use std::path::PathBuf;
2use clap::{Parser, ValueEnum};
3use crate::diff::DiffAlgorithmType;
4
5#[derive(Parser)]
6#[command(name = "watchdiff")]
7#[command(author = "WatchDiff Team")]
8#[command(version = "0.1.0")]
9#[command(about = "A high-performance file watcher with beautiful TUI showing real-time diffs")]
10#[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.")]
11pub struct Cli {
12 #[arg(value_name = "PATH", help = "Path to watch (defaults to current directory)")]
14 pub path: Option<PathBuf>,
15
16 #[arg(short, long, default_value = "auto", help = "File watching mode")]
18 pub mode: WatchMode,
19
20 #[arg(long, default_value = "1000", help = "Maximum events to store")]
22 pub max_events: usize,
23
24 #[arg(short, long, help = "Enable verbose output")]
26 pub verbose: bool,
27
28 #[arg(long, help = "Disable colored output")]
30 pub no_color: bool,
31
32 #[arg(long, value_delimiter = ',', help = "File extensions to watch (e.g., rs,py,js)")]
34 pub extensions: Option<Vec<String>>,
35
36 #[arg(long, value_delimiter = ',', help = "Additional patterns to ignore")]
38 pub ignore: Option<Vec<String>>,
39
40 #[arg(long, default_value = "3", help = "Number of context lines in diffs")]
42 pub context: usize,
43
44 #[arg(long, default_value = "tui", help = "Output format")]
46 pub output: OutputFormat,
47
48 #[arg(long, default_value = "1000", help = "Polling interval in ms")]
50 pub poll_interval: u64,
51
52 #[arg(long, default_value = "myers", help = "Diff algorithm (myers, patience, lcs)")]
54 pub algorithm: DiffAlgorithmType,
55
56 #[arg(long, help = "Export patches to specified directory")]
58 pub export_dir: Option<PathBuf>,
59}
60
61#[derive(Debug, Clone, ValueEnum)]
62pub enum WatchMode {
63 Auto,
65 Native,
67 Polling,
69}
70
71#[derive(Debug, Clone, ValueEnum)]
72pub enum OutputFormat {
73 Tui,
75 Json,
77 Text,
79 Compact,
81}
82
83impl Cli {
84 pub fn get_watch_path(&self) -> PathBuf {
85 self.path.clone().unwrap_or_else(|| {
86 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
87 })
88 }
89
90 pub fn should_watch_extension(&self, path: &std::path::Path) -> bool {
91 if let Some(ref extensions) = self.extensions {
92 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
93 extensions.iter().any(|e| e.eq_ignore_ascii_case(ext))
94 } else {
95 false
96 }
97 } else {
98 true }
100 }
101
102 pub fn get_ignore_patterns(&self) -> Vec<String> {
103 self.ignore.clone().unwrap_or_default()
104 }
105
106 pub fn setup_logging(&self) {
107 let level = if self.verbose {
108 tracing::Level::DEBUG
109 } else {
110 tracing::Level::INFO
111 };
112
113 tracing_subscriber::fmt()
114 .with_max_level(level)
115 .with_target(false)
116 .with_thread_ids(false)
117 .with_file(false)
118 .with_line_number(false)
119 .init();
120 }
121
122 pub fn validate(&self) -> Result<(), String> {
123 let path = self.get_watch_path();
124
125 if !path.exists() {
126 return Err(format!("Path does not exist: {}", path.display()));
127 }
128
129 if !path.is_dir() {
130 return Err(format!("Path is not a directory: {}", path.display()));
131 }
132
133 if self.max_events == 0 {
134 return Err("Max events must be greater than 0".to_string());
135 }
136
137 if self.poll_interval == 0 {
138 return Err("Poll interval must be greater than 0".to_string());
139 }
140
141 Ok(())
142 }
143}
144
145impl Default for Cli {
146 fn default() -> Self {
147 Self {
148 path: None,
149 mode: WatchMode::Auto,
150 max_events: 1000,
151 verbose: false,
152 no_color: false,
153 extensions: None,
154 ignore: None,
155 context: 3,
156 output: OutputFormat::Tui,
157 poll_interval: 1000,
158 algorithm: DiffAlgorithmType::Myers,
159 export_dir: None,
160 }
161 }
162}