use std::io::{self, IsTerminal, Read, Write};
use tape::{render, sigil, Chunk, ReadResult, Reader};
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));
}
}
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())));
}
}
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)));
}
}
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))
}
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();
}
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);
}
}
}