http-handle 0.0.5

A fast and lightweight Rust library for handling HTTP requests and responses.
Documentation
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (c) 2023 - 2026 HTTP Handle

//! Shared display helpers for http-handle examples.
//!
//! Animated spinner + checkmark output. Each example imports this
//! module via `#[path = "support.rs"] mod support;` so the helpers
//! never have to be re-exported through the library crate.

#![allow(dead_code)]

use std::io::{self, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
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 a footer (blank line). Mirrors `noyalib`'s helper for
/// examples that prefer an explicit footer over `summary`.
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 with the error rendered next to the label.
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. Pass the number of operations completed.
pub(crate) fn summary(count: usize) {
    println!();
    println!(
        "  \x1b[1;32m{CHECK} {count} operations completed.\x1b[0m"
    );
    println!();
}