1use crate::core::{
10 find_ports_for_pid, resolve_in_dir, sort_processes, PortInfo, Process, SortKey, StatusFilter,
11};
12use crate::error::{ProcError, Result};
13use crate::ui::{format_duration, format_memory, Printer};
14use clap::Args;
15use colored::*;
16use serde::Serialize;
17use std::collections::HashSet;
18use std::path::PathBuf;
19
20#[derive(Args, Debug)]
22pub struct ForCommand {
23 pub file: String,
25
26 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
28 pub in_dir: Option<String>,
29
30 #[arg(long = "by", short = 'b')]
32 pub by_name: Option<String>,
33
34 #[arg(long)]
36 pub min_cpu: Option<f32>,
37
38 #[arg(long)]
40 pub min_mem: Option<f64>,
41
42 #[arg(long, value_enum, ignore_case = true)]
44 pub status: Option<StatusFilter>,
45
46 #[arg(long)]
48 pub min_uptime: Option<u64>,
49
50 #[arg(long, short = 'j')]
52 pub json: bool,
53
54 #[arg(long, short = 'v')]
56 pub verbose: bool,
57
58 #[arg(long, short = 's', value_enum, default_value_t = SortKey::Cpu)]
60 pub sort: SortKey,
61
62 #[arg(long, short = 'n')]
64 pub limit: Option<usize>,
65}
66
67impl ForCommand {
68 pub fn execute(&self) -> Result<()> {
70 let file_path = self.resolve_path(&self.file)?;
72
73 let exe_processes = Process::find_by_exe_path(&file_path)?;
75
76 let open_file_procs = Process::find_by_open_file(&file_path)?;
78
79 let mut seen_pids = HashSet::new();
81 let mut processes: Vec<Process> = Vec::new();
82
83 for proc in exe_processes {
84 if seen_pids.insert(proc.pid) {
85 processes.push(proc);
86 }
87 }
88
89 for proc in open_file_procs {
90 if seen_pids.insert(proc.pid) {
91 processes.push(proc);
92 }
93 }
94
95 self.apply_filters(&mut processes);
97
98 sort_processes(&mut processes, self.sort);
100
101 if let Some(limit) = self.limit {
103 processes.truncate(limit);
104 }
105
106 if processes.is_empty() {
108 return Err(ProcError::ProcessNotFound(format!(
109 "No processes found for file: {}",
110 self.file
111 )));
112 }
113
114 let mut results: Vec<(Process, Vec<PortInfo>)> = Vec::new();
116 for proc in processes {
117 let ports = find_ports_for_pid(proc.pid)?;
118 results.push((proc, ports));
119 }
120
121 if self.json {
123 self.print_json(&results)?;
124 } else {
125 self.print_human(&results);
126 }
127
128 Ok(())
129 }
130
131 fn resolve_path(&self, path: &str) -> Result<PathBuf> {
132 let expanded = if let Some(stripped) = path.strip_prefix("~/") {
134 if let Ok(home) = std::env::var("HOME") {
135 PathBuf::from(home).join(stripped)
136 } else {
137 PathBuf::from(path)
138 }
139 } else if path == "~" {
140 PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string()))
141 } else {
142 PathBuf::from(path)
143 };
144
145 let absolute = if expanded.is_relative() {
147 std::env::current_dir()?.join(expanded)
148 } else {
149 expanded
150 };
151
152 absolute
154 .canonicalize()
155 .map_err(|_| ProcError::InvalidInput(format!("File not found: {}", path)))
156 }
157
158 fn apply_filters(&self, processes: &mut Vec<Process>) {
159 let in_dir_filter = resolve_in_dir(&self.in_dir);
160
161 processes.retain(|p| {
162 if let Some(ref dir_path) = in_dir_filter {
164 if let Some(ref proc_cwd) = p.cwd {
165 let proc_path = PathBuf::from(proc_cwd);
166 if !proc_path.starts_with(dir_path) {
167 return false;
168 }
169 } else {
170 return false;
171 }
172 }
173
174 if let Some(ref name) = self.by_name {
176 if !crate::core::matches_by_filter(p, name) {
177 return false;
178 }
179 }
180
181 if let Some(min_cpu) = self.min_cpu {
183 if p.cpu_percent < min_cpu {
184 return false;
185 }
186 }
187
188 if let Some(min_mem) = self.min_mem {
190 if p.memory_mb < min_mem {
191 return false;
192 }
193 }
194
195 if let Some(status) = self.status {
197 if !status.matches(p.status) {
198 return false;
199 }
200 }
201
202 if let Some(min_uptime) = self.min_uptime {
204 if let Some(start_time) = p.start_time {
205 let now = std::time::SystemTime::now()
206 .duration_since(std::time::UNIX_EPOCH)
207 .map(|d| d.as_secs())
208 .unwrap_or(0);
209 if now.saturating_sub(start_time) < min_uptime {
210 return false;
211 }
212 } else {
213 return false;
214 }
215 }
216
217 true
218 });
219 }
220
221 fn print_human(&self, results: &[(Process, Vec<PortInfo>)]) {
222 let count = results.len();
223 let file_display = &self.file;
224
225 if count == 1 {
226 let (proc, ports) = &results[0];
228
229 println!(
230 "{} Found 1 process for {}",
231 "✓".green().bold(),
232 file_display.cyan().bold()
233 );
234 println!();
235
236 println!(
238 " {} {} (PID {})",
239 "Process:".bright_black(),
240 proc.name.white().bold(),
241 proc.pid.to_string().cyan()
242 );
243 if let Some(ref cwd) = proc.cwd {
244 println!(" {} {}", "Directory:".bright_black(), cwd);
245 }
246 if let Some(ref path) = proc.exe_path {
247 println!(" {} {}", "Path:".bright_black(), path.bright_black());
248 }
249 for port_info in ports {
250 let addr = port_info.address.as_deref().unwrap_or("*");
251 println!(
252 " {} :{} ({} on {})",
253 "Listening:".bright_black(),
254 port_info.port.to_string().cyan(),
255 format!("{:?}", port_info.protocol).to_uppercase(),
256 addr
257 );
258 }
259 println!(
260 " {} {:.1}% CPU, {}",
261 "Resources:".bright_black(),
262 proc.cpu_percent,
263 format_memory(proc.memory_mb)
264 );
265 if let Some(uptime) = proc.uptime_secs {
266 println!(" {} {}", "Uptime:".bright_black(), format_duration(uptime));
267 }
268 if self.verbose {
269 if let Some(ref cmd) = proc.command {
270 println!(" {} {}", "Command:".bright_black(), cmd.bright_black());
271 }
272 }
273 println!();
274 } else {
275 let processes: Vec<Process> = results.iter().map(|(p, _)| p.clone()).collect();
277 Printer::from_flags(false, self.verbose).print_processes_as(
278 "for",
279 &processes,
280 Some(&format!("for {}", file_display)),
281 );
282
283 let listening: Vec<_> = results
284 .iter()
285 .flat_map(|(proc, ports)| ports.iter().map(move |p| (proc, p)))
286 .collect();
287 if !listening.is_empty() {
288 println!(" {}", "Listening:".bright_black());
289 for (proc, port_info) in listening {
290 let addr = port_info.address.as_deref().unwrap_or("*");
291 println!(
292 " {} :{} ({} on {}) {} [PID {}]",
293 "→".bright_black(),
294 port_info.port.to_string().cyan(),
295 format!("{:?}", port_info.protocol).to_uppercase(),
296 addr,
297 proc.name.white(),
298 proc.pid.to_string().cyan()
299 );
300 }
301 println!();
302 }
303 }
304 }
305
306 fn print_json(&self, results: &[(Process, Vec<PortInfo>)]) -> Result<()> {
307 let printer = Printer::from_flags(true, self.verbose);
308 let items: Vec<ProcessForJson> = results
309 .iter()
310 .map(|(proc, ports)| ProcessForJson {
311 process: proc,
312 ports,
313 })
314 .collect();
315
316 printer.print_json(&ForOutput {
317 action: "for",
318 success: true,
319 count: items.len(),
320 results: &items,
321 });
322 Ok(())
323 }
324}
325
326#[derive(Serialize)]
327struct ProcessForJson<'a> {
328 process: &'a Process,
329 ports: &'a [PortInfo],
330}
331
332#[derive(Serialize)]
333struct ForOutput<'a> {
334 action: &'static str,
335 success: bool,
336 count: usize,
337 results: &'a [ProcessForJson<'a>],
338}