proc_cli/commands/
find_in.rs1use 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 InCommand {
18 pub path: String,
20
21 #[arg(long = "by", short = 'b')]
23 pub by_name: 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, short = 'j')]
39 pub json: bool,
40
41 #[arg(long, short = 'v')]
43 pub verbose: bool,
44
45 #[arg(long, short = 'n')]
47 pub limit: Option<usize>,
48
49 #[arg(long, short = 's', default_value = "cpu")]
51 pub sort: String,
52}
53
54impl InCommand {
55 fn expand_tilde(path: &str) -> PathBuf {
57 if let Some(stripped) = path.strip_prefix("~/") {
58 if let Ok(home) = std::env::var("HOME") {
59 return PathBuf::from(home).join(stripped);
60 }
61 } else if path == "~" {
62 if let Ok(home) = std::env::var("HOME") {
63 return PathBuf::from(home);
64 }
65 }
66 PathBuf::from(path)
67 }
68
69 pub fn execute(&self) -> Result<()> {
71 let format = if self.json {
72 OutputFormat::Json
73 } else {
74 OutputFormat::Human
75 };
76 let printer = Printer::new(format, self.verbose);
77
78 let mut processes = if let Some(ref name) = self.by_name {
80 Process::find_by_name(name)?
81 } else {
82 Process::find_all()?
83 };
84
85 let dir_filter = if self.path == "." {
87 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
88 } else {
89 let expanded = Self::expand_tilde(&self.path);
90 if expanded.is_relative() {
91 std::env::current_dir()
92 .unwrap_or_else(|_| PathBuf::from("."))
93 .join(expanded)
94 } else {
95 expanded
96 }
97 };
98
99 processes.retain(|p| {
101 if let Some(ref proc_cwd) = p.cwd {
103 let proc_path = PathBuf::from(proc_cwd);
104 if !proc_path.starts_with(&dir_filter) {
105 return false;
106 }
107 } else {
108 return false;
109 }
110
111 if let Some(min_cpu) = self.min_cpu {
113 if p.cpu_percent < min_cpu {
114 return false;
115 }
116 }
117
118 if let Some(min_mem) = self.min_mem {
120 if p.memory_mb < min_mem {
121 return false;
122 }
123 }
124
125 if let Some(ref status) = self.status {
127 let status_match = match status.to_lowercase().as_str() {
128 "running" => matches!(p.status, ProcessStatus::Running),
129 "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
130 "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
131 "zombie" => matches!(p.status, ProcessStatus::Zombie),
132 _ => true,
133 };
134 if !status_match {
135 return false;
136 }
137 }
138
139 true
140 });
141
142 match self.sort.to_lowercase().as_str() {
144 "cpu" => processes.sort_by(|a, b| {
145 b.cpu_percent
146 .partial_cmp(&a.cpu_percent)
147 .unwrap_or(std::cmp::Ordering::Equal)
148 }),
149 "mem" | "memory" => processes.sort_by(|a, b| {
150 b.memory_mb
151 .partial_cmp(&a.memory_mb)
152 .unwrap_or(std::cmp::Ordering::Equal)
153 }),
154 "pid" => processes.sort_by_key(|p| p.pid),
155 "name" => processes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())),
156 _ => {} }
158
159 if let Some(limit) = self.limit {
161 processes.truncate(limit);
162 }
163
164 let mut context_parts = vec![format!("in {}", dir_filter.display())];
166 if let Some(ref name) = self.by_name {
167 context_parts.push(format!("by '{}'", name));
168 }
169 let context = Some(context_parts.join(" "));
170
171 printer.print_processes_with_context(&processes, context.as_deref());
172 Ok(())
173 }
174}