cargo-port 0.4.0

A TUI for inspecting and managing Rust projects
use std::process::Command;
use std::process::Output;

use crate::constants::SCCACHE_BINARY;
use crate::constants::SCCACHE_STATS_ARG;

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatsResult {
    Ready(Vec<String>),
    Failed(Vec<String>),
}

pub(crate) fn read_stats() -> StatsResult {
    Command::new(SCCACHE_BINARY)
        .arg(SCCACHE_STATS_ARG)
        .output()
        .map_or_else(
            |err| StatsResult::Failed(vec![format!("Unable to run sccache: {err}")]),
            |output| stats_from_output(&output),
        )
}

fn stats_from_output(output: &Output) -> StatsResult {
    let lines = output_lines(output);
    if output.status.success() {
        return StatsResult::Ready(non_empty_lines(
            lines,
            "sccache returned no stats".to_string(),
        ));
    }
    let code = output
        .status
        .code()
        .map_or_else(|| "signal".to_string(), |code| code.to_string());
    let mut failed = vec![format!("sccache --show-stats failed with status {code}")];
    failed.extend(lines);
    StatsResult::Failed(failed)
}

fn output_lines(output: &Output) -> Vec<String> {
    let mut lines = text_lines(&output.stdout);
    lines.extend(text_lines(&output.stderr));
    lines
}

fn text_lines(bytes: &[u8]) -> Vec<String> {
    String::from_utf8_lossy(bytes)
        .lines()
        .map(str::trim_end)
        .filter(|line| !line.is_empty())
        .map(str::to_string)
        .collect()
}

fn non_empty_lines(lines: Vec<String>, fallback: String) -> Vec<String> {
    if lines.is_empty() {
        vec![fallback]
    } else {
        lines
    }
}