use crate::core::{
find_ports_for_pid, resolve_in_dir, sort_processes, PortInfo, Process, ProcessStatus, SortKey,
};
use crate::error::{ProcError, Result};
use crate::ui::{format_memory, truncate_string, Printer};
use clap::Args;
use colored::*;
use serde::Serialize;
use std::collections::HashSet;
use std::path::PathBuf;
#[derive(Args, Debug)]
pub struct ForCommand {
pub file: String,
#[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
pub in_dir: Option<String>,
#[arg(long = "by", short = 'b')]
pub by_name: Option<String>,
#[arg(long)]
pub min_cpu: Option<f32>,
#[arg(long)]
pub min_mem: Option<f64>,
#[arg(long)]
pub status: Option<String>,
#[arg(long)]
pub min_uptime: Option<u64>,
#[arg(long, short = 'j')]
pub json: bool,
#[arg(long, short = 'v')]
pub verbose: bool,
#[arg(long, short = 's', value_enum, default_value_t = SortKey::Cpu)]
pub sort: SortKey,
#[arg(long, short = 'n')]
pub limit: Option<usize>,
}
impl ForCommand {
pub fn execute(&self) -> Result<()> {
let file_path = self.resolve_path(&self.file)?;
let exe_processes = Process::find_by_exe_path(&file_path)?;
let open_file_procs = Process::find_by_open_file(&file_path)?;
let mut seen_pids = HashSet::new();
let mut processes: Vec<Process> = Vec::new();
for proc in exe_processes {
if seen_pids.insert(proc.pid) {
processes.push(proc);
}
}
for proc in open_file_procs {
if seen_pids.insert(proc.pid) {
processes.push(proc);
}
}
self.apply_filters(&mut processes);
sort_processes(&mut processes, self.sort);
if let Some(limit) = self.limit {
processes.truncate(limit);
}
if processes.is_empty() {
return Err(ProcError::ProcessNotFound(format!(
"No processes found for file: {}",
self.file
)));
}
let mut results: Vec<(Process, Vec<PortInfo>)> = Vec::new();
for proc in processes {
let ports = find_ports_for_pid(proc.pid)?;
results.push((proc, ports));
}
if self.json {
self.print_json(&results)?;
} else {
self.print_human(&results);
}
Ok(())
}
fn resolve_path(&self, path: &str) -> Result<PathBuf> {
let expanded = if let Some(stripped) = path.strip_prefix("~/") {
if let Ok(home) = std::env::var("HOME") {
PathBuf::from(home).join(stripped)
} else {
PathBuf::from(path)
}
} else if path == "~" {
PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string()))
} else {
PathBuf::from(path)
};
let absolute = if expanded.is_relative() {
std::env::current_dir()?.join(expanded)
} else {
expanded
};
absolute
.canonicalize()
.map_err(|_| ProcError::InvalidInput(format!("File not found: {}", path)))
}
fn apply_filters(&self, processes: &mut Vec<Process>) {
let in_dir_filter = resolve_in_dir(&self.in_dir);
processes.retain(|p| {
if let Some(ref dir_path) = in_dir_filter {
if let Some(ref proc_cwd) = p.cwd {
let proc_path = PathBuf::from(proc_cwd);
if !proc_path.starts_with(dir_path) {
return false;
}
} else {
return false;
}
}
if let Some(ref name) = self.by_name {
if !crate::core::matches_by_filter(p, name) {
return false;
}
}
if let Some(min_cpu) = self.min_cpu {
if p.cpu_percent < min_cpu {
return false;
}
}
if let Some(min_mem) = self.min_mem {
if p.memory_mb < min_mem {
return false;
}
}
if let Some(ref status) = self.status {
let status_match = match status.to_lowercase().as_str() {
"running" => matches!(p.status, ProcessStatus::Running),
"sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
"stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
"zombie" => matches!(p.status, ProcessStatus::Zombie),
_ => true,
};
if !status_match {
return false;
}
}
if let Some(min_uptime) = self.min_uptime {
if let Some(start_time) = p.start_time {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
if now.saturating_sub(start_time) < min_uptime {
return false;
}
} else {
return false;
}
}
true
});
}
fn print_human(&self, results: &[(Process, Vec<PortInfo>)]) {
let count = results.len();
let file_display = &self.file;
if count == 1 {
let (proc, ports) = &results[0];
println!(
"{} Found 1 process for {}",
"✓".green().bold(),
file_display.cyan().bold()
);
println!();
println!(" {}", "Process:".bright_black());
println!(
" {} {} (PID {})",
"Name:".bright_black(),
proc.name.white().bold(),
proc.pid.to_string().cyan()
);
println!(" {} {:.1}%", "CPU:".bright_black(), proc.cpu_percent);
println!(
" {} {}",
"MEM:".bright_black(),
format_memory(proc.memory_mb)
);
if let Some(ref path) = proc.exe_path {
println!(" {} {}", "Path:".bright_black(), path.bright_black());
}
if self.verbose {
if let Some(ref cwd) = proc.cwd {
println!(" {} {}", "CWD:".bright_black(), cwd.bright_black());
}
if let Some(ref cmd) = proc.command {
println!(" {} {}", "Command:".bright_black(), cmd.bright_black());
}
}
println!();
if ports.is_empty() {
println!(" {} No listening ports", "ℹ".blue());
} else {
println!(" {}", "Listening Ports:".bright_black());
for port_info in ports {
let addr = port_info.address.as_deref().unwrap_or("*");
println!(
" {} :{} ({} on {})",
"→".bright_black(),
port_info.port.to_string().cyan(),
format!("{:?}", port_info.protocol).to_uppercase(),
addr
);
}
}
} else {
println!(
"{} Found {} processes for {}",
"✓".green().bold(),
count.to_string().white().bold(),
file_display.cyan().bold()
);
println!();
println!(
" {:>7} {:<15} {:>5} {:>8} {}",
"PID".bright_black(),
"NAME".bright_black(),
"CPU%".bright_black(),
"MEM".bright_black(),
"PORTS".bright_black()
);
println!(" {}", "─".repeat(60).bright_black());
for (proc, ports) in results {
let ports_str = if ports.is_empty() {
"-".to_string()
} else {
ports
.iter()
.map(|p| format!(":{}", p.port))
.collect::<Vec<_>>()
.join(", ")
};
println!(
" {:>7} {:<15} {:>5.1} {:>8} {}",
proc.pid.to_string().cyan(),
truncate_string(&proc.name, 15).white(),
proc.cpu_percent,
format_memory(proc.memory_mb),
ports_str
);
}
}
println!();
}
fn print_json(&self, results: &[(Process, Vec<PortInfo>)]) -> Result<()> {
let printer = Printer::from_flags(true, self.verbose);
let items: Vec<ProcessForJson> = results
.iter()
.map(|(proc, ports)| ProcessForJson {
process: proc,
ports,
})
.collect();
printer.print_json(&ForOutput {
action: "for",
success: true,
count: items.len(),
results: &items,
});
Ok(())
}
}
#[derive(Serialize)]
struct ProcessForJson<'a> {
process: &'a Process,
ports: &'a [PortInfo],
}
#[derive(Serialize)]
struct ForOutput<'a> {
action: &'static str,
success: bool,
count: usize,
results: &'a [ProcessForJson<'a>],
}