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