1use crate::core::{resolve_in_dir, Process, ProcessStatus};
10use crate::error::Result;
11use crate::ui::{OutputFormat, Printer};
12use clap::Args;
13use std::path::PathBuf;
14
15#[derive(Args, Debug)]
17pub struct ByCommand {
18 pub name: String,
20
21 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
23 pub in_dir: Option<String>,
24
25 #[arg(long)]
27 pub min_cpu: Option<f32>,
28
29 #[arg(long)]
31 pub min_mem: Option<f64>,
32
33 #[arg(long)]
35 pub status: Option<String>,
36
37 #[arg(long)]
39 pub min_uptime: Option<u64>,
40
41 #[arg(long)]
43 pub parent: Option<u32>,
44
45 #[arg(long, short = 'j')]
47 pub json: bool,
48
49 #[arg(long, short = 'v')]
51 pub verbose: bool,
52
53 #[arg(long, short = 'n')]
55 pub limit: Option<usize>,
56
57 #[arg(long, short = 's', default_value = "cpu")]
59 pub sort: String,
60}
61
62impl ByCommand {
63 pub fn execute(&self) -> Result<()> {
65 let format = if self.json {
66 OutputFormat::Json
67 } else {
68 OutputFormat::Human
69 };
70 let printer = Printer::new(format, self.verbose);
71
72 let mut processes = Process::find_by_name(&self.name)?;
74
75 let in_dir_filter = resolve_in_dir(&self.in_dir);
77
78 processes.retain(|p| {
80 if let Some(ref dir_path) = in_dir_filter {
82 if let Some(ref proc_cwd) = p.cwd {
83 let proc_path = PathBuf::from(proc_cwd);
84 if !proc_path.starts_with(dir_path) {
85 return false;
86 }
87 } else {
88 return false;
89 }
90 }
91
92 if let Some(min_cpu) = self.min_cpu {
94 if p.cpu_percent < min_cpu {
95 return false;
96 }
97 }
98
99 if let Some(min_mem) = self.min_mem {
101 if p.memory_mb < min_mem {
102 return false;
103 }
104 }
105
106 if let Some(ref status) = self.status {
108 let status_match = match status.to_lowercase().as_str() {
109 "running" => matches!(p.status, ProcessStatus::Running),
110 "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
111 "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
112 "zombie" => matches!(p.status, ProcessStatus::Zombie),
113 _ => true,
114 };
115 if !status_match {
116 return false;
117 }
118 }
119
120 if let Some(min_uptime) = self.min_uptime {
122 if let Some(start_time) = p.start_time {
123 let now = std::time::SystemTime::now()
124 .duration_since(std::time::UNIX_EPOCH)
125 .map(|d| d.as_secs())
126 .unwrap_or(0);
127 if now.saturating_sub(start_time) < min_uptime {
128 return false;
129 }
130 } else {
131 return false;
132 }
133 }
134
135 if let Some(ppid) = self.parent {
137 if p.parent_pid != Some(ppid) {
138 return false;
139 }
140 }
141
142 true
143 });
144
145 match self.sort.to_lowercase().as_str() {
147 "cpu" => processes.sort_by(|a, b| {
148 b.cpu_percent
149 .partial_cmp(&a.cpu_percent)
150 .unwrap_or(std::cmp::Ordering::Equal)
151 }),
152 "mem" | "memory" => processes.sort_by(|a, b| {
153 b.memory_mb
154 .partial_cmp(&a.memory_mb)
155 .unwrap_or(std::cmp::Ordering::Equal)
156 }),
157 "pid" => processes.sort_by_key(|p| p.pid),
158 "name" => processes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())),
159 _ => {} }
161
162 if let Some(limit) = self.limit {
164 processes.truncate(limit);
165 }
166
167 let mut context_parts = vec![format!("by '{}'", self.name)];
169 if let Some(ref dir) = in_dir_filter {
170 context_parts.push(format!("in {}", dir.display()));
171 }
172 let context = Some(context_parts.join(" "));
173
174 printer.print_processes_with_context(&processes, context.as_deref());
175 Ok(())
176 }
177}