1use crate::core::port::{parse_port, PortInfo};
9use crate::core::Process;
10use crate::error::{ProcError, Result};
11
12#[derive(Debug, Clone)]
14pub enum TargetType {
15 Port(u16),
17 Pid(u32),
19 Name(String),
21}
22
23pub fn parse_target(target: &str) -> TargetType {
25 let target = target.trim();
26
27 if target.starts_with(':') {
29 if let Ok(port) = parse_port(target) {
30 return TargetType::Port(port);
31 }
32 }
33
34 if let Ok(pid) = target.parse::<u32>() {
36 return TargetType::Pid(pid);
37 }
38
39 TargetType::Name(target.to_string())
41}
42
43pub 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
52pub 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
71fn 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
82fn 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
90pub 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}
95
96pub fn parse_targets(targets_str: &str) -> Vec<String> {
103 targets_str
104 .split(',')
105 .map(|s| s.trim().to_string())
106 .filter(|s| !s.is_empty())
107 .collect()
108}
109
110pub fn resolve_targets(targets: &[String]) -> (Vec<Process>, Vec<String>) {
114 use std::collections::HashSet;
115
116 let mut all_processes = Vec::new();
117 let mut seen_pids = HashSet::new();
118 let mut not_found = Vec::new();
119
120 for target in targets {
121 match resolve_target(target) {
122 Ok(processes) => {
123 for proc in processes {
124 if seen_pids.insert(proc.pid) {
125 all_processes.push(proc);
126 }
127 }
128 }
129 Err(_) => not_found.push(target.clone()),
130 }
131 }
132
133 (all_processes, not_found)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_parse_targets_single() {
142 assert_eq!(parse_targets(":3000"), vec![":3000"]);
143 assert_eq!(parse_targets("node"), vec!["node"]);
144 assert_eq!(parse_targets("1234"), vec!["1234"]);
145 }
146
147 #[test]
148 fn test_parse_targets_multiple() {
149 assert_eq!(parse_targets(":3000,:8080"), vec![":3000", ":8080"]);
150 assert_eq!(parse_targets("node,python"), vec!["node", "python"]);
151 assert_eq!(
152 parse_targets(":3000,1234,node"),
153 vec![":3000", "1234", "node"]
154 );
155 }
156
157 #[test]
158 fn test_parse_targets_with_whitespace() {
159 assert_eq!(
160 parse_targets(":3000, :8080, :9000"),
161 vec![":3000", ":8080", ":9000"]
162 );
163 assert_eq!(parse_targets(" node , python "), vec!["node", "python"]);
164 }
165
166 #[test]
167 fn test_parse_targets_empty_entries() {
168 assert_eq!(parse_targets(":3000,,,:8080"), vec![":3000", ":8080"]);
169 assert_eq!(parse_targets(",,node,,"), vec!["node"]);
170 }
171
172 #[test]
173 fn test_parse_target_port() {
174 assert!(matches!(parse_target(":3000"), TargetType::Port(3000)));
175 assert!(matches!(parse_target(":8080"), TargetType::Port(8080)));
176 }
177
178 #[test]
179 fn test_parse_target_pid() {
180 assert!(matches!(parse_target("1234"), TargetType::Pid(1234)));
181 assert!(matches!(parse_target("99999"), TargetType::Pid(99999)));
182 }
183
184 #[test]
185 fn test_parse_target_name() {
186 assert!(matches!(parse_target("node"), TargetType::Name(_)));
187 assert!(matches!(parse_target("my-process"), TargetType::Name(_)));
188 }
189}