wsb 0.258.6441

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
use crate::fleet::git::SubmoduleStatus;
use colored::*;

pub struct Formatter;

impl Formatter {
    pub fn print_status(statuses: &[SubmoduleStatus]) {
        crate::outln!("\n{}", "Git Status:".bold().underline());
        crate::outln!();

        for status in statuses {
            let indent = "  ".repeat(status.depth);
            if !status.exists {
                crate::outln!(
                    "  {}{} {} {}",
                    indent,
                    "●".red(),
                    status.name.bold(),
                    "[NOT INITIALIZED]".red()
                );
                continue;
            }

            // Green means nothing to do here; yellow means something needs
            // attention, whether that is local changes, drift from the remote, or
            // a detached HEAD. Which one it is, is spelled out on the lines below.
            let indicator = if status.is_clean && status.ahead == 0 && status.behind == 0 {
                "●".green()
            } else {
                "●".yellow()
            };

            crate::out!("  {}{} {}", indent, indicator, status.name.bold());

            if let Some(ref branch) = status.current_branch {
                crate::out!(" ({})", branch.cyan());
            } else if status.is_detached {
                crate::out!(" {}", "[DETACHED HEAD]".red());
            }

            crate::outln!();

            if !status.is_clean {
                crate::outln!(
                    "    {}{} {} uncommitted file(s)",
                    indent,
                    "!".yellow(),
                    status.uncommitted_files.len()
                );
            }

            // Said, but quietly. These differ from HEAD and a commit here would
            // carry them, so a status that omitted them would misdescribe what
            // this tree contains. They are not what somebody scanning for their
            // own work is looking for, so they do not raise the indicator and
            // do not make a repository unclean.
            if !status.rendered_files.is_empty() {
                crate::outln!(
                    "    {}{}",
                    indent,
                    format!(
                        "~ {} generated file(s) — the local render",
                        status.rendered_files.len()
                    )
                    .dimmed()
                );
            }

            // Likewise said quietly. A container repository is mostly gitlinks,
            // and they move whenever a child's tree does — so in a fleet under
            // `wsb local on` they move all at once, tracking the render rather
            // than anything somebody did.
            if !status.moved_pointers.is_empty() {
                crate::outln!(
                    "    {}{}",
                    indent,
                    format!(
                        "~ {} submodule pointer(s) moved",
                        status.moved_pointers.len()
                    )
                    .dimmed()
                );
            }

            if status.ahead > 0 {
                crate::outln!(
                    "    {}{} {} commit(s) ahead",
                    indent,
                    "↑".green(),
                    status.ahead
                );
            }

            if status.behind > 0 {
                crate::outln!(
                    "    {}{} {} commit(s) behind",
                    indent,
                    "↓".red(),
                    status.behind
                );
            }
        }

        crate::outln!();
    }

    pub fn print_success(message: &str) {
        crate::outln!("{} {}", "✓".green(), message);
    }

    pub fn print_error(message: &str) {
        crate::errln!("{} {}", "✗".red(), message);
    }

    pub fn print_warning(message: &str) {
        crate::outln!("{} {}", "!".yellow(), message);
    }

    pub fn print_info(message: &str) {
        crate::outln!("{} {}", "ℹ".cyan(), message);
    }

    pub fn print_submodule_header(name: &str) {
        crate::outln!("\n{} {}...", "→".cyan(), name.bold());
    }

    pub fn print_summary(successful: usize, failed: usize, skipped: usize) {
        crate::outln!("\n{}", "Summary:".bold().underline());
        crate::outln!(
            "  {} successful, {} failed, {} skipped",
            successful.to_string().green(),
            failed.to_string().red(),
            skipped.to_string().yellow()
        );
    }

    pub fn print_branches(submodule_name: &str, branches: &[String], current: Option<&str>) {
        crate::outln!("\n{}", submodule_name.bold());
        for branch in branches {
            if Some(branch.as_str()) == current {
                crate::outln!("  {} {}", "*".green(), branch.green());
            } else {
                crate::outln!("    {}", branch);
            }
        }
    }
}