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::{resolve_in_dir, sort_processes, Process, ProcessStatus, SortKey};
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    /// 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', value_enum, default_value_t = SortKey::Cpu)]
59    pub sort: SortKey,
60}
61
62impl ByCommand {
63    /// Executes the by command, listing processes matching the name filter.
64    pub fn execute(&self) -> Result<()> {
65        let format = if self.json {
66            OutputFormat::Json
67        } else {
68            OutputFormat::Human
69        };
70        let printer = Printer::new(format, self.verbose);
71
72        // Get processes by name
73        let mut processes = Process::find_by_name(&self.name)?;
74
75        // Resolve --in filter path
76        let in_dir_filter = resolve_in_dir(&self.in_dir);
77
78        // Apply filters
79        processes.retain(|p| {
80            // Directory filter (--in)
81            if let Some(ref dir_path) = in_dir_filter {
82                if let Some(ref proc_cwd) = p.cwd {
83                    let proc_path = PathBuf::from(proc_cwd);
84                    if !proc_path.starts_with(dir_path) {
85                        return false;
86                    }
87                } else {
88                    return false;
89                }
90            }
91
92            // CPU filter
93            if let Some(min_cpu) = self.min_cpu {
94                if p.cpu_percent < min_cpu {
95                    return false;
96                }
97            }
98
99            // Memory filter
100            if let Some(min_mem) = self.min_mem {
101                if p.memory_mb < min_mem {
102                    return false;
103                }
104            }
105
106            // Status filter
107            if let Some(ref status) = self.status {
108                let status_match = match status.to_lowercase().as_str() {
109                    "running" => matches!(p.status, ProcessStatus::Running),
110                    "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
111                    "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
112                    "zombie" => matches!(p.status, ProcessStatus::Zombie),
113                    _ => true,
114                };
115                if !status_match {
116                    return false;
117                }
118            }
119
120            // Uptime filter
121            if let Some(min_uptime) = self.min_uptime {
122                if let Some(start_time) = p.start_time {
123                    let now = std::time::SystemTime::now()
124                        .duration_since(std::time::UNIX_EPOCH)
125                        .map(|d| d.as_secs())
126                        .unwrap_or(0);
127                    if now.saturating_sub(start_time) < min_uptime {
128                        return false;
129                    }
130                } else {
131                    return false;
132                }
133            }
134
135            // Parent PID filter
136            if let Some(ppid) = self.parent {
137                if p.parent_pid != Some(ppid) {
138                    return false;
139                }
140            }
141
142            true
143        });
144
145        // Sort processes
146        sort_processes(&mut processes, self.sort);
147
148        // Apply limit if specified
149        if let Some(limit) = self.limit {
150            processes.truncate(limit);
151        }
152
153        // Build context string for output
154        let mut context_parts = vec![format!("by '{}'", self.name)];
155        if let Some(ref dir) = in_dir_filter {
156            context_parts.push(format!("in {}", dir.display()));
157        }
158        let context = Some(context_parts.join(" "));
159
160        printer.print_processes_with_context(&processes, context.as_deref());
161        Ok(())
162    }
163}