proc_cli/commands/
info.rs1use crate::core::{parse_targets, resolve_in_dir, resolve_target, Process};
11use crate::error::Result;
12use crate::ui::{colorize_status, format_duration, format_memory, 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 = 'j')]
27 json: bool,
28
29 #[arg(long, short = 'v')]
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 = colorize_status(&proc.status, &status_str);
153 println!(" {} {}", "Status:".bright_black(), status_colored);
154
155 println!(" {} {:.1}%", "CPU:".bright_black(), proc.cpu_percent);
156 println!(
157 " {} {}",
158 "Memory:".bright_black(),
159 format_memory(proc.memory_mb)
160 );
161
162 if let Some(start_time) = proc.start_time {
163 let duration = std::time::SystemTime::now()
164 .duration_since(std::time::UNIX_EPOCH)
165 .map(|d| d.as_secs().saturating_sub(start_time))
166 .unwrap_or(0);
167
168 let uptime = format_duration(duration);
169 println!(" {} {}", "Uptime:".bright_black(), uptime);
170 }
171
172 if self.verbose {
173 if let Some(ref cmd) = proc.command {
174 println!(" {} {}", "Command:".bright_black(), cmd.bright_black());
175 }
176 }
177
178 println!();
179 }
180}
181
182#[derive(Serialize)]
183struct InfoOutput<'a> {
184 action: &'static str,
185 success: bool,
186 found_count: usize,
187 not_found_count: usize,
188 processes: &'a [Process],
189 not_found: &'a [String],
190}