aurora-modules 0.1.0

Git, filesystem, system, network, archive, docker, unix, crypto, calculator, QR, color, timer, notes, clipboard, and text processing modules
Documentation
use aurora_core::{AuroraResult, Pipeline, Value, AuroraError};
use std::process::Command;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::Write;

fn find_tool(name: &str) -> bool {
    Command::new("which")
        .arg(name)
        .output()
        .ok()
        .is_some_and(|o| o.status.success())
}

pub fn crypto_hash(input: &str, algo: Option<&str>) -> AuroraResult<Pipeline> {
    let algo_name = algo.unwrap_or("sha256");
    let is_file = std::path::Path::new(input).exists() && std::path::Path::new(input).is_file();

    match algo_name {
        "sha256" => {
            if is_file && find_tool("sha256sum") {
                hash_file_cmd("sha256sum", input, "sha256")
            } else if is_file && find_tool("shasum") {
                hash_file_cmd("shasum", input, "sha256")
            } else {
                hash_string_cmd("sha256sum", input, "sha256")
            }
        }
        "md5" => {
            if is_file && find_tool("md5sum") {
                hash_file_cmd("md5sum", input, "md5")
            } else if find_tool("md5sum") {
                hash_string_cmd("md5sum", input, "md5")
            } else if find_tool("shasum") {
                hash_string_cmd("shasum", input, "md5")
            } else {
                return Err(AuroraError::ModuleError(
                    "md5 requires md5sum or shasum command".into()
                ));
            }
        }
        _ => {
            if find_tool(algo_name) {
                hash_string_cmd(algo_name, input, algo_name)
            } else {
                return Err(AuroraError::InvalidInput(
                    format!("unknown algorithm: {algo_name}")
                ));
            }
        }
    }
}

fn hash_file_cmd(tool: &str, path: &str, algo: &str) -> AuroraResult<Pipeline> {
    let output = Command::new(tool)
        .arg(path)
        .output()
        .map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let hash = stdout.split_whitespace().next().unwrap_or("").to_string();

    Ok(Pipeline::table(
        vec!["algorithm".into(), "hash".into()],
        vec![vec![Value::String(algo.into()), Value::String(hash)]],
    ))
}

fn hash_string_cmd(tool: &str, input: &str, algo: &str) -> AuroraResult<Pipeline> {
    let mut child = Command::new(tool)
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::null())
        .spawn()
        .map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;

    if let Some(ref mut stdin) = child.stdin {
        stdin.write_all(input.as_bytes())
            .map_err(|e| AuroraError::ModuleError(format!("Write failed: {e}")))?;
    }
    drop(child.stdin.take());

    let output = child.wait_with_output()
        .map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;
    let hash = String::from_utf8_lossy(&output.stdout)
        .split_whitespace()
        .next()
        .unwrap_or("")
        .to_string();

    Ok(Pipeline::table(
        vec!["algorithm".into(), "hash".into()],
        vec![vec![Value::String(algo.into()), Value::String(hash)]],
    ))
}

pub fn crypto_genkey(algo: Option<&str>) -> AuroraResult<Pipeline> {
    let algo_name = algo.unwrap_or("aes-256");
    use std::time::{SystemTime, UNIX_EPOCH};

    let seed = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(0);

    let mut hasher = DefaultHasher::new();
    seed.hash(&mut hasher);
    let hash = hasher.finish();

    let key = format!("{:016x}{:016x}", hash, hash);

    Ok(Pipeline::table(
        vec!["algorithm".into(), "key".into()],
        vec![vec![Value::String(algo_name.into()), Value::String(key)]],
    ))
}