mdx-gen 0.0.4

A robust Rust library for processing Markdown and converting it to HTML with support for custom blocks, enhanced table formatting, and flexible configuration options.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2026 MDX Gen. All rights reserved.

//! Shared display helpers for mdx-gen examples.
//!
//! Provides animated spinner + checkmark output inspired by
//! charmbracelet/bubbletea's package-manager example.

#![allow(dead_code)]

use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

const SPINNER: &[&str] = &[
    "\u{2807}", "\u{280b}", "\u{2819}", "\u{2838}", "\u{2834}",
    "\u{2826}", "\u{2847}", "\u{280f}",
];
const CHECK: &str = "\u{2713}";
const CROSS: &str = "\u{2717}";

/// Print the example header.
pub(crate) fn header(title: &str) {
    println!();
    println!("  \x1b[1m{title}\x1b[0m");
    println!();
}

/// Print the footer.
pub(crate) fn footer() {
    println!();
}

fn spin(label: &str, done: &Arc<AtomicBool>) {
    let mut i = 0;
    while !done.load(Ordering::Relaxed) {
        let frame = SPINNER[i % SPINNER.len()];
        print!(
            "\r  \x1b[36m{frame}\x1b[0m \x1b[90m{label}\x1b[0m\x1b[K"
        );
        let _ = io::stdout().flush();
        thread::sleep(Duration::from_millis(80));
        i += 1;
    }
}

/// Run a task with an animated spinner, then show a checkmark.
///
/// The closure must NOT print to stdout — capture results and use
/// [`task_with_output`] instead if output is needed.
pub(crate) fn task<F, R>(label: &str, f: F) -> R
where
    F: FnOnce() -> R,
{
    let done = Arc::new(AtomicBool::new(false));
    let done_clone = done.clone();
    let label_owned = label.to_string();

    let handle = thread::spawn(move || spin(&label_owned, &done_clone));

    let result = f();
    done.store(true, Ordering::Relaxed);
    let _ = handle.join();
    print!("\r  \x1b[32m{CHECK}\x1b[0m {label}\x1b[K\n");
    let _ = io::stdout().flush();

    result
}

/// Run a task, then print additional detail lines beneath the checkmark.
pub(crate) fn task_with_output<F>(label: &str, f: F)
where
    F: FnOnce() -> Vec<String>,
{
    let done = Arc::new(AtomicBool::new(false));
    let done_clone = done.clone();
    let label_owned = label.to_string();

    let handle = thread::spawn(move || spin(&label_owned, &done_clone));

    let lines = f();
    done.store(true, Ordering::Relaxed);
    let _ = handle.join();
    print!("\r  \x1b[32m{CHECK}\x1b[0m {label}\x1b[K\n");
    for line in &lines {
        println!("    \x1b[90m{line}\x1b[0m");
    }
    let _ = io::stdout().flush();
}

/// Run a task that may fail. Shows checkmark on success, cross on failure.
pub(crate) fn task_result<F, T, E>(label: &str, f: F) -> Result<T, E>
where
    F: FnOnce() -> Result<T, E>,
    E: std::fmt::Display,
{
    let done = Arc::new(AtomicBool::new(false));
    let done_clone = done.clone();
    let label_owned = label.to_string();

    let handle = thread::spawn(move || spin(&label_owned, &done_clone));

    let result = f();
    done.store(true, Ordering::Relaxed);
    let _ = handle.join();

    match &result {
        Ok(_) => {
            print!("\r  \x1b[32m{CHECK}\x1b[0m {label}\x1b[K\n")
        }
        Err(e) => print!(
            "\r  \x1b[31m{CROSS}\x1b[0m {label} \x1b[90m({e})\x1b[0m\x1b[K\n"
        ),
    }
    let _ = io::stdout().flush();

    result
}

/// Show a summary line.
pub(crate) fn summary(count: usize) {
    println!();
    println!(
        "  \x1b[1;32m{CHECK} {count} operations completed.\x1b[0m"
    );
    println!();
}