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
86 processes.retain(|p| self.keeps(p, &in_dir_filter));
88
89 sort_processes(&mut processes, self.sort);
91
92 if let Some(limit) = self.limit {
94 processes.truncate(limit);
95 }
96
97 let mut context_parts = vec![format!("by '{}'", self.name)];
99 if let Some(ref dir) = in_dir_filter {
100 context_parts.push(format!("in {}", dir.display()));
101 }
102 let context = Some(context_parts.join(" "));
103
104 printer.print_processes_as("by", &processes, context.as_deref());
105 Ok(())
106 }
107
108 fn keeps(&self, p: &Process, in_dir_filter: &Option<PathBuf>) -> bool {
111 if let Some(ref dir_path) = in_dir_filter {
113 if let Some(ref proc_cwd) = p.cwd {
114 if !crate::core::filters::cwd_within(proc_cwd, dir_path) {
115 return false;
116 }
117 } else {
118 return false;
119 }
120 }
121
122 if let Some(min_cpu) = self.min_cpu {
124 if p.cpu_percent < min_cpu {
125 return false;
126 }
127 }
128
129 if let Some(min_mem) = self.min_mem {
131 if p.memory_mb < min_mem {
132 return false;
133 }
134 }
135
136 if let Some(status) = self.status {
138 if !status.matches(p.status) {
139 return false;
140 }
141 }
142
143 if let Some(min_uptime) = self.min_uptime {
145 if let Some(start_time) = p.start_time {
146 let now = std::time::SystemTime::now()
147 .duration_since(std::time::UNIX_EPOCH)
148 .map(|d| d.as_secs())
149 .unwrap_or(0);
150 if now.saturating_sub(start_time) < min_uptime {
151 return false;
152 }
153 } else {
154 return false;
155 }
156 }
157
158 if let Some(ppid) = self.parent {
160 if p.parent_pid != Some(ppid) {
161 return false;
162 }
163 }
164
165 true
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::core::ProcessStatus;
173
174 fn command() -> ByCommand {
175 ByCommand {
176 name: "x".to_string(),
177 cmd: false,
178 in_dir: None,
179 min_cpu: None,
180 min_mem: None,
181 status: None,
182 min_uptime: None,
183 parent: None,
184 json: false,
185 verbose: false,
186 wide: false,
187 limit: None,
188 sort: SortKey::Cpu,
189 }
190 }
191
192 fn bare_process() -> Process {
195 Process {
196 pid: 1,
197 name: "x".to_string(),
198 exe_path: None,
199 cwd: None,
200 command: None,
201 argv0: None,
202 raw: None,
203 cpu_percent: 0.0,
204 memory_mb: 0.0,
205 status: ProcessStatus::Sleeping,
206 user: None,
207 parent_pid: None,
208 start_time: None,
209 uptime_secs: None,
210 }
211 }
212
213 #[test]
214 fn a_process_with_no_working_directory_fails_in() {
215 let dir = Some(PathBuf::from("/"));
216 assert!(!command().keeps(&bare_process(), &dir));
217 assert!(command().keeps(&bare_process(), &None));
218 }
219
220 #[test]
221 fn a_process_with_no_start_time_fails_min_uptime() {
222 let cmd = ByCommand {
223 min_uptime: Some(0),
224 ..command()
225 };
226 assert!(!cmd.keeps(&bare_process(), &None));
227 }
228}