proc_cli/commands/
stop.rs1use crate::core::{parse_targets, resolve_targets_excluding_self, Process};
11use crate::error::{ProcError, Result};
12use crate::ui::{OutputFormat, Printer};
13use clap::Args;
14use dialoguer::Confirm;
15use serde::Serialize;
16
17#[derive(Args, Debug)]
19pub struct StopCommand {
20 #[arg(required = true)]
22 target: String,
23
24 #[arg(long, short = 'y')]
26 yes: bool,
27
28 #[arg(long)]
30 dry_run: bool,
31
32 #[arg(long, short)]
34 json: bool,
35
36 #[arg(long, short, default_value = "10")]
38 timeout: u64,
39}
40
41impl StopCommand {
42 pub fn execute(&self) -> Result<()> {
44 let format = if self.json {
45 OutputFormat::Json
46 } else {
47 OutputFormat::Human
48 };
49 let printer = Printer::new(format, false);
50
51 let targets = parse_targets(&self.target);
54 let (processes, not_found) = resolve_targets_excluding_self(&targets);
55
56 for target in ¬_found {
58 printer.warning(&format!("Target not found: {}", target));
59 }
60
61 if processes.is_empty() {
62 return Err(ProcError::ProcessNotFound(self.target.clone()));
63 }
64
65 if self.dry_run {
67 printer.print_processes(&processes);
68 printer.warning(&format!(
69 "Dry run: would stop {} process{}",
70 processes.len(),
71 if processes.len() == 1 { "" } else { "es" }
72 ));
73 return Ok(());
74 }
75
76 if !self.yes && !self.json {
78 self.show_processes(&processes);
79
80 let prompt = format!(
81 "Stop {} process{}?",
82 processes.len(),
83 if processes.len() == 1 { "" } else { "es" }
84 );
85
86 if !Confirm::new()
87 .with_prompt(prompt)
88 .default(false)
89 .interact()?
90 {
91 printer.warning("Aborted");
92 return Ok(());
93 }
94 }
95
96 let mut stopped = Vec::new();
98 let mut failed = Vec::new();
99
100 for proc in &processes {
101 match proc.terminate() {
102 Ok(()) => {
103 let stopped_gracefully = self.wait_for_exit(proc);
105 if stopped_gracefully {
106 stopped.push(proc.clone());
107 } else {
108 match proc.kill_and_wait() {
110 Ok(_) => stopped.push(proc.clone()),
111 Err(e) => failed.push((proc.clone(), e.to_string())),
112 }
113 }
114 }
115 Err(e) => failed.push((proc.clone(), e.to_string())),
116 }
117 }
118
119 if self.json {
121 printer.print_json(&StopOutput {
122 action: "stop",
123 success: failed.is_empty(),
124 stopped_count: stopped.len(),
125 failed_count: failed.len(),
126 stopped: &stopped,
127 failed: &failed
128 .iter()
129 .map(|(p, e)| FailedStop {
130 process: p,
131 error: e,
132 })
133 .collect::<Vec<_>>(),
134 });
135 } else {
136 self.print_results(&printer, &stopped, &failed);
137 }
138
139 Ok(())
140 }
141
142 fn wait_for_exit(&self, proc: &Process) -> bool {
143 let start = std::time::Instant::now();
144 let timeout = std::time::Duration::from_secs(self.timeout);
145
146 while start.elapsed() < timeout {
147 if !proc.is_running() {
148 return true;
149 }
150 std::thread::sleep(std::time::Duration::from_millis(100));
151 }
152
153 false
154 }
155
156 fn show_processes(&self, processes: &[Process]) {
157 use colored::*;
158
159 println!(
160 "\n{} Found {} process{}:\n",
161 "!".yellow().bold(),
162 processes.len().to_string().cyan().bold(),
163 if processes.len() == 1 { "" } else { "es" }
164 );
165
166 for proc in processes {
167 println!(
168 " {} {} [PID {}] - {:.1}% CPU, {:.1} MB",
169 "→".bright_black(),
170 proc.name.white().bold(),
171 proc.pid.to_string().cyan(),
172 proc.cpu_percent,
173 proc.memory_mb
174 );
175 }
176 println!();
177 }
178
179 fn print_results(&self, printer: &Printer, stopped: &[Process], failed: &[(Process, String)]) {
180 use colored::*;
181
182 if !stopped.is_empty() {
183 println!(
184 "{} Stopped {} process{}",
185 "✓".green().bold(),
186 stopped.len().to_string().cyan().bold(),
187 if stopped.len() == 1 { "" } else { "es" }
188 );
189 for proc in stopped {
190 println!(
191 " {} {} [PID {}]",
192 "→".bright_black(),
193 proc.name.white(),
194 proc.pid.to_string().cyan()
195 );
196 }
197 }
198
199 if !failed.is_empty() {
200 printer.error(&format!(
201 "Failed to stop {} process{}",
202 failed.len(),
203 if failed.len() == 1 { "" } else { "es" }
204 ));
205 for (proc, err) in failed {
206 println!(
207 " {} {} [PID {}]: {}",
208 "→".bright_black(),
209 proc.name.white(),
210 proc.pid.to_string().cyan(),
211 err.red()
212 );
213 }
214 }
215 }
216}
217
218#[derive(Serialize)]
219struct StopOutput<'a> {
220 action: &'static str,
221 success: bool,
222 stopped_count: usize,
223 failed_count: usize,
224 stopped: &'a [Process],
225 failed: &'a [FailedStop<'a>],
226}
227
228#[derive(Serialize)]
229struct FailedStop<'a> {
230 process: &'a Process,
231 error: &'a str,
232}