rbx_mantle 0.10.6

Infra-as-code for Roblox
Documentation
use std::{
    fmt::Display,
    panic,
    sync::atomic::{AtomicU16, Ordering},
};

use difference::{Changeset, Difference};
use yansi::{Color, Paint, Style};

const SPACING: &str = "  ";

static ACTION_COUNT: AtomicU16 = AtomicU16::new(0);

fn with_prefix_and_style<S1, S2>(text: S1, prefix: S2, style: Style) -> String
where
    S1: Display,
    S2: Display,
{
    text.to_string()
        .split('\n')
        .map(|line| {
            format!(
                "{}{}",
                prefix.to_string(),
                Paint::new(line).with_style(style)
            )
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn with_prefix<S1, S2>(text: S1, prefix: S2) -> String
where
    S1: Display,
    S2: Display,
{
    with_prefix_and_style(text, prefix, Style::default())
}

fn get_line_prefix() -> String {
    format!("{0}{0}", SPACING).repeat(ACTION_COUNT.load(Ordering::SeqCst).into())
}

pub fn log<S>(message: S)
where
    S: Display,
{
    let line_prefix = get_line_prefix();
    println!("{}", with_prefix(&message, &line_prefix));
}

pub fn start_action<S>(title: S)
where
    S: Display,
{
    log(title);
    log("");
    ACTION_COUNT.fetch_add(1, Ordering::SeqCst);
}

fn end_action_internal<S>(message: Option<S>, results: Option<Changeset>)
where
    S: Display,
{
    if ACTION_COUNT.load(Ordering::SeqCst) == 0 {
        panic!("Attempted to end an action that was not started.");
    }

    log("");
    ACTION_COUNT.fetch_sub(1, Ordering::SeqCst);

    if let Some(message) = message {
        log(&format!("{}╰─ {}", SPACING, message));
    } else {
        log(&format!("{}╰──○", SPACING));
    }

    if let Some(results) = results {
        log_changeset_with_prefix(results, format!("{0}{0} ", SPACING));
    }

    log("");
}

pub fn end_action<S>(message: S)
where
    S: Display,
{
    end_action_internal(Some(message), None);
}

pub fn end_action_with_results<S>(message: S, results: Changeset)
where
    S: Display,
{
    end_action_internal(Some(message), Some(results));
}

pub fn end_action_without_message() {
    end_action_internal(None::<String>, None);
}

pub fn log_changeset_with_prefix<S>(changeset: Changeset, prefix: S)
where
    S: Display,
{
    log(&changeset
        .diffs
        .iter()
        .map(|diff| match diff {
            Difference::Same(same) => with_prefix_and_style(
                same,
                format!("{}{}{}", prefix, SPACING, SPACING),
                Style::default().dimmed(),
            ),
            Difference::Add(add) => with_prefix_and_style(
                add,
                &format!("{}{}{} ", prefix, SPACING, Paint::green("+")),
                Style::new(Color::Green),
            ),
            Difference::Rem(rem) => with_prefix_and_style(
                rem,
                &format!("{}{}{} ", prefix, SPACING, Paint::red("-")),
                Style::new(Color::Red),
            ),
        })
        .collect::<Vec<String>>()
        .join(&changeset.split));
}

pub fn log_changeset(changeset: Changeset) {
    log_changeset_with_prefix(changeset, "");
}