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    Port(u16),
16    Pid(u32),
17    Name(String),
18}
19
20/// Parse a target string and determine its type
21pub fn parse_target(target: &str) -> TargetType {
22    let target = target.trim();
23
24    // Explicit port prefix
25    if target.starts_with(':') {
26        if let Ok(port) = parse_port(target) {
27            return TargetType::Port(port);
28        }
29    }
30
31    // Pure number - treat as PID
32    if let Ok(pid) = target.parse::<u32>() {
33        return TargetType::Pid(pid);
34    }
35
36    // Otherwise it's a name
37    TargetType::Name(target.to_string())
38}
39
40/// Resolve a target to processes
41pub fn resolve_target(target: &str) -> Result<Vec<Process>> {
42    match parse_target(target) {
43        TargetType::Port(port) => resolve_port(port),
44        TargetType::Pid(pid) => resolve_pid(pid),
45        TargetType::Name(name) => Process::find_by_name(&name),
46    }
47}
48
49/// Resolve a single target to exactly one process
50pub fn resolve_target_single(target: &str) -> Result<Process> {
51    let processes = resolve_target(target)?;
52
53    if processes.is_empty() {
54        return Err(ProcError::ProcessNotFound(target.to_string()));
55    }
56
57    if processes.len() > 1 {
58        return Err(ProcError::InvalidInput(format!(
59            "Target '{}' matches {} processes. Be more specific.",
60            target,
61            processes.len()
62        )));
63    }
64
65    Ok(processes.into_iter().next().unwrap())
66}
67
68/// Resolve port to process
69fn resolve_port(port: u16) -> Result<Vec<Process>> {
70    match PortInfo::find_by_port(port)? {
71        Some(port_info) => match Process::find_by_pid(port_info.pid)? {
72            Some(proc) => Ok(vec![proc]),
73            None => Err(ProcError::ProcessGone(port_info.pid)),
74        },
75        None => Err(ProcError::PortNotFound(port)),
76    }
77}
78
79/// Resolve PID to process
80fn resolve_pid(pid: u32) -> Result<Vec<Process>> {
81    match Process::find_by_pid(pid)? {
82        Some(proc) => Ok(vec![proc]),
83        None => Err(ProcError::ProcessNotFound(pid.to_string())),
84    }
85}
86
87/// Find all ports a process is listening on
88pub fn find_ports_for_pid(pid: u32) -> Result<Vec<PortInfo>> {
89    let all_ports = PortInfo::get_all_listening()?;
90    Ok(all_ports.into_iter().filter(|p| p.pid == pid).collect())
91}