Skip to main content

proc_cli/commands/
orphans.rs

1//! `proc orphans` - Find orphaned processes
2//!
3//! Examples:
4//!   proc orphans              # List orphaned processes
5//!   proc orphans --in .       # Orphans in current directory
6//!   proc orphans --kill       # Find and kill orphans
7//!   proc orphans --kill --yes # Kill orphans without confirmation
8
9use crate::core::{apply_filters, Process};
10use crate::error::Result;
11use crate::ui::{plural, Printer};
12use clap::Args;
13use dialoguer::Confirm;
14
15/// Find orphaned processes (parent has exited)
16#[derive(Args, Debug)]
17pub struct OrphansCommand {
18    /// Kill found orphaned processes
19    #[arg(long, short = 'k')]
20    pub kill: bool,
21
22    /// Show what would be killed without actually killing
23    #[arg(long)]
24    pub dry_run: bool,
25
26    /// Skip confirmation when killing
27    #[arg(long, short = 'y')]
28    pub yes: bool,
29
30    /// Output as JSON
31    #[arg(long, short = 'j')]
32    pub json: bool,
33
34    /// Show verbose output
35    #[arg(long, short = 'v')]
36    pub verbose: bool,
37
38    /// Filter by directory (defaults to current directory if no path given)
39    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
40    pub in_dir: Option<String>,
41
42    /// Filter by process name
43    #[arg(long = "by", short = 'b')]
44    pub by_name: Option<String>,
45}
46
47impl OrphansCommand {
48    /// Executes the orphans command, finding processes whose parent has exited.
49    pub fn execute(&self) -> Result<()> {
50        let printer = Printer::from_flags(self.json, self.verbose);
51
52        let mut processes = Process::find_orphans()?;
53
54        // Apply --in and --by filters
55        apply_filters(&mut processes, &self.in_dir, &self.by_name);
56
57        if processes.is_empty() {
58            printer.print_empty_result("orphans", "No orphaned processes found");
59            return Ok(());
60        }
61
62        printer.warning(&format!(
63            "Found {} orphaned process{}",
64            processes.len(),
65            plural(processes.len())
66        ));
67
68        if !self.kill {
69            printer.print_processes_as("orphans", &processes, None);
70            return Ok(());
71        }
72
73        // Dry run: show what would be killed
74        if self.dry_run {
75            printer.print_dry_run_with(
76                "orphans",
77                &processes,
78                &format!(
79                    "Dry run: would kill {} orphaned process{}",
80                    processes.len(),
81                    plural(processes.len())
82                ),
83            );
84            return Ok(());
85        }
86
87        // Kill: JSON prints a single action result; humans see the list first
88        if !self.json {
89            printer.print_processes_as("orphans", &processes, None);
90        }
91
92        if !self.yes && !self.json {
93            let confirmed = Confirm::new()
94                .with_prompt(format!(
95                    "Kill {} orphaned process{}?",
96                    processes.len(),
97                    plural(processes.len())
98                ))
99                .default(false)
100                .interact()?;
101
102            if !confirmed {
103                printer.warning("Cancelled");
104                return Ok(());
105            }
106        }
107
108        let mut killed = Vec::new();
109        let mut failed = Vec::new();
110
111        for proc in processes {
112            match proc.kill_and_wait() {
113                Ok(_) => killed.push(proc),
114                Err(e) => failed.push((proc, e.to_string())),
115            }
116        }
117
118        printer.print_action_result_as("orphans", "kill", &killed, &failed)
119    }
120}