1use crate::core::{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, short = 'p')]
27 pub path: Option<String>,
28
29 #[arg(long)]
31 pub min_cpu: Option<f32>,
32
33 #[arg(long)]
35 pub min_mem: Option<f64>,
36
37 #[arg(long)]
39 pub status: Option<String>,
40
41 #[arg(long, short = 'j')]
43 pub json: bool,
44
45 #[arg(long, short = 'v')]
47 pub verbose: bool,
48
49 #[arg(long, short = 'n')]
51 pub limit: Option<usize>,
52
53 #[arg(long, short = 's', default_value = "cpu")]
55 pub sort: String,
56}
57
58impl ByCommand {
59 pub fn execute(&self) -> Result<()> {
61 let format = if self.json {
62 OutputFormat::Json
63 } else {
64 OutputFormat::Human
65 };
66 let printer = Printer::new(format, self.verbose);
67
68 let mut processes = Process::find_by_name(&self.name)?;
70
71 let in_dir_filter: Option<PathBuf> = self.in_dir.as_ref().map(|p| {
73 if p == "." {
74 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
75 } else {
76 let path = PathBuf::from(p);
77 if path.is_relative() {
78 std::env::current_dir()
79 .unwrap_or_else(|_| PathBuf::from("."))
80 .join(path)
81 } else {
82 path
83 }
84 }
85 });
86
87 let path_filter: Option<PathBuf> = self.path.as_ref().map(|p| {
89 let path = PathBuf::from(p);
90 if path.is_relative() {
91 std::env::current_dir()
92 .unwrap_or_else(|_| PathBuf::from("."))
93 .join(path)
94 } else {
95 path
96 }
97 });
98
99 processes.retain(|p| {
101 if let Some(ref dir_path) = in_dir_filter {
103 if let Some(ref proc_cwd) = p.cwd {
104 let proc_path = PathBuf::from(proc_cwd);
105 if !proc_path.starts_with(dir_path) {
106 return false;
107 }
108 } else {
109 return false;
110 }
111 }
112
113 if let Some(ref exe_path) = path_filter {
115 if let Some(ref proc_exe) = p.exe_path {
116 let proc_path = PathBuf::from(proc_exe);
117 if !proc_path.starts_with(exe_path) {
118 return false;
119 }
120 } else {
121 return false;
122 }
123 }
124
125 if let Some(min_cpu) = self.min_cpu {
127 if p.cpu_percent < min_cpu {
128 return false;
129 }
130 }
131
132 if let Some(min_mem) = self.min_mem {
134 if p.memory_mb < min_mem {
135 return false;
136 }
137 }
138
139 if let Some(ref status) = self.status {
141 let status_match = match status.to_lowercase().as_str() {
142 "running" => matches!(p.status, ProcessStatus::Running),
143 "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
144 "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
145 "zombie" => matches!(p.status, ProcessStatus::Zombie),
146 _ => true,
147 };
148 if !status_match {
149 return false;
150 }
151 }
152
153 true
154 });
155
156 match self.sort.to_lowercase().as_str() {
158 "cpu" => processes.sort_by(|a, b| {
159 b.cpu_percent
160 .partial_cmp(&a.cpu_percent)
161 .unwrap_or(std::cmp::Ordering::Equal)
162 }),
163 "mem" | "memory" => processes.sort_by(|a, b| {
164 b.memory_mb
165 .partial_cmp(&a.memory_mb)
166 .unwrap_or(std::cmp::Ordering::Equal)
167 }),
168 "pid" => processes.sort_by_key(|p| p.pid),
169 "name" => processes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())),
170 _ => {} }
172
173 if let Some(limit) = self.limit {
175 processes.truncate(limit);
176 }
177
178 let mut context_parts = vec![format!("by '{}'", self.name)];
180 if let Some(ref dir) = in_dir_filter {
181 context_parts.push(format!("in {}", dir.display()));
182 }
183 let context = Some(context_parts.join(" "));
184
185 printer.print_processes_with_context(&processes, context.as_deref());
186 Ok(())
187 }
188}