proc_cli/core/
target.rs

1//! Target resolution - Convert user input to processes
2//!
3//! Targets can be:
4//! - `:port` - Process listening on this port
5//! - `pid` - Process with this PID (numeric)
6//! - `name` - Processes matching this name
7
8use crate::core::port::{parse_port, PortInfo};
9use crate::core::Process;
10use crate::error::{ProcError, Result};
11
12/// Resolved target type
13#[derive(Debug, Clone)]
14pub enum TargetType {
15    /// Target a process by the port it listens on (e.g., `:3000`)
16    Port(u16),
17    /// Target a process by its process ID (e.g., `1234`)
18    Pid(u32),
19    /// Target processes by name pattern (e.g., `node`)
20    Name(String),
21}
22
23/// Parse a target string and determine its type
24pub fn parse_target(target: &str) -> TargetType {
25    let target = target.trim();
26
27    // Explicit port prefix
28    if target.starts_with(':') {
29        if let Ok(port) = parse_port(target) {
30            return TargetType::Port(port);
31        }
32    }
33
34    // Pure number - treat as PID
35    if let Ok(pid) = target.parse::<u32>() {
36        return TargetType::Pid(pid);
37    }
38
39    // Otherwise it's a name
40    TargetType::Name(target.to_string())
41}
42
43/// Resolve a target to processes
44pub fn resolve_target(target: &str) -> Result<Vec<Process>> {
45    match parse_target(target) {
46        TargetType::Port(port) => resolve_port(port),
47        TargetType::Pid(pid) => resolve_pid(pid),
48        TargetType::Name(name) => Process::find_by_name(&name),
49    }
50}
51
52/// Resolve a single target to exactly one process
53pub fn resolve_target_single(target: &str) -> Result<Process> {
54    let processes = resolve_target(target)?;
55
56    if processes.is_empty() {
57        return Err(ProcError::ProcessNotFound(target.to_string()));
58    }
59
60    if processes.len() > 1 {
61        return Err(ProcError::InvalidInput(format!(
62            "Target '{}' matches {} processes. Be more specific.",
63            target,
64            processes.len()
65        )));
66    }
67
68    Ok(processes.into_iter().next().unwrap())
69}
70
71/// Resolve port to process
72fn resolve_port(port: u16) -> Result<Vec<Process>> {
73    match PortInfo::find_by_port(port)? {
74        Some(port_info) => match Process::find_by_pid(port_info.pid)? {
75            Some(proc) => Ok(vec![proc]),
76            None => Err(ProcError::ProcessGone(port_info.pid)),
77        },
78        None => Err(ProcError::PortNotFound(port)),
79    }
80}
81
82/// Resolve PID to process
83fn resolve_pid(pid: u32) -> Result<Vec<Process>> {
84    match Process::find_by_pid(pid)? {
85        Some(proc) => Ok(vec![proc]),
86        None => Err(ProcError::ProcessNotFound(pid.to_string())),
87    }
88}
89
90/// Find all ports a process is listening on
91pub fn find_ports_for_pid(pid: u32) -> Result<Vec<PortInfo>> {
92    let all_ports = PortInfo::get_all_listening()?;
93    Ok(all_ports.into_iter().filter(|p| p.pid == pid).collect())
94}