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 resolve_targets_impl(targets, false)
115}
116
117pub fn resolve_targets_excluding_self(targets: &[String]) -> (Vec<Process>, Vec<String>) {
122 resolve_targets_impl(targets, true)
123}
124
125fn resolve_targets_impl(targets: &[String], exclude_self: bool) -> (Vec<Process>, Vec<String>) {
126 use std::collections::HashSet;
127
128 let mut all_processes = Vec::new();
129 let mut seen_pids = HashSet::new();
130 let mut not_found = Vec::new();
131 let self_pid = std::process::id();
132
133 for target in targets {
134 match resolve_target(target) {
135 Ok(processes) => {
136 for proc in processes {
137 if exclude_self && proc.pid == self_pid {
139 continue;
140 }
141 if seen_pids.insert(proc.pid) {
142 all_processes.push(proc);
143 }
144 }
145 }
146 Err(_) => not_found.push(target.clone()),
147 }
148 }
149
150 (all_processes, not_found)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_parse_targets_single() {
159 assert_eq!(parse_targets(":3000"), vec![":3000"]);
160 assert_eq!(parse_targets("node"), vec!["node"]);
161 assert_eq!(parse_targets("1234"), vec!["1234"]);
162 }
163
164 #[test]
165 fn test_parse_targets_multiple() {
166 assert_eq!(parse_targets(":3000,:8080"), vec![":3000", ":8080"]);
167 assert_eq!(parse_targets("node,python"), vec!["node", "python"]);
168 assert_eq!(
169 parse_targets(":3000,1234,node"),
170 vec![":3000", "1234", "node"]
171 );
172 }
173
174 #[test]
175 fn test_parse_targets_with_whitespace() {
176 assert_eq!(
177 parse_targets(":3000, :8080, :9000"),
178 vec![":3000", ":8080", ":9000"]
179 );
180 assert_eq!(parse_targets(" node , python "), vec!["node", "python"]);
181 }
182
183 #[test]
184 fn test_parse_targets_empty_entries() {
185 assert_eq!(parse_targets(":3000,,,:8080"), vec![":3000", ":8080"]);
186 assert_eq!(parse_targets(",,node,,"), vec!["node"]);
187 }
188
189 #[test]
190 fn test_parse_target_port() {
191 assert!(matches!(parse_target(":3000"), TargetType::Port(3000)));
192 assert!(matches!(parse_target(":8080"), TargetType::Port(8080)));
193 }
194
195 #[test]
196 fn test_parse_target_pid() {
197 assert!(matches!(parse_target("1234"), TargetType::Pid(1234)));
198 assert!(matches!(parse_target("99999"), TargetType::Pid(99999)));
199 }
200
201 #[test]
202 fn test_parse_target_name() {
203 assert!(matches!(parse_target("node"), TargetType::Name(_)));
204 assert!(matches!(parse_target("my-process"), TargetType::Name(_)));
205 }
206}