proc_cli/commands/
stop.rs1use crate::core::{parse_targets, resolve_in_dir, resolve_targets_excluding_self, Process};
11use crate::error::{ProcError, Result};
12use crate::ui::{OutputFormat, Printer};
13use clap::Args;
14use dialoguer::Confirm;
15use std::path::PathBuf;
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 = 'j')]
34 json: bool,
35
36 #[arg(long, short = 'v')]
38 verbose: bool,
39
40 #[arg(long, short, default_value = "10")]
42 timeout: u64,
43
44 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
46 pub in_dir: Option<String>,
47
48 #[arg(long = "by", short = 'b')]
50 pub by_name: Option<String>,
51}
52
53impl StopCommand {
54 pub fn execute(&self) -> Result<()> {
56 let format = if self.json {
57 OutputFormat::Json
58 } else {
59 OutputFormat::Human
60 };
61 let printer = Printer::new(format, self.verbose);
62
63 let targets = parse_targets(&self.target);
66 let (mut processes, not_found) = resolve_targets_excluding_self(&targets);
67
68 for target in ¬_found {
70 printer.warning(&format!("Target not found: {}", target));
71 }
72
73 let in_dir_filter = resolve_in_dir(&self.in_dir);
75 processes.retain(|p| {
76 if let Some(ref dir_path) = in_dir_filter {
77 if let Some(ref cwd) = p.cwd {
78 if !PathBuf::from(cwd).starts_with(dir_path) {
79 return false;
80 }
81 } else {
82 return false;
83 }
84 }
85 if let Some(ref name) = self.by_name {
86 if !p.name.to_lowercase().contains(&name.to_lowercase()) {
87 return false;
88 }
89 }
90 true
91 });
92
93 if processes.is_empty() {
94 return Err(ProcError::ProcessNotFound(self.target.clone()));
95 }
96
97 if self.dry_run {
99 printer.print_processes(&processes);
100 printer.warning(&format!(
101 "Dry run: would stop {} process{}",
102 processes.len(),
103 if processes.len() == 1 { "" } else { "es" }
104 ));
105 return Ok(());
106 }
107
108 if !self.yes && !self.json {
110 printer.print_confirmation("stop", &processes);
111
112 let prompt = format!(
113 "Stop {} process{}?",
114 processes.len(),
115 if processes.len() == 1 { "" } else { "es" }
116 );
117
118 if !Confirm::new()
119 .with_prompt(prompt)
120 .default(false)
121 .interact()?
122 {
123 printer.warning("Aborted");
124 return Ok(());
125 }
126 }
127
128 let mut stopped = Vec::new();
130 let mut failed = Vec::new();
131
132 for proc in &processes {
133 match proc.terminate() {
134 Ok(()) => {
135 let stopped_gracefully = self.wait_for_exit(proc);
137 if stopped_gracefully {
138 stopped.push(proc.clone());
139 } else {
140 match proc.kill_and_wait() {
142 Ok(_) => stopped.push(proc.clone()),
143 Err(e) => failed.push((proc.clone(), e.to_string())),
144 }
145 }
146 }
147 Err(e) => failed.push((proc.clone(), e.to_string())),
148 }
149 }
150
151 printer.print_action_result("Stopped", &stopped, &failed);
153
154 Ok(())
155 }
156
157 fn wait_for_exit(&self, proc: &Process) -> bool {
158 let start = std::time::Instant::now();
159 let timeout = std::time::Duration::from_secs(self.timeout);
160
161 while start.elapsed() < timeout {
162 if !proc.is_running() {
163 return true;
164 }
165 std::thread::sleep(std::time::Duration::from_millis(100));
166 }
167
168 false
169 }
170}