Skip to main content

proc_cli/commands/
by.rs

1//! `proc by` - Filter processes by name
2//!
3//! Examples:
4//!   proc by node               # Processes named 'node'
5//!   proc by node --in .        # Node processes in current directory
6//!   proc by node --min-cpu 5   # Node processes using >5% CPU
7//!   proc by "my app"           # Processes with spaces in name
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 name
16#[derive(Args, Debug)]
17pub struct ByCommand {
18    /// Process name or pattern to match
19    pub name: String,
20
21    /// Filter by directory (defaults to current directory if no path given)
22    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
23    pub in_dir: Option<String>,
24
25    /// Filter by executable path
26    #[arg(long, short = 'p')]
27    pub path: Option<String>,
28
29    /// Only show processes using more than this CPU %
30    #[arg(long)]
31    pub min_cpu: Option<f32>,
32
33    /// Only show processes using more than this memory (MB)
34    #[arg(long)]
35    pub min_mem: Option<f64>,
36
37    /// Filter by status: running, sleeping, stopped, zombie
38    #[arg(long)]
39    pub status: Option<String>,
40
41    /// Output as JSON
42    #[arg(long, short = 'j')]
43    pub json: bool,
44
45    /// Show verbose output with command line, cwd, and parent PID
46    #[arg(long, short = 'v')]
47    pub verbose: bool,
48
49    /// Limit the number of results
50    #[arg(long, short = 'n')]
51    pub limit: Option<usize>,
52
53    /// Sort by: cpu, mem, pid, name
54    #[arg(long, short = 's', default_value = "cpu")]
55    pub sort: String,
56}
57
58impl ByCommand {
59    /// Executes the by command, listing processes matching the name filter.
60    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        // Get processes by name
69        let mut processes = Process::find_by_name(&self.name)?;
70
71        // Resolve --in filter path
72        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        // Resolve path filter
88        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        // Apply filters
100        processes.retain(|p| {
101            // Directory filter (--in)
102            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            // Path filter (executable path)
114            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            // CPU filter
126            if let Some(min_cpu) = self.min_cpu {
127                if p.cpu_percent < min_cpu {
128                    return false;
129                }
130            }
131
132            // Memory filter
133            if let Some(min_mem) = self.min_mem {
134                if p.memory_mb < min_mem {
135                    return false;
136                }
137            }
138
139            // Status filter
140            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        // Sort processes
157        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            _ => {} // Keep default order
171        }
172
173        // Apply limit if specified
174        if let Some(limit) = self.limit {
175            processes.truncate(limit);
176        }
177
178        // Build context string for output
179        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}