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    /// 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    /// Output as JSON
38    #[arg(long, short = 'j')]
39    pub json: bool,
40
41    /// Show verbose output with command line, cwd, and parent PID
42    #[arg(long, short = 'v')]
43    pub verbose: bool,
44
45    /// Limit the number of results
46    #[arg(long, short = 'n')]
47    pub limit: Option<usize>,
48
49    /// Sort by: cpu, mem, pid, name
50    #[arg(long, short = 's', default_value = "cpu")]
51    pub sort: String,
52}
53
54impl ByCommand {
55    /// Executes the by command, listing processes matching the name filter.
56    pub fn execute(&self) -> Result<()> {
57        let format = if self.json {
58            OutputFormat::Json
59        } else {
60            OutputFormat::Human
61        };
62        let printer = Printer::new(format, self.verbose);
63
64        // Get processes by name
65        let mut processes = Process::find_by_name(&self.name)?;
66
67        // Resolve --in filter path
68        let in_dir_filter: Option<PathBuf> = self.in_dir.as_ref().map(|p| {
69            if p == "." {
70                std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
71            } else {
72                let path = PathBuf::from(p);
73                if path.is_relative() {
74                    std::env::current_dir()
75                        .unwrap_or_else(|_| PathBuf::from("."))
76                        .join(path)
77                } else {
78                    path
79                }
80            }
81        });
82
83        // Apply filters
84        processes.retain(|p| {
85            // Directory filter (--in)
86            if let Some(ref dir_path) = in_dir_filter {
87                if let Some(ref proc_cwd) = p.cwd {
88                    let proc_path = PathBuf::from(proc_cwd);
89                    if !proc_path.starts_with(dir_path) {
90                        return false;
91                    }
92                } else {
93                    return false;
94                }
95            }
96
97            // CPU filter
98            if let Some(min_cpu) = self.min_cpu {
99                if p.cpu_percent < min_cpu {
100                    return false;
101                }
102            }
103
104            // Memory filter
105            if let Some(min_mem) = self.min_mem {
106                if p.memory_mb < min_mem {
107                    return false;
108                }
109            }
110
111            // Status filter
112            if let Some(ref status) = self.status {
113                let status_match = match status.to_lowercase().as_str() {
114                    "running" => matches!(p.status, ProcessStatus::Running),
115                    "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
116                    "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
117                    "zombie" => matches!(p.status, ProcessStatus::Zombie),
118                    _ => true,
119                };
120                if !status_match {
121                    return false;
122                }
123            }
124
125            true
126        });
127
128        // Sort processes
129        match self.sort.to_lowercase().as_str() {
130            "cpu" => processes.sort_by(|a, b| {
131                b.cpu_percent
132                    .partial_cmp(&a.cpu_percent)
133                    .unwrap_or(std::cmp::Ordering::Equal)
134            }),
135            "mem" | "memory" => processes.sort_by(|a, b| {
136                b.memory_mb
137                    .partial_cmp(&a.memory_mb)
138                    .unwrap_or(std::cmp::Ordering::Equal)
139            }),
140            "pid" => processes.sort_by_key(|p| p.pid),
141            "name" => processes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())),
142            _ => {} // Keep default order
143        }
144
145        // Apply limit if specified
146        if let Some(limit) = self.limit {
147            processes.truncate(limit);
148        }
149
150        // Build context string for output
151        let mut context_parts = vec![format!("by '{}'", self.name)];
152        if let Some(ref dir) = in_dir_filter {
153            context_parts.push(format!("in {}", dir.display()));
154        }
155        let context = Some(context_parts.join(" "));
156
157        printer.print_processes_with_context(&processes, context.as_deref());
158        Ok(())
159    }
160}