owlgo 0.1.7

A lightweight CLI to assist in solving CP problems
use crate::common::{OwlError, Result};
use std::io::{BufReader, Read, Write};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

pub fn bat_file(path: &Path) -> Result<()> {
    if !path.exists() {
        return Err(OwlError::FileError(
            format!("'{}': no such file", path.to_string_lossy()),
            "".into(),
        ));
    }

    if path.is_dir() {
        return Err(OwlError::ProcessError(
            format!("Failed to bat dir '{}'", path.to_string_lossy()),
            "cannot bat a dir".into(),
        ));
    }

    let mut child = Command::new("bat")
        .arg(path)
        .spawn()
        .map_err(|e| OwlError::ProcessError("[bat] failed to spawn".into(), e.to_string()))?;

    let status = child
        .wait()
        .map_err(|e| OwlError::ProcessError("[bat] not running".into(), e.to_string()))?;

    if status.success() {
        Ok(())
    } else {
        Err(OwlError::ProcessError(
            format!("Failed to bat file '{}'", path.to_string_lossy()),
            "status failed".into(),
        ))
    }
}

pub fn glow_file(path: &Path) -> Result<()> {
    if !path.exists() {
        return Err(OwlError::FileError(
            format!("'{}': no such file", path.to_string_lossy()),
            "".into(),
        ));
    }

    if path.is_dir() {
        return Err(OwlError::ProcessError(
            format!("Failed to glow dir '{}'", path.to_string_lossy()),
            "cannot glow a dir".into(),
        ));
    }

    let mut child = Command::new("glow")
        .arg(path)
        .spawn()
        .map_err(|e| OwlError::ProcessError("[glow] failed to spawn".into(), e.to_string()))?;

    let status = child
        .wait()
        .map_err(|e| OwlError::ProcessError("[glow] not running".into(), e.to_string()))?;

    if status.success() {
        Ok(())
    } else {
        Err(OwlError::ProcessError(
            format!("Failed to glow file '{}'", path.to_string_lossy()),
            "status failed".into(),
        ))
    }
}

pub fn run_binary(exe: &Path) -> Result<(String, Duration)> {
    let exe_str = exe.to_str().ok_or(OwlError::UriError(
        "Invalid binary file URI".into(),
        "None".into(),
    ))?;

    run_cmd("./binary", Command::new(format!("./{}", exe_str)))
}

pub fn run_binary_with_stdin(exe: &Path, input: &str) -> Result<(String, Duration)> {
    let exe_str = exe.to_str().ok_or(OwlError::UriError(
        "Invalid binary file URI".into(),
        "None".into(),
    ))?;

    run_cmd_with_stdin("./binary", Command::new(format!("./{}", exe_str)), input)
}

pub fn run_cmd(cmd_tag: &'static str, mut cmd: Command) -> Result<(String, Duration)> {
    let start = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("[run_cmd::start_time] unreachable");

    let child = cmd
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| {
            OwlError::ProcessError(format!("[{}] failed to spawn", cmd_tag), e.to_string())
        })?;

    stdout_else_stderr(cmd_tag, child).map(|stdout| {
        let stop = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("[run_cmd::stop_time] unreachable");

        (stdout, stop - start)
    })
}

pub fn run_cmd_with_stdin(
    cmd_tag: &'static str,
    mut cmd: Command,
    input: &str,
) -> Result<(String, Duration)> {
    let start = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("[run_cmd_with_stdin::start_time] unreachable");

    let mut child = cmd
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| {
            OwlError::ProcessError(format!("[{}] failed to spawn", cmd_tag), e.to_string())
        })?;

    let mut stdin = child.stdin.take().expect("[stdin handle] unreachable");
    let write_result = stdin.write_all(input.as_bytes()).map_err(|e| {
        OwlError::FileError(
            "Failed not write to stdin of child process".into(),
            e.to_string(),
        )
    });

    if let Err(e) = write_result {
        child.wait().map_err(|e| {
            OwlError::ProcessError(format!("[{}] not running", cmd_tag), e.to_string())
        })?;

        return Err(e);
    }

    stdout_else_stderr(cmd_tag, child).map(|stdout| {
        let stop = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("[run_cmd_with_stdin::stop_time] unreachable");

        (stdout, stop - start)
    })
}

pub fn stderr_only(cmd_tag: &'static str, mut child: Child) -> Result<String> {
    let stderr_pipe = child.stderr.take().expect("[stderr handle] unreachable");

    let status = child
        .wait()
        .map_err(|e| OwlError::ProcessError(format!("[{}] not running", cmd_tag), e.to_string()))?;

    let mut buffer = String::new();

    let mut reader = BufReader::new(stderr_pipe);
    reader.read_to_string(&mut buffer).map_err(|e| {
        OwlError::FileError(
            format!("'{}': failed to read stderr", cmd_tag),
            e.to_string(),
        )
    })?;

    if status.success() {
        Ok(buffer)
    } else {
        buffer.push_str("(run program manually for stack trace)");

        Err(OwlError::ProcessError(
            format!("'{}': exit with status failed", cmd_tag),
            buffer,
        ))
    }
}

pub fn stdout_else_stderr(cmd_tag: &'static str, mut child: Child) -> Result<String> {
    let stdout_pipe = child.stdout.take().expect("[stdout handle] unreachable");
    let stderr_pipe = child.stderr.take().expect("[stderr handle] unreachable");

    let status = child
        .wait()
        .map_err(|e| OwlError::ProcessError(format!("[{}] not running", cmd_tag), e.to_string()))?;

    if status.success() {
        let mut buffer = String::new();

        let mut reader = BufReader::new(stdout_pipe);
        reader.read_to_string(&mut buffer).map_err(|e| {
            OwlError::FileError(
                format!("'{}': failed to read stdout", cmd_tag),
                e.to_string(),
            )
        })?;

        Ok(buffer)
    } else {
        let mut buffer = String::new();

        let mut reader = BufReader::new(stderr_pipe);
        reader.read_to_string(&mut buffer).map_err(|e| {
            OwlError::FileError(
                format!("'{}': failed to read stderr", cmd_tag),
                e.to_string(),
            )
        })?;
        buffer.push_str("(run program manually for stack trace)");

        Err(OwlError::ProcessError(
            format!("'{}': exit with status failed", cmd_tag),
            buffer,
        ))
    }
}

pub fn tree_dir(dir: &Path) -> Result<()> {
    let mut child = Command::new("tree")
        .args(["-a", "-s", "-h", "--du", "-I", ".git"])
        .arg(dir)
        .spawn()
        .map_err(|e| OwlError::ProcessError("[tree] failed to spawn".into(), e.to_string()))?;

    let status = child
        .wait()
        .map_err(|e| OwlError::ProcessError("[tree] not running".into(), e.to_string()))?;
    if status.success() {
        Ok(())
    } else {
        Err(OwlError::ProcessError(
            format!("Failed to tree dir '{}'", dir.to_string_lossy()),
            "status failed".into(),
        ))
    }
}