killport 2.0.0

A command-line tool to easily kill processes and containers running on a specified port.
Documentation
//! The `killport` command-line utility is designed to kill processes
//! listening on specified ports.
//!
//! The utility accepts a list of port numbers as input and attempts to
//! terminate any processes listening on those ports.

use clap::Parser;
use log::error;
use log::LevelFilter;
use std::io::Write;
use std::process::exit;

use killport::cli::{service_descriptors, KillPortArgs};
use killport::killport::Killport;

fn main() {
    // Reset SIGPIPE to default behavior so piping output (e.g., `killport 8080 | head`)
    // exits silently instead of panicking with a broken pipe error.
    #[cfg(unix)]
    unsafe {
        nix::libc::signal(nix::libc::SIGPIPE, nix::libc::SIG_DFL);
    }

    // Parse command-line arguments
    let args = KillPortArgs::parse();

    // Set up logging environment
    let log_level = args
        .verbose
        .log_level()
        .map(|level| level.to_level_filter())
        .unwrap_or(LevelFilter::Off);

    env_logger::builder()
        .format(move |buf, record| {
            if log_level <= LevelFilter::Info {
                writeln!(buf, "{}", record.args())
            } else {
                // Default format for lower levels
                writeln!(
                    buf,
                    "[{}] {}: {}",
                    record.target(),
                    record.level(),
                    record.args()
                )
            }
        })
        .format_module_path(log_level == log::LevelFilter::Trace)
        .format_target(log_level == log::LevelFilter::Trace)
        .format_timestamp(Option::None)
        .filter_level(log_level)
        .init();

    let (service_type_singular, _service_type_plural) = service_descriptors(args.mode);

    // Create an instance of Killport
    let killport = Killport::with_real_deps().unwrap_or_else(|e| {
        error!("Failed to initialize: {}", e);
        exit(1);
    });

    // Attempt to kill processes listening on specified ports
    let mut any_not_found = false;

    for port in args.ports {
        match killport.kill_service_by_port(port, args.signal.clone(), args.mode, args.dry_run) {
            Ok(killed_services) => {
                if killed_services.is_empty() {
                    println!("No {} found using port {}", service_type_singular, port);
                    any_not_found = true;
                } else {
                    for (killable_type, name) in killed_services {
                        let action = if args.dry_run {
                            "Would kill"
                        } else {
                            "Successfully killed"
                        };
                        println!(
                            "{} {} '{}' listening on port {}",
                            action, killable_type, name, port
                        );
                    }
                }
            }
            Err(err) => {
                error!("{}", err);
                exit(1);
            }
        }
    }

    if any_not_found && !args.no_fail {
        exit(2);
    }
}