proc_cli/commands/
orphans.rs1use crate::core::{resolve_in_dir, Process};
10use crate::error::Result;
11use crate::ui::{OutputFormat, Printer};
12use clap::Args;
13use dialoguer::Confirm;
14use std::path::PathBuf;
15
16#[derive(Args, Debug)]
18pub struct OrphansCommand {
19 #[arg(long, short = 'k')]
21 pub kill: bool,
22
23 #[arg(long)]
25 pub dry_run: bool,
26
27 #[arg(long, short = 'y')]
29 pub yes: bool,
30
31 #[arg(long, short = 'j')]
33 pub json: bool,
34
35 #[arg(long, short = 'v')]
37 pub verbose: bool,
38
39 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
41 pub in_dir: Option<String>,
42
43 #[arg(long = "by", short = 'b')]
45 pub by_name: Option<String>,
46}
47
48impl OrphansCommand {
49 pub fn execute(&self) -> Result<()> {
51 let format = if self.json {
52 OutputFormat::Json
53 } else {
54 OutputFormat::Human
55 };
56 let printer = Printer::new(format, self.verbose);
57
58 let mut processes = Process::find_orphans()?;
59
60 let in_dir_filter = resolve_in_dir(&self.in_dir);
62 processes.retain(|p| {
63 if let Some(ref dir_path) = in_dir_filter {
64 if let Some(ref cwd) = p.cwd {
65 if !PathBuf::from(cwd).starts_with(dir_path) {
66 return false;
67 }
68 } else {
69 return false;
70 }
71 }
72 if let Some(ref name) = self.by_name {
73 if !p.name.to_lowercase().contains(&name.to_lowercase()) {
74 return false;
75 }
76 }
77 true
78 });
79
80 if processes.is_empty() {
81 printer.success("No orphaned processes found");
82 return Ok(());
83 }
84
85 printer.warning(&format!(
86 "Found {} orphaned process{}",
87 processes.len(),
88 if processes.len() == 1 { "" } else { "es" }
89 ));
90 printer.print_processes(&processes);
91
92 if self.kill && self.dry_run {
94 printer.warning(&format!(
95 "Dry run: would kill {} orphaned process{}",
96 processes.len(),
97 if processes.len() == 1 { "" } else { "es" }
98 ));
99 return Ok(());
100 }
101
102 if self.kill {
104 if !self.yes && !self.json {
105 let confirmed = Confirm::new()
106 .with_prompt(format!(
107 "Kill {} orphaned process{}?",
108 processes.len(),
109 if processes.len() == 1 { "" } else { "es" }
110 ))
111 .default(false)
112 .interact()
113 .unwrap_or(false);
114
115 if !confirmed {
116 printer.warning("Cancelled");
117 return Ok(());
118 }
119 }
120
121 let mut killed = Vec::new();
122 let mut failed = Vec::new();
123
124 for proc in processes {
125 match proc.kill_and_wait() {
126 Ok(_) => killed.push(proc),
127 Err(e) => failed.push((proc, e.to_string())),
128 }
129 }
130
131 printer.print_kill_result(&killed, &failed);
132 }
133
134 Ok(())
135 }
136}