proc_cli/commands/
info.rs1use crate::core::{parse_targets, resolve_target, Process, ProcessStatus};
11use crate::error::Result;
12use crate::ui::{OutputFormat, Printer};
13use clap::Args;
14use colored::*;
15use serde::Serialize;
16use std::path::PathBuf;
17
18#[derive(Args, Debug)]
20pub struct InfoCommand {
21 #[arg(required = true)]
23 targets: Vec<String>,
24
25 #[arg(long, short)]
27 json: bool,
28
29 #[arg(long, short)]
31 verbose: bool,
32
33 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
35 pub in_dir: Option<String>,
36
37 #[arg(long = "by", short = 'b')]
39 pub by_name: Option<String>,
40}
41
42impl InfoCommand {
43 pub fn execute(&self) -> Result<()> {
45 let format = if self.json {
46 OutputFormat::Json
47 } else {
48 OutputFormat::Human
49 };
50 let printer = Printer::new(format, self.verbose);
51
52 let all_targets: Vec<String> = self.targets.iter().flat_map(|t| parse_targets(t)).collect();
54
55 let mut found = Vec::new();
56 let mut not_found = Vec::new();
57 let mut seen_pids = std::collections::HashSet::new();
58
59 for target in &all_targets {
60 match resolve_target(target) {
61 Ok(processes) => {
62 if processes.is_empty() {
63 not_found.push(target.clone());
64 } else {
65 for proc in processes {
66 if seen_pids.insert(proc.pid) {
68 found.push(proc);
69 }
70 }
71 }
72 }
73 Err(_) => not_found.push(target.clone()),
74 }
75 }
76
77 let in_dir_filter = resolve_in_dir(&self.in_dir);
79 found.retain(|p| {
80 if let Some(ref dir_path) = in_dir_filter {
81 if let Some(ref cwd) = p.cwd {
82 if !PathBuf::from(cwd).starts_with(dir_path) {
83 return false;
84 }
85 } else {
86 return false;
87 }
88 }
89 if let Some(ref name) = self.by_name {
90 if !p.name.to_lowercase().contains(&name.to_lowercase()) {
91 return false;
92 }
93 }
94 true
95 });
96
97 if self.json {
98 printer.print_json(&InfoOutput {
99 action: "info",
100 success: !found.is_empty(),
101 found_count: found.len(),
102 not_found_count: not_found.len(),
103 processes: &found,
104 not_found: ¬_found,
105 });
106 } else {
107 for proc in &found {
108 self.print_process_info(proc);
109 }
110
111 if !not_found.is_empty() {
112 for target in ¬_found {
113 printer.warning(&format!("Target '{}' not found", target));
114 }
115 }
116 }
117
118 Ok(())
119 }
120
121 fn print_process_info(&self, proc: &Process) {
122 println!(
123 "{} Process {}",
124 "✓".green().bold(),
125 proc.pid.to_string().cyan().bold()
126 );
127 println!();
128 println!(" {} {}", "Name:".bright_black(), proc.name.white().bold());
129 println!(
130 " {} {}",
131 "PID:".bright_black(),
132 proc.pid.to_string().cyan()
133 );
134
135 if let Some(ref path) = proc.exe_path {
136 println!(" {} {}", "Path:".bright_black(), path);
137 }
138
139 if let Some(ref user) = proc.user {
140 println!(" {} {}", "User:".bright_black(), user);
141 }
142
143 if let Some(ppid) = proc.parent_pid {
144 println!(
145 " {} {}",
146 "Parent PID:".bright_black(),
147 ppid.to_string().cyan()
148 );
149 }
150
151 let status_str = format!("{:?}", proc.status);
152 let status_colored = match proc.status {
153 ProcessStatus::Running => status_str.green(),
154 ProcessStatus::Sleeping => status_str.blue(),
155 ProcessStatus::Stopped => status_str.yellow(),
156 ProcessStatus::Zombie => status_str.red(),
157 _ => status_str.white(),
158 };
159 println!(" {} {}", "Status:".bright_black(), status_colored);
160
161 println!(" {} {:.1}%", "CPU:".bright_black(), proc.cpu_percent);
162 println!(" {} {:.1} MB", "Memory:".bright_black(), proc.memory_mb);
163
164 if let Some(start_time) = proc.start_time {
165 let duration = std::time::SystemTime::now()
166 .duration_since(std::time::UNIX_EPOCH)
167 .map(|d| d.as_secs().saturating_sub(start_time))
168 .unwrap_or(0);
169
170 let uptime = format_duration(duration);
171 println!(" {} {}", "Uptime:".bright_black(), uptime);
172 }
173
174 if self.verbose {
175 if let Some(ref cmd) = proc.command {
176 println!(" {} {}", "Command:".bright_black(), cmd.bright_black());
177 }
178 }
179
180 println!();
181 }
182}
183
184fn format_duration(secs: u64) -> String {
185 if secs < 60 {
186 format!("{}s", secs)
187 } else if secs < 3600 {
188 format!("{}m {}s", secs / 60, secs % 60)
189 } else if secs < 86400 {
190 format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
191 } else {
192 format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
193 }
194}
195
196#[derive(Serialize)]
197struct InfoOutput<'a> {
198 action: &'static str,
199 success: bool,
200 found_count: usize,
201 not_found_count: usize,
202 processes: &'a [Process],
203 not_found: &'a [String],
204}
205
206fn resolve_in_dir(in_dir: &Option<String>) -> Option<PathBuf> {
207 in_dir.as_ref().map(|p| {
208 if p == "." {
209 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
210 } else {
211 let path = PathBuf::from(p);
212 if path.is_relative() {
213 std::env::current_dir()
214 .unwrap_or_else(|_| PathBuf::from("."))
215 .join(path)
216 } else {
217 path
218 }
219 }
220 })
221}