hyper-jump 0.11.0

A generalist all-in-one command-line version manager toolset for a more efficient development workflow.
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

use anyhow::anyhow;
use anyhow::Result;
use tokio::time::sleep;
use tokio::time::Duration;

use crate::ports::Process;

#[derive(Clone, Copy)]
pub struct TokioProcess;

impl Process for TokioProcess {
    async fn run(&self, program: &Path, args: &[String]) -> anyhow::Result<()> {
        let _term = Arc::new(AtomicBool::new(false));

        #[cfg(unix)]
        {
            signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&_term))?;
        }

        let mut child = tokio::process::Command::new(program);
        child.args(args);

        let mut spawned_child = child.spawn()?;
        watch_process(&mut spawned_child, &_term).await
    }

    async fn output(&self, program: &Path, args: &[String]) -> anyhow::Result<Vec<u8>> {
        let output = tokio::process::Command::new(program).args(args).output().await?;
        Ok(output.stdout)
    }
}

async fn watch_process(
    spawned_child: &mut tokio::process::Child,
    term_signal: &Arc<AtomicBool>,
) -> Result<()> {
    tokio::select! {
        status = spawned_child.wait() => handle_process_exit(status).await,
        _ = tokio::signal::ctrl_c() => handle_ctrl_c(spawned_child, term_signal).await,
    }
}

async fn handle_process_exit(
    status: Result<std::process::ExitStatus, std::io::Error>,
) -> Result<()> {
    match status?.code() {
        Some(0) => Ok(()),
        Some(code) => {
            std::process::exit(code);
        }
        None => Err(anyhow!("Process terminated by signal")),
    }
}

async fn handle_ctrl_c(
    spawned_child: &mut tokio::process::Child,
    term_signal: &Arc<AtomicBool>,
) -> Result<()> {
    term_signal.store(true, std::sync::atomic::Ordering::Relaxed);

    #[cfg(unix)]
    handle_unix_signals(spawned_child, term_signal)?;

    sleep(Duration::from_millis(200)).await;
    Ok(())
}

#[cfg(unix)]
fn handle_unix_signals(
    spawned_child: &mut tokio::process::Child,
    term_signal: &Arc<AtomicBool>,
) -> Result<()> {
    use std::sync::atomic::Ordering;

    use nix::sys::signal::Signal;
    use nix::sys::signal::{self};
    use nix::unistd::Pid;

    if term_signal.load(Ordering::Relaxed) {
        let pid = spawned_child.id().expect("Failed to get child process ID") as i32;
        signal::kill(Pid::from_raw(pid), Signal::SIGUSR1)?;
        term_signal.store(false, Ordering::Relaxed);
    }

    Ok(())
}