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;
16
17#[derive(Args, Debug)]
19pub struct InfoCommand {
20 #[arg(required = true)]
22 targets: Vec<String>,
23
24 #[arg(long, short)]
26 json: bool,
27
28 #[arg(long, short)]
30 verbose: bool,
31}
32
33impl InfoCommand {
34 pub fn execute(&self) -> Result<()> {
36 let format = if self.json {
37 OutputFormat::Json
38 } else {
39 OutputFormat::Human
40 };
41 let printer = Printer::new(format, self.verbose);
42
43 let all_targets: Vec<String> = self.targets.iter().flat_map(|t| parse_targets(t)).collect();
45
46 let mut found = Vec::new();
47 let mut not_found = Vec::new();
48 let mut seen_pids = std::collections::HashSet::new();
49
50 for target in &all_targets {
51 match resolve_target(target) {
52 Ok(processes) => {
53 if processes.is_empty() {
54 not_found.push(target.clone());
55 } else {
56 for proc in processes {
57 if seen_pids.insert(proc.pid) {
59 found.push(proc);
60 }
61 }
62 }
63 }
64 Err(_) => not_found.push(target.clone()),
65 }
66 }
67
68 if self.json {
69 printer.print_json(&InfoOutput {
70 action: "info",
71 success: !found.is_empty(),
72 found_count: found.len(),
73 not_found_count: not_found.len(),
74 processes: &found,
75 not_found: ¬_found,
76 });
77 } else {
78 for proc in &found {
79 self.print_process_info(proc);
80 }
81
82 if !not_found.is_empty() {
83 for target in ¬_found {
84 printer.warning(&format!("Target '{}' not found", target));
85 }
86 }
87 }
88
89 Ok(())
90 }
91
92 fn print_process_info(&self, proc: &Process) {
93 println!(
94 "{} Process {}",
95 "✓".green().bold(),
96 proc.pid.to_string().cyan().bold()
97 );
98 println!();
99 println!(" {} {}", "Name:".bright_black(), proc.name.white().bold());
100 println!(
101 " {} {}",
102 "PID:".bright_black(),
103 proc.pid.to_string().cyan()
104 );
105
106 if let Some(ref path) = proc.exe_path {
107 println!(" {} {}", "Path:".bright_black(), path);
108 }
109
110 if let Some(ref user) = proc.user {
111 println!(" {} {}", "User:".bright_black(), user);
112 }
113
114 if let Some(ppid) = proc.parent_pid {
115 println!(
116 " {} {}",
117 "Parent PID:".bright_black(),
118 ppid.to_string().cyan()
119 );
120 }
121
122 let status_str = format!("{:?}", proc.status);
123 let status_colored = match proc.status {
124 ProcessStatus::Running => status_str.green(),
125 ProcessStatus::Sleeping => status_str.blue(),
126 ProcessStatus::Stopped => status_str.yellow(),
127 ProcessStatus::Zombie => status_str.red(),
128 _ => status_str.white(),
129 };
130 println!(" {} {}", "Status:".bright_black(), status_colored);
131
132 println!(" {} {:.1}%", "CPU:".bright_black(), proc.cpu_percent);
133 println!(" {} {:.1} MB", "Memory:".bright_black(), proc.memory_mb);
134
135 if let Some(start_time) = proc.start_time {
136 let duration = std::time::SystemTime::now()
137 .duration_since(std::time::UNIX_EPOCH)
138 .map(|d| d.as_secs().saturating_sub(start_time))
139 .unwrap_or(0);
140
141 let uptime = format_duration(duration);
142 println!(" {} {}", "Uptime:".bright_black(), uptime);
143 }
144
145 if self.verbose {
146 if let Some(ref cmd) = proc.command {
147 println!(" {} {}", "Command:".bright_black(), cmd.bright_black());
148 }
149 }
150
151 println!();
152 }
153}
154
155fn format_duration(secs: u64) -> String {
156 if secs < 60 {
157 format!("{}s", secs)
158 } else if secs < 3600 {
159 format!("{}m {}s", secs / 60, secs % 60)
160 } else if secs < 86400 {
161 format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
162 } else {
163 format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
164 }
165}
166
167#[derive(Serialize)]
168struct InfoOutput<'a> {
169 action: &'static str,
170 success: bool,
171 found_count: usize,
172 not_found_count: usize,
173 processes: &'a [Process],
174 not_found: &'a [String],
175}