proc_cli/commands/
kill.rs1use crate::core::{parse_targets, resolve_in_dir, resolve_targets_excluding_self};
12use crate::error::{ProcError, Result};
13use crate::ui::{OutputFormat, Printer};
14use clap::Args;
15use dialoguer::Confirm;
16use std::path::PathBuf;
17
18#[derive(Args, Debug)]
20pub struct KillCommand {
21 pub target: String,
23
24 #[arg(long, short = 'y')]
26 pub yes: bool,
27
28 #[arg(long)]
30 pub dry_run: bool,
31
32 #[arg(long, short = 'j')]
34 pub json: bool,
35
36 #[arg(long, short = 'v')]
38 pub verbose: bool,
39
40 #[arg(long, short = 'g')]
42 pub graceful: bool,
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 KillCommand {
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 kill {} 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("kill", &processes);
111
112 let confirmed = Confirm::new()
113 .with_prompt(format!(
114 "Kill {} process{}?",
115 processes.len(),
116 if processes.len() == 1 { "" } else { "es" }
117 ))
118 .default(false)
119 .interact()
120 .unwrap_or(false);
121
122 if !confirmed {
123 printer.warning("Cancelled");
124 return Ok(());
125 }
126 }
127
128 let mut killed = Vec::new();
130 let mut failed = Vec::new();
131
132 for proc in processes {
133 let result = if self.graceful {
134 proc.terminate()
135 } else {
136 proc.kill()
137 };
138
139 match result {
140 Ok(()) => killed.push(proc),
141 Err(e) => failed.push((proc, e.to_string())),
142 }
143 }
144
145 printer.print_kill_result(&killed, &failed);
146
147 if failed.is_empty() {
148 Ok(())
149 } else {
150 Err(ProcError::SignalError(format!(
151 "Failed to kill {} process(es)",
152 failed.len()
153 )))
154 }
155 }
156}