1use std::{path::PathBuf, time::Duration};
2
3use clap::{Args, ValueEnum};
4use color_eyre::Result;
5use envx_core::{ConflictStrategy, EnvVarManager, EnvWatcher, SyncMode, WatchConfig};
6
7#[derive(Debug, Clone, ValueEnum)]
8pub enum Direction {
9 FileToSystem,
11 SystemToFile,
13 Bidirectional,
15}
16
17#[derive(Args, Clone)]
18pub struct WatchArgs {
19 #[arg(value_name = "PATH")]
21 pub paths: Vec<PathBuf>,
22
23 #[arg(short, long, value_enum, default_value = "file-to-system")]
25 pub direction: Direction,
26
27 #[arg(short, long)]
29 pub output: Option<PathBuf>,
30
31 #[arg(short, long)]
33 pub pattern: Vec<String>,
34
35 #[arg(long, default_value = "300")]
37 pub debounce: u64,
38
39 #[arg(short, long)]
41 pub log: Option<PathBuf>,
42
43 #[arg(short, long)]
45 pub vars: Vec<String>,
46
47 #[arg(short, long)]
49 pub quiet: bool,
50}
51
52pub fn handle_watch(args: &WatchArgs) -> Result<()> {
67 if matches!(args.direction, Direction::SystemToFile | Direction::Bidirectional) && args.output.is_none() {
69 return Err(color_eyre::eyre::eyre!(
70 "Output file required for system-to-file synchronization. Use --output <file>"
71 ));
72 }
73
74 let sync_mode = match args.direction {
75 Direction::FileToSystem => SyncMode::FileToSystem,
76 Direction::SystemToFile => SyncMode::SystemToFile,
77 Direction::Bidirectional => SyncMode::Bidirectional,
78 };
79
80 let mut config = WatchConfig {
81 paths: if args.paths.is_empty() {
82 vec![PathBuf::from(".")]
83 } else {
84 args.paths.clone()
85 },
86 mode: sync_mode,
87 auto_reload: true,
88 debounce_duration: Duration::from_millis(args.debounce),
89 log_changes: !args.quiet,
90 conflict_strategy: ConflictStrategy::UseLatest,
91 ..Default::default()
92 };
93
94 if !args.pattern.is_empty() {
95 config.patterns.clone_from(&args.pattern);
96 }
97
98 if let Some(output) = &args.output {
100 if matches!(args.direction, Direction::Bidirectional) {
101 config.paths.push(output.clone());
102 }
103 }
104
105 let mut manager = EnvVarManager::new();
106 manager.load_all()?;
107
108 let mut watcher = EnvWatcher::new(config.clone(), manager);
109
110 if !args.vars.is_empty() {
112 watcher.set_variable_filter(args.vars.clone());
113 }
114
115 if let Some(output) = args.output.clone() {
116 watcher.set_output_file(output);
117 }
118
119 print_watch_header(args, &config);
120
121 watcher.start()?;
122
123 let running = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
125 let r = running.clone();
126
127 ctrlc::set_handler(move || {
128 r.store(false, std::sync::atomic::Ordering::SeqCst);
129 })?;
130
131 while running.load(std::sync::atomic::Ordering::SeqCst) {
133 std::thread::sleep(Duration::from_secs(1));
134
135 if let Some(log_file) = &args.log {
136 let _ = watcher.export_change_log(log_file);
137 }
138 }
139
140 watcher.stop()?;
141 println!("\nā
Watch mode stopped");
142
143 Ok(())
144}
145
146fn print_watch_header(args: &WatchArgs, config: &WatchConfig) {
147 println!("š Starting envx watch mode");
148 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāā");
149
150 match args.direction {
151 Direction::FileToSystem => {
152 println!("š ā š» Syncing from files to system");
153 println!(
154 "Watching: {}",
155 config
156 .paths
157 .iter()
158 .map(|p| p.display().to_string())
159 .collect::<Vec<_>>()
160 .join(", ")
161 );
162 }
163 Direction::SystemToFile => {
164 println!("š» ā š Syncing from system to file");
165 if let Some(output) = &args.output {
166 println!("Output: {}", output.display());
167 }
168 }
169 Direction::Bidirectional => {
170 println!("š āļø š» Bidirectional sync");
171 println!(
172 "Watching: {}",
173 config
174 .paths
175 .iter()
176 .map(|p| p.display().to_string())
177 .collect::<Vec<_>>()
178 .join(", ")
179 );
180 if let Some(output) = &args.output {
181 println!("Output: {}", output.display());
182 }
183 }
184 }
185
186 if !args.vars.is_empty() {
187 println!("Variables: {}", args.vars.join(", "));
188 }
189
190 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāā");
191 println!("Press Ctrl+C to stop\n");
192}