1use crate::core::{resolve_in_dir, sort_processes, NameMatch, Process, SortKey, StatusFilter};
11use crate::error::Result;
12use crate::ui::Printer;
13use clap::Args;
14use std::path::PathBuf;
15
16#[derive(Args, Debug)]
18pub struct ByCommand {
19 pub name: String,
21
22 #[arg(long)]
24 pub cmd: bool,
25
26 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
29 pub in_dir: Option<String>,
30
31 #[arg(long)]
33 pub min_cpu: Option<f32>,
34
35 #[arg(long)]
37 pub min_mem: Option<f64>,
38
39 #[arg(long, value_enum, ignore_case = true)]
41 pub status: Option<StatusFilter>,
42
43 #[arg(long)]
45 pub min_uptime: Option<u64>,
46
47 #[arg(long)]
49 pub parent: Option<u32>,
50
51 #[arg(long, short = 'j')]
53 pub json: bool,
54
55 #[arg(long, short = 'v')]
57 pub verbose: bool,
58
59 #[arg(long, short = 'w')]
61 pub wide: bool,
62
63 #[arg(long, short = 'n')]
65 pub limit: Option<usize>,
66
67 #[arg(long, short = 's', value_enum, default_value_t = SortKey::Cpu)]
69 pub sort: SortKey,
70}
71
72impl ByCommand {
73 pub fn execute(&self) -> Result<()> {
75 let printer = Printer::from_flags(self.json, self.verbose).wide(self.wide);
76
77 let mut processes = Process::find_by_name(&self.name, NameMatch::from_flag(self.cmd))?;
79
80 Process::measure_cpu(&mut processes);
82
83 let in_dir_filter = resolve_in_dir(&self.in_dir)?;
85 let in_dir_has_any = in_dir_filter.as_ref().is_some_and(|dir_path| {
86 processes
87 .iter()
88 .any(|p| crate::core::filters::matches_in_filter(p, dir_path))
89 });
90 crate::core::require_in_dir(&self.in_dir, &in_dir_filter, in_dir_has_any)?;
91
92 processes.retain(|p| self.keeps(p, &in_dir_filter));
94
95 sort_processes(&mut processes, self.sort);
97
98 if let Some(limit) = self.limit {
100 processes.truncate(limit);
101 }
102
103 let mut context_parts = vec![format!("by '{}'", self.name)];
105 if let Some(ref dir) = in_dir_filter {
106 context_parts.push(format!("in {}", dir.display()));
107 }
108 let context = Some(context_parts.join(" "));
109
110 printer.print_processes_as("by", &processes, context.as_deref());
111 Ok(())
112 }
113
114 fn keeps(&self, p: &Process, in_dir_filter: &Option<PathBuf>) -> bool {
117 if let Some(ref dir_path) = in_dir_filter {
119 if !crate::core::filters::matches_in_filter(p, dir_path) {
120 return false;
121 }
122 }
123
124 if let Some(min_cpu) = self.min_cpu {
126 if p.cpu_percent < min_cpu {
127 return false;
128 }
129 }
130
131 if let Some(min_mem) = self.min_mem {
133 if p.memory_mb < min_mem {
134 return false;
135 }
136 }
137
138 if let Some(status) = self.status {
140 if !status.matches(p.status) {
141 return false;
142 }
143 }
144
145 if let Some(min_uptime) = self.min_uptime {
147 if let Some(start_time) = p.start_time {
148 let now = std::time::SystemTime::now()
149 .duration_since(std::time::UNIX_EPOCH)
150 .map(|d| d.as_secs())
151 .unwrap_or(0);
152 if now.saturating_sub(start_time) < min_uptime {
153 return false;
154 }
155 } else {
156 return false;
157 }
158 }
159
160 if let Some(ppid) = self.parent {
162 if p.parent_pid != Some(ppid) {
163 return false;
164 }
165 }
166
167 true
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::core::ProcessStatus;
175
176 fn command() -> ByCommand {
177 ByCommand {
178 name: "x".to_string(),
179 cmd: false,
180 in_dir: None,
181 min_cpu: None,
182 min_mem: None,
183 status: None,
184 min_uptime: None,
185 parent: None,
186 json: false,
187 verbose: false,
188 wide: false,
189 limit: None,
190 sort: SortKey::Cpu,
191 }
192 }
193
194 fn bare_process() -> Process {
197 Process {
198 pid: 1,
199 name: "x".to_string(),
200 exe_path: None,
201 cwd: None,
202 command: None,
203 argv0: None,
204 raw: None,
205 cpu_percent: 0.0,
206 memory_mb: 0.0,
207 status: ProcessStatus::Sleeping,
208 user: None,
209 parent_pid: None,
210 start_time: None,
211 uptime_secs: None,
212 }
213 }
214
215 #[test]
216 fn a_process_with_no_working_directory_fails_in() {
217 let dir = Some(PathBuf::from("/"));
218 assert!(!command().keeps(&bare_process(), &dir));
219 assert!(command().keeps(&bare_process(), &None));
220 }
221
222 #[test]
223 fn a_process_with_no_start_time_fails_min_uptime() {
224 let cmd = ByCommand {
225 min_uptime: Some(0),
226 ..command()
227 };
228 assert!(!cmd.keeps(&bare_process(), &None));
229 }
230}