proc_cli/commands/
list.rs1use crate::core::{resolve_in_dir, sort_processes, Process, ProcessStatus, SortKey};
11use crate::error::Result;
12use crate::ui::{OutputFormat, Printer};
13use clap::Args;
14use std::path::PathBuf;
15
16#[derive(Args, Debug)]
18pub struct ListCommand {
19 pub name: Option<String>,
21
22 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
24 pub in_dir: Option<String>,
25
26 #[arg(long, short = 'p')]
28 pub path: Option<String>,
29
30 #[arg(long)]
32 pub min_cpu: Option<f32>,
33
34 #[arg(long)]
36 pub min_mem: Option<f64>,
37
38 #[arg(long)]
40 pub status: Option<String>,
41
42 #[arg(long)]
44 pub min_uptime: Option<u64>,
45
46 #[arg(long)]
48 pub parent: Option<u32>,
49
50 #[arg(long, short = 'j')]
52 pub json: bool,
53
54 #[arg(long, short = 'v')]
56 pub verbose: bool,
57
58 #[arg(long, short = 'n')]
60 pub limit: Option<usize>,
61
62 #[arg(long, short = 's', value_enum, default_value_t = SortKey::Cpu)]
64 pub sort: SortKey,
65}
66
67impl ListCommand {
68 pub fn execute(&self) -> Result<()> {
70 let format = if self.json {
71 OutputFormat::Json
72 } else {
73 OutputFormat::Human
74 };
75 let printer = Printer::new(format, self.verbose);
76
77 let mut processes = if let Some(ref name) = self.name {
79 let names: Vec<String> = name
80 .split(',')
81 .map(|s| s.trim().to_string())
82 .filter(|s| !s.is_empty())
83 .collect();
84 let mut all = Vec::new();
85 let mut seen = std::collections::HashSet::new();
86 for n in &names {
87 if let Ok(found) = Process::find_by_name(n) {
88 for p in found {
89 if seen.insert(p.pid) {
90 all.push(p);
91 }
92 }
93 }
94 }
95 all
96 } else {
97 Process::find_all()?
98 };
99
100 let in_dir_filter = resolve_in_dir(&self.in_dir);
102
103 let path_filter: Option<PathBuf> = self.path.as_ref().map(|p| {
105 let path = PathBuf::from(p);
106 if path.is_relative() {
107 std::env::current_dir()
108 .unwrap_or_else(|_| PathBuf::from("."))
109 .join(path)
110 } else {
111 path
112 }
113 });
114
115 processes.retain(|p| {
117 if let Some(ref dir_path) = in_dir_filter {
119 if let Some(ref proc_cwd) = p.cwd {
120 let proc_path = PathBuf::from(proc_cwd);
121 if !proc_path.starts_with(dir_path) {
122 return false;
123 }
124 } else {
125 return false;
126 }
127 }
128
129 if let Some(ref exe_path) = path_filter {
131 if let Some(ref proc_exe) = p.exe_path {
132 let proc_path = PathBuf::from(proc_exe);
133 if !proc_path.starts_with(exe_path) {
134 return false;
135 }
136 } else {
137 return false;
138 }
139 }
140
141 if let Some(min_cpu) = self.min_cpu {
143 if p.cpu_percent < min_cpu {
144 return false;
145 }
146 }
147
148 if let Some(min_mem) = self.min_mem {
150 if p.memory_mb < min_mem {
151 return false;
152 }
153 }
154
155 if let Some(ref status) = self.status {
157 let status_match = match status.to_lowercase().as_str() {
158 "running" => matches!(p.status, ProcessStatus::Running),
159 "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
160 "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
161 "zombie" => matches!(p.status, ProcessStatus::Zombie),
162 _ => true,
163 };
164 if !status_match {
165 return false;
166 }
167 }
168
169 if let Some(min_uptime) = self.min_uptime {
171 if let Some(start_time) = p.start_time {
172 let now = std::time::SystemTime::now()
173 .duration_since(std::time::UNIX_EPOCH)
174 .map(|d| d.as_secs())
175 .unwrap_or(0);
176 if now.saturating_sub(start_time) < min_uptime {
177 return false;
178 }
179 } else {
180 return false;
181 }
182 }
183
184 if let Some(ppid) = self.parent {
186 if p.parent_pid != Some(ppid) {
187 return false;
188 }
189 }
190
191 true
192 });
193
194 sort_processes(&mut processes, self.sort);
196
197 if let Some(limit) = self.limit {
199 processes.truncate(limit);
200 }
201
202 let context = in_dir_filter
204 .as_ref()
205 .map(|p| format!("in {}", p.display()));
206
207 printer.print_processes_with_context(&processes, context.as_deref());
208 Ok(())
209 }
210}