zouni 0.2.1

Variety of functions that `std` does not offer or `std` offers but are not satisfiable
Documentation
use serde::{Deserialize, Serialize};
use shared_child::SharedChild;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
use std::{
    collections::HashMap,
    io::Read,
    process::{Command, Stdio},
    sync::{
        atomic::{AtomicU16, Ordering},
        Arc, LazyLock, Mutex,
    },
};
#[cfg(target_os = "windows")]
use windows::Win32::System::Threading::CREATE_NO_WINDOW;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SpawnOption {
    pub program: String,
    pub args: Option<Vec<String>>,
    pub cancellation_token: Option<String>,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CommandStatus {
    pub success: bool,
    pub code: Option<i32>,
    pub error: Option<String>,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Output {
    pub status: CommandStatus,
    pub stdout: String,
    pub stderr: String,
}

impl Output {
    fn error(code: Option<i32>, error: Option<String>) -> Self {
        Self {
            status: CommandStatus {
                success: false,
                code,
                error,
            },
            ..Default::default()
        }
    }
}

static CHILDREN: LazyLock<Mutex<HashMap<String, Arc<SharedChild>>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
static UUID: AtomicU16 = AtomicU16::new(0);

pub async fn spawn(option: SpawnOption) -> Result<Output, Output> {
    let mut command = Command::new(option.program);
    if let Some(args) = option.args {
        command.args(args);
    }
    command.stdout(Stdio::piped());
    command.stderr(Stdio::piped());

    #[cfg(windows)]
    command.creation_flags(CREATE_NO_WINDOW.0);

    let token = if let Some(token) = option.cancellation_token {
        token
    } else {
        UUID.fetch_add(1, Ordering::Relaxed).to_string()
    };

    let child = SharedChild::spawn(&mut command).map_err(|e| Output::error(e.raw_os_error(), Some(e.to_string())))?;
    {
        CHILDREN.lock().unwrap().insert(token.clone(), Arc::new(child));
    }

    smol::spawn(async move {
        let mut children = CHILDREN.lock().unwrap();
        let child = children.get(&token).unwrap();
        match child.wait() {
            Ok(exit_status) => {
                let stdout = if let Some(mut out) = child.take_stdout() {
                    let mut buf = String::new();
                    out.read_to_string(&mut buf).map_err(|e| Output::error(e.raw_os_error(), Some(e.to_string())))?;
                    buf
                } else {
                    String::new()
                };

                let stderr = if let Some(mut out) = child.take_stderr() {
                    let mut buf = String::new();
                    out.read_to_string(&mut buf).map_err(|e| Output::error(e.raw_os_error(), Some(e.to_string())))?;
                    buf
                } else {
                    String::new()
                };

                children.remove(&token);

                let result = Output {
                    status: CommandStatus {
                        success: exit_status.success(),
                        code: exit_status.code(),
                        error: None,
                    },
                    stderr,
                    stdout,
                };

                if exit_status.success() {
                    Ok(result)
                } else {
                    Err(result)
                }
            }
            Err(e) => Err(Output {
                status: CommandStatus {
                    success: false,
                    code: e.raw_os_error(),
                    error: Some(e.to_string()),
                },
                stderr: String::new(),
                stdout: String::new(),
            }),
        }
    })
    .await
}

pub fn kill(cancellation_token: String) -> Result<(), String> {
    if let Ok(mut children) = CHILDREN.try_lock() {
        if children.contains_key(&cancellation_token) {
            children.get_mut(&cancellation_token).unwrap().kill().map_err(|e| e.to_string())?;
            children.remove(&cancellation_token);
        }
    }

    Ok(())
}

pub fn clear() {
    let children = {
        let mut lock = CHILDREN.lock().unwrap();
        std::mem::take(&mut *lock)
    };
    for child in children.into_values() {
        let _ = child.kill();
    }
}