super-tool 0.13.1

The Super CLI for verifying software behavior
use super::{
    Results, presentation,
    style::{Ink, Line},
};
use crate::cancel::{Entry, Outcome};

fn label(outcome: Outcome) -> (&'static str, Ink) {
    match outcome {
        Outcome::Canceled => ("canceled", Ink::Muted),
        Outcome::AlreadyCanceled => ("already canceled", Ink::Muted),
        Outcome::AlreadyFinished => ("already finished", Ink::Muted),
        Outcome::BackgroundStopped => ("background work stopped", Ink::Muted),
        Outcome::Finished => ("finished before cancellation", Ink::Muted),
        Outcome::Requested => ("cancellation requested", Ink::Accent),
        Outcome::Unconfirmed => ("cancellation not confirmed", Ink::Warning),
    }
}

fn row_status(entry: &Entry, check: &super_types::Check) -> Line {
    match entry.outcome {
        Outcome::Requested | Outcome::Unconfirmed => {
            let (text, ink) = label(entry.outcome);
            Line::default().push(text, ink, false)
        }
        _ => presentation::status(check).line(),
    }
}

pub(super) fn render(results: &Results, entries: &[Entry], width: usize) -> Vec<Line> {
    let checks: Vec<_> = entries
        .iter()
        .filter_map(|entry| {
            results
                .rows
                .iter()
                .filter_map(|row| row.current.as_ref())
                .find(|check| check.number == entry.check_number)
                .map(|check| (entry, check))
        })
        .collect();
    let mut lines = match checks.as_slice() {
        [] => vec![Line::default().push(
            "! Nothing to cancel · no checks in this selection.",
            Ink::Warning,
            false,
        )],
        [(entry, check)] => single(results, entry, check),
        _ => multiple(results, entries, &checks, width),
    };
    for (entry, _) in &checks {
        if let Some(error) = &entry.error {
            lines.extend([
                Line::default(),
                Line::default().push(format!("! Check #{}: {error}", entry.check_number), Ink::Warning, false),
            ]);
        }
    }
    if entries
        .iter()
        .any(|entry| matches!(entry.outcome, Outcome::Requested | Outcome::Unconfirmed))
    {
        lines.push(Line::default());
        lines.push(
            Line::default()
                .muted("Watch: ")
                .push(format!("{} --watch", results.status_command()), Ink::Accent, true)
                .soft_wrap(),
        );
    }
    lines
}

fn single(results: &Results, entry: &Entry, check: &super_types::Check) -> Vec<Line> {
    let mut lines = Vec::new();
    let (label, ink) = label(entry.outcome);
    match entry.outcome {
        Outcome::AlreadyFinished | Outcome::AlreadyCanceled => lines.push(
            Line::default()
                .push("! ", Ink::Warning, false)
                .plain(format!("Nothing to cancel · check #{} is {label}.", check.number)),
        ),
        Outcome::Canceled => lines.push(presentation::canceled_line(check)),
        _ => {
            let mut chars = label.chars();
            let title = format!("{}{}", chars.next().unwrap().to_uppercase(), chars.as_str());
            lines.push(
                Line::default()
                    .push(title, ink, false)
                    .plain(" · ")
                    .bold(&check.supertest.name)
                    .muted(format!(" · #{}", check.number)),
            );
            if entry.outcome == Outcome::BackgroundStopped {
                lines.push(Line::default().muted("Result retained · ").push(
                    presentation::status(check).label,
                    presentation::status(check).ink,
                    false,
                ));
            } else if entry.outcome == Outcome::Finished {
                lines.push(presentation::result_line(check, None));
            }
        }
    }
    if entry.outcome == Outcome::Canceled && presentation::simple_cancellation(check) {
        lines.extend([Line::default(), results.recheck(check)]);
    } else if matches!(entry.outcome, Outcome::Canceled | Outcome::BackgroundStopped) {
        lines.extend([
            Line::default(),
            Line::default().muted("Details: ").push(
                format!("super status --check {}", check.number),
                Ink::Accent,
                true,
            ),
        ]);
    }
    lines
}

fn multiple(results: &Results, entries: &[Entry], checks: &[(&Entry, &super_types::Check)], width: usize) -> Vec<Line> {
    let mut lines = Vec::new();
    lines.push(Line::default().bold(&results.repository));
    let nothing = entries
        .iter()
        .all(|entry| matches!(entry.outcome, Outcome::AlreadyCanceled | Outcome::AlreadyFinished));
    if nothing {
        lines.push(Line::default().push("! Nothing to cancel.", Ink::Warning, false));
    }
    let mut summary = Line::default();
    for outcome in [
        Outcome::Canceled,
        Outcome::BackgroundStopped,
        Outcome::Requested,
        Outcome::Unconfirmed,
        Outcome::Finished,
        Outcome::AlreadyFinished,
        Outcome::AlreadyCanceled,
    ] {
        let count = entries.iter().filter(|entry| entry.outcome == outcome).count();
        if count > 0 {
            if !summary.parts.is_empty() {
                summary = summary.muted(" · ");
            }
            let (text, ink) = label(outcome);
            summary = summary.push(format!("{count} {text}"), ink, false);
        }
    }
    lines.extend([summary, Line::default()]);
    let status_width = checks
        .iter()
        .map(|(entry, check)| row_status(entry, check).width())
        .max()
        .unwrap_or(6)
        .max(6);
    let number_width = checks
        .iter()
        .map(|(_, check)| format!("#{}", check.number).len())
        .max()
        .unwrap_or(5)
        .max(5);
    let available = width.saturating_sub(4);
    let name_width = checks
        .iter()
        .map(|(_, check)| Line::new(&check.supertest.name).width())
        .max()
        .unwrap_or(9)
        .max(9)
        .min(available.saturating_sub(status_width + number_width + 6))
        .max(9);
    let table = name_width + status_width + number_width + 6 <= available;
    if table {
        lines.push(
            Line::default()
                .muted("SUPERTEST")
                .cell(name_width)
                .muted(" · ")
                .append(Line::default().muted("STATUS").cell(status_width))
                .muted(" · CHECK"),
        );
    }
    let mut last_path = None;
    let multiple_files = checks.first().is_some_and(|(_, first)| {
        checks
            .iter()
            .any(|(_, check)| check.supertest.path != first.supertest.path)
    });
    for (entry, check) in checks {
        if multiple_files && last_path != Some(&check.supertest.path) {
            lines.push(Line::default().muted(&check.supertest.path));
            last_path = Some(&check.supertest.path);
        }
        let status = row_status(entry, check);
        if table {
            lines.push(
                Line::new(&check.supertest.name)
                    .cell(name_width)
                    .muted(" · ")
                    .append(status.cell(status_width))
                    .muted(format!(" · #{}", check.number)),
            );
        } else {
            lines.push(
                Line::default()
                    .bold(&check.supertest.name)
                    .muted(format!(" · #{}", check.number)),
            );
            lines.push(status);
        }
    }
    lines
}