git-bra 0.4.0

A Git worktree manager with project-aware configuration.
Documentation
use std::io::{self, IsTerminal};
use std::path::Path;

use anyhow::Error;
use console::style;

pub fn print_path(path: &Path) {
    println!("{}", path.display());
}

pub fn print_success(message: &str) {
    println!(
        "{}",
        style_stdout(message, |text| style(text).green().bold().to_string())
    );
}

pub fn print_info(message: &str) {
    println!(
        "{}",
        style_stdout(message, |text| style(text).cyan().to_string())
    );
}

pub fn print_section(message: &str) {
    println!(
        "{}",
        style_stdout(message, |text| style(text).green().bold().to_string())
    );
}

pub fn print_list_entry(prefix: &str, message: &str, current: bool) {
    let marker = if current {
        style_stdout(prefix, |text| style(text).green().bold().to_string())
    } else {
        prefix.to_owned()
    };
    println!("{marker} {message}");
}

pub fn print_error(error: &Error) {
    let mut chain = error.chain();
    let Some(first) = chain.next() else {
        return;
    };

    let first_message = clean_message(&first.to_string());
    eprintln!(
        "{} {}",
        style_stderr("error:", |text| style(text).red().bold().to_string()),
        first_message
    );

    let mut previous = first_message;
    for cause in chain {
        let message = clean_message(&cause.to_string());
        if message.is_empty() || message == previous {
            continue;
        }
        eprintln!(
            "  {} {}",
            style_stderr("caused by:", |text| style(text).yellow().to_string()),
            message
        );
        previous = message;
    }
}

pub fn clean_message(message: &str) -> String {
    let mut current = message.trim();

    loop {
        let lowered = current.to_ascii_lowercase();
        if let Some(rest) = lowered.strip_prefix("error:") {
            let index = current.len() - rest.len();
            current = current[index..].trim();
            continue;
        }
        if let Some(rest) = lowered.strip_prefix("fatal:") {
            let index = current.len() - rest.len();
            current = current[index..].trim();
            continue;
        }
        break;
    }

    current.to_owned()
}

fn style_stdout<F>(message: &str, style: F) -> String
where
    F: FnOnce(&str) -> String,
{
    if io::stdout().is_terminal() {
        style(message)
    } else {
        message.to_owned()
    }
}

fn style_stderr<F>(message: &str, style: F) -> String
where
    F: FnOnce(&str) -> String,
{
    if io::stderr().is_terminal() {
        style(message)
    } else {
        message.to_owned()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn removes_error_prefixes() {
        assert_eq!(
            clean_message("error: No such remote 'origin'"),
            "No such remote 'origin'"
        );
        assert_eq!(clean_message("fatal: bad revision"), "bad revision");
        assert_eq!(clean_message("error: fatal: bad revision"), "bad revision");
    }
}