// ---
// tags: tape, rust, cli
// crystal-type: source
// crystal-domain: comp
// ---
//! tape โ€” inspect and mint cyb-stream frames.
//!
//!   tape inspect [file]              decode a tape stream into a frame table
//!   tape sigils                      the 13 sigils (the frame's type)
//!   tape renders                     the 15 render kinds (how to show a payload)
//!   tape make <sigil> <render> <s>   emit one frame to stdout
//!
//! A tape frame is `MARKER(0x1F) ยท sigil ยท render ยท varint(len) ยท payload`. Every
//! durable log and every gossiped message in the stack is a run of these, so
//! `tape inspect` is the debugger for the wire.

use std::io::{self, IsTerminal, Read, Write};

use tape::{render, sigil, Chunk, ReadResult, Reader};

// โ”€โ”€ color โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn tty() -> bool {
    io::stdout().is_terminal()
}
fn paint(code: &str, s: &str) -> String {
    if tty() { format!("\x1b[{code}m{s}\x1b[0m") } else { s.to_string() }
}
fn dim(s: &str) -> String {
    paint("90", s)
}
fn cyan(s: &str) -> String {
    paint("36", s)
}
fn green(s: &str) -> String {
    paint("32", s)
}
fn yellow(s: &str) -> String {
    paint("33", s)
}
fn bold(s: &str) -> String {
    paint("1", s)
}
fn red(s: &str) -> String {
    paint("31", s)
}

const LOGO: &str = "\
\x1b[31mโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—\x1b[0m
\x1b[33mโ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•\x1b[0m
\x1b[32m   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  \x1b[0m
\x1b[36m   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•  \x1b[0m
\x1b[34m   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—\x1b[0m
\x1b[35m   โ•šโ•โ•   โ•šโ•โ•  โ•šโ•โ•โ•šโ•โ•     โ•šโ•โ•โ•โ•โ•โ•โ•\x1b[0m";

fn banner() {
    if !tty() {
        return;
    }
    println!("{LOGO}");
    println!("{}", paint("37", "    the wire โ€” self-describing frames"));
    println!("{}", dim("\n    MARKER 0x1F ยท sigil ยท render ยท varint ยท payload\n    13 sigils ยท 15 renders\n"));
}

fn help() {
    banner();
    let rows = [
        ("inspect [file]", "decode a tape stream into a frame table (stdin if no file)"),
        ("sigils", "the 13 sigils โ€” a frame's type"),
        ("renders", "the 15 render kinds โ€” how to show a payload"),
        ("make <sigil> <render> <s>", "emit one frame to stdout"),
    ];
    let w = rows.iter().map(|(c, _)| c.len()).max().unwrap_or(0);
    println!("{}", dim("commands"));
    for (cmd, desc) in rows {
        println!("  {}   {}", bold(&format!("{cmd:<w$}")), dim(desc));
    }
}

// โ”€โ”€ inspect โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// A short, human preview of a payload: text renders as a quoted string, the
/// rest as a hex prefix.
fn preview(c: &Chunk) -> String {
    let textual = matches!(c.render, render::TEXT | render::LOG | render::ERROR | render::INPUT);
    if textual {
        let s: String =
            String::from_utf8_lossy(&c.payload).chars().take(56).collect::<String>().replace('\n', "โŽ");
        format!("\"{s}\"")
    } else if c.payload.is_empty() {
        "ยท".into()
    } else {
        let hex: String = c.payload.iter().take(12).map(|b| format!("{b:02x}")).collect();
        format!("{hex}{}", if c.payload.len() > 12 { "โ€ฆ" } else { "" })
    }
}

fn cmd_inspect(path: Option<&str>) {
    let mut bytes = Vec::new();
    let read = match path {
        Some(p) => std::fs::File::open(p).and_then(|mut f| f.read_to_end(&mut bytes)),
        None => io::stdin().read_to_end(&mut bytes),
    };
    if let Err(e) = read {
        eprintln!("  {}: {}", red("error"), e);
        std::process::exit(1);
    }

    let mut reader = Reader::new();
    reader.feed(&bytes);
    println!(
        "  {}   {}  {}  {}  {}",
        dim("#"),
        dim(&format!("{:<10}", "sigil")),
        dim(&format!("{:<12}", "render")),
        dim(&format!("{:>5}", "bytes")),
        dim("payload"),
    );
    let mut n = 0u64;
    loop {
        match reader.next_chunk() {
            ReadResult::Chunk(c) => {
                let sig = format!("{} {}", c.sigil as char, sigil::name(c.sigil));
                let ren = format!("{} {}", c.render as char, render::name(c.render));
                println!(
                    "  {:>2}   {}  {}  {}  {}",
                    yellow(&n.to_string()),
                    cyan(&format!("{sig:<10}")),
                    green(&format!("{ren:<12}")),
                    &format!("{:>5}", c.payload.len()),
                    dim(&preview(&c)),
                );
                n += 1;
            }
            ReadResult::Pending | ReadResult::Eof => break,
        }
    }
    if n == 0 {
        println!("  {}", dim("(no frames)"));
    } else {
        println!("  {}", dim(&format!("{n} frame(s), {} bytes", bytes.len())));
    }
}

// โ”€โ”€ reference tables โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn cmd_sigils() {
    println!("{}", dim("sigils โ€” a frame's type"));
    for b in sigil::ALL {
        println!("  {}  {}", cyan(&format!("{}  0x{:02X}", b as char, b)), bold(sigil::name(b)));
    }
}

fn cmd_renders() {
    println!("{}", dim("renders โ€” how to show a payload"));
    for b in render::ALL {
        println!("  {}  {}", cyan(&format!("{}  0x{:02X}", b as char, b)), bold(render::name(b)));
    }
}

// โ”€โ”€ make โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Resolve a sigil from a single char or a name (`!` or `zap`).
fn sigil_byte(s: &str) -> Option<u8> {
    if s.len() == 1 && sigil::is_valid(s.as_bytes()[0]) {
        return Some(s.as_bytes()[0]);
    }
    sigil::ALL.into_iter().find(|&b| sigil::name(b).eq_ignore_ascii_case(s))
}

/// Resolve a render from a single char or a name (`t` or `text`).
fn render_byte(s: &str) -> Option<u8> {
    if s.len() == 1 && render::is_valid(s.as_bytes()[0]) {
        return Some(s.as_bytes()[0]);
    }
    render::ALL.into_iter().find(|&b| render::name(b).eq_ignore_ascii_case(s))
}

fn cmd_make(args: &[String]) {
    let (Some(s), Some(r)) = (args.first(), args.get(1)) else {
        eprintln!("  {}: tape make <sigil> <render> [payload]", dim("usage"));
        std::process::exit(2);
    };
    let Some(sig) = sigil_byte(s) else {
        eprintln!("  {}: unknown sigil '{s}' (try `tape sigils`)", red("error"));
        std::process::exit(2);
    };
    let Some(ren) = render_byte(r) else {
        eprintln!("  {}: unknown render '{r}' (try `tape renders`)", red("error"));
        std::process::exit(2);
    };
    let payload = args.get(2).cloned().unwrap_or_default();
    let frame = Chunk::new(sig, ren, payload.into_bytes().into()).encode();
    io::stdout().write_all(&frame).ok();
}

// โ”€โ”€ main โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    match args.first().map(String::as_str) {
        Some("inspect" | "read" | "cat") => cmd_inspect(args.get(1).map(String::as_str)),
        Some("sigils") => cmd_sigils(),
        Some("renders") => cmd_renders(),
        Some("make" | "encode") => cmd_make(&args[1..]),
        Some("help" | "--help" | "-h") | None => help(),
        Some(other) => {
            eprintln!("  {}: {other}  (try: tape help)", dim("unknown"));
            std::process::exit(2);
        }
    }
}

Homonyms

cyb/honeycrisp/unimem/src/tape.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/src/tape.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/src/tape.rs

Graph