Skip to main content

proc_cli/commands/
find_in.rs

1//! `proc in` - Filter processes by working directory
2//!
3//! Examples:
4//!   proc in .                  # Processes in current directory
5//!   proc in /path/to/project   # Processes in specific directory
6//!   proc in . --by node        # Node processes in cwd
7//!   proc in ~/projects         # Processes in ~/projects
8
9use crate::core::{Process, ProcessStatus};
10use crate::error::Result;
11use crate::ui::{OutputFormat, Printer};
12use clap::Args;
13use std::path::PathBuf;
14
15/// Filter processes by working directory
16#[derive(Args, Debug)]
17pub struct InCommand {
18    /// Directory path (absolute, relative, or . for cwd)
19    pub path: String,
20
21    /// Filter by process name
22    #[arg(long = "by", short = 'b')]
23    pub by_name: Option<String>,
24
25    /// Only show processes using more than this CPU %
26    #[arg(long)]
27    pub min_cpu: Option<f32>,
28
29    /// Only show processes using more than this memory (MB)
30    #[arg(long)]
31    pub min_mem: Option<f64>,
32
33    /// Filter by status: running, sleeping, stopped, zombie
34    #[arg(long)]
35    pub status: Option<String>,
36
37    /// Only show processes running longer than this (seconds)
38    #[arg(long)]
39    pub min_uptime: Option<u64>,
40
41    /// Only show children of this parent PID
42    #[arg(long)]
43    pub parent: Option<u32>,
44
45    /// Output as JSON
46    #[arg(long, short = 'j')]
47    pub json: bool,
48
49    /// Show verbose output with command line, cwd, and parent PID
50    #[arg(long, short = 'v')]
51    pub verbose: bool,
52
53    /// Limit the number of results
54    #[arg(long, short = 'n')]
55    pub limit: Option<usize>,
56
57    /// Sort by: cpu, mem, pid, name
58    #[arg(long, short = 's', default_value = "cpu")]
59    pub sort: String,
60}
61
62impl InCommand {
63    /// Expand ~ to home directory
64    fn expand_tilde(path: &str) -> PathBuf {
65        if let Some(stripped) = path.strip_prefix("~/") {
66            if let Ok(home) = std::env::var("HOME") {
67                return PathBuf::from(home).join(stripped);
68            }
69        } else if path == "~" {
70            if let Ok(home) = std::env::var("HOME") {
71                return PathBuf::from(home);
72            }
73        }
74        PathBuf::from(path)
75    }
76
77    /// Executes the in command, listing processes in the specified directory.
78    pub fn execute(&self) -> Result<()> {
79        let format = if self.json {
80            OutputFormat::Json
81        } else {
82            OutputFormat::Human
83        };
84        let printer = Printer::new(format, self.verbose);
85
86        // Get base process list
87        let mut processes = if let Some(ref name) = self.by_name {
88            Process::find_by_name(name)?
89        } else {
90            Process::find_all()?
91        };
92
93        // Resolve directory path
94        let dir_filter = if self.path == "." {
95            std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
96        } else {
97            let expanded = Self::expand_tilde(&self.path);
98            if expanded.is_relative() {
99                std::env::current_dir()
100                    .unwrap_or_else(|_| PathBuf::from("."))
101                    .join(expanded)
102            } else {
103                expanded
104            }
105        };
106
107        // Apply filters
108        processes.retain(|p| {
109            // Directory filter (required for this command)
110            if let Some(ref proc_cwd) = p.cwd {
111                let proc_path = PathBuf::from(proc_cwd);
112                if !proc_path.starts_with(&dir_filter) {
113                    return false;
114                }
115            } else {
116                return false;
117            }
118
119            // CPU filter
120            if let Some(min_cpu) = self.min_cpu {
121                if p.cpu_percent < min_cpu {
122                    return false;
123                }
124            }
125
126            // Memory filter
127            if let Some(min_mem) = self.min_mem {
128                if p.memory_mb < min_mem {
129                    return false;
130                }
131            }
132
133            // Status filter
134            if let Some(ref status) = self.status {
135                let status_match = match status.to_lowercase().as_str() {
136                    "running" => matches!(p.status, ProcessStatus::Running),
137                    "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
138                    "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
139                    "zombie" => matches!(p.status, ProcessStatus::Zombie),
140                    _ => true,
141                };
142                if !status_match {
143                    return false;
144                }
145            }
146
147            // Uptime filter
148            if let Some(min_uptime) = self.min_uptime {
149                if let Some(start_time) = p.start_time {
150                    let now = std::time::SystemTime::now()
151                        .duration_since(std::time::UNIX_EPOCH)
152                        .map(|d| d.as_secs())
153                        .unwrap_or(0);
154                    if now.saturating_sub(start_time) < min_uptime {
155                        return false;
156                    }
157                } else {
158                    return false;
159                }
160            }
161
162            // Parent PID filter
163            if let Some(ppid) = self.parent {
164                if p.parent_pid != Some(ppid) {
165                    return false;
166                }
167            }
168
169            true
170        });
171
172        // Sort processes
173        match self.sort.to_lowercase().as_str() {
174            "cpu" => processes.sort_by(|a, b| {
175                b.cpu_percent
176                    .partial_cmp(&a.cpu_percent)
177                    .unwrap_or(std::cmp::Ordering::Equal)
178            }),
179            "mem" | "memory" => processes.sort_by(|a, b| {
180                b.memory_mb
181                    .partial_cmp(&a.memory_mb)
182                    .unwrap_or(std::cmp::Ordering::Equal)
183            }),
184            "pid" => processes.sort_by_key(|p| p.pid),
185            "name" => processes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())),
186            _ => {} // Keep default order
187        }
188
189        // Apply limit if specified
190        if let Some(limit) = self.limit {
191            processes.truncate(limit);
192        }
193
194        // Build context string for output
195        let mut context_parts = vec![format!("in {}", dir_filter.display())];
196        if let Some(ref name) = self.by_name {
197            context_parts.push(format!("by '{}'", name));
198        }
199        let context = Some(context_parts.join(" "));
200
201        printer.print_processes_with_context(&processes, context.as_deref());
202        Ok(())
203    }
204}