use crate::decoding::parse::parse;
use crate::file_format::{VsfField, VsfHeader};
use crate::themes::Theme;
#[cfg(feature = "spirix")]
use crate::types::Fill;
use crate::types::{EagleTime, EtType, VsfType};
use chrono::{Datelike, Local, Timelike};
use colored::*;
static THEME: std::sync::LazyLock<Theme> = std::sync::LazyLock::new(Theme::dark);
struct Tc(String);
impl Tc {
fn new<S: AsRef<str>>(s: S, r: u8, g: u8, b: u8) -> Self {
Tc(format!("\x1b[38;2;{};{};{}m{}\x1b[0m", r, g, b, s.as_ref()))
}
}
impl std::fmt::Display for Tc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
fn tc<S: AsRef<str>>(s: S, (r, g, b): (u8, u8, u8)) -> Tc {
Tc::new(s, r, g, b)
}
fn trc<S: AsRef<str>>(s: S, (r, g, b): (u8, u8, u8)) -> ColoredString {
s.as_ref().truecolor(r, g, b)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Terminal,
Html,
Plain,
}
#[derive(Debug, Clone, Copy)]
pub enum Colour {
Data,
Type,
Size,
Label,
Punct,
Tree,
Pass,
Fail,
DataBold,
}
#[derive(Debug, Clone, Copy)]
pub struct Styler {
format: OutputFormat,
}
impl Styler {
pub fn new(format: OutputFormat) -> Self {
Self { format }
}
pub fn style(&self, text: &str, colour: Colour) -> String {
match self.format {
OutputFormat::Terminal => self.terminal_style(text, colour),
OutputFormat::Html => self.html_style(text, colour),
OutputFormat::Plain => text.to_string(),
}
}
fn terminal_style(&self, text: &str, colour: Colour) -> String {
let (lr, lg, lb) = THEME.label;
let (pr, pg, pb) = THEME.comment;
let (tr, tg, tb) = THEME.tree;
let (pasr, pasg, pasb) = THEME.pass;
let (failr, failg, failb) = THEME.fail;
let (sr, sg, sb) = THEME.size;
match colour {
Colour::Data => text.white().to_string(),
Colour::DataBold => text.white().bold().to_string(),
Colour::Type => text.cyan().to_string(),
Colour::Size => text.truecolor(sr, sg, sb).to_string(),
Colour::Label => text.truecolor(lr, lg, lb).to_string(),
Colour::Punct => text.truecolor(pr, pg, pb).to_string(),
Colour::Tree => text.truecolor(tr, tg, tb).to_string(),
Colour::Pass => text.truecolor(pasr, pasg, pasb).to_string(),
Colour::Fail => text.truecolor(failr, failg, failb).to_string(),
}
}
fn html_style(&self, text: &str, colour: Colour) -> String {
let css = match colour {
Colour::Data => "color:#ffffff",
Colour::DataBold => "color:#ffffff;font-weight:bold",
Colour::Type => "color:#4ec9b0",
Colour::Size => "color:#c8c864",
Colour::Label => "color:#808080",
Colour::Punct => "color:#646464",
Colour::Tree => "color:#404040",
Colour::Pass => "color:#64dc64",
Colour::Fail => "color:#dc6464",
};
let escaped = text
.replace('&', "&")
.replace('<', "<")
.replace('>', ">");
format!("<span style='{}'>{}</span>", css, escaped)
}
pub fn tree_vert(&self) -> String {
self.style(BOX_VERT, Colour::Tree)
}
pub fn tree_corner(&self) -> String {
self.style(&format!("{}{}", BOX_CORNER, BOX_HORIZ), Colour::Tree)
}
pub fn tree_tee(&self) -> String {
self.style(&format!("{}{}", BOX_TEE, BOX_HORIZ), Colour::Tree)
}
}
pub fn ansi_to_html(input: &str) -> String {
let mut output = String::with_capacity(input.len() * 2);
let mut chars = input.chars().peekable();
let mut in_span = false;
while let Some(c) = chars.next() {
if c == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next(); let mut params = String::new();
while let Some(&pc) = chars.peek() {
if pc.is_ascii_digit() || pc == ';' {
params.push(chars.next().unwrap());
} else {
break;
}
}
if chars.peek() == Some(&'m') {
chars.next();
if in_span {
output.push_str("</span>");
in_span = false;
}
if params == "0" || params.is_empty() {
} else {
let css = parse_sgr_to_css(¶ms);
if !css.is_empty() {
output.push_str(&format!("<span style='{}'>", css));
in_span = true;
}
}
}
}
} else {
match c {
'<' => output.push_str("<"),
'>' => output.push_str(">"),
'&' => output.push_str("&"),
'\n' => {
if in_span {
output.push_str("</span>");
}
output.push('\n');
if in_span {
in_span = false;
}
}
_ => output.push(c),
}
}
}
if in_span {
output.push_str("</span>");
}
output
}
fn parse_sgr_to_css(params: &str) -> String {
let parts: Vec<&str> = params.split(';').collect();
let mut css = Vec::new();
let mut i = 0;
while i < parts.len() {
match parts[i] {
"0" => {} "1" => css.push("font-weight:bold".to_string()),
"38" if i + 4 < parts.len() && parts[i + 1] == "2" => {
let r = parts[i + 2];
let g = parts[i + 3];
let b = parts[i + 4];
css.push(format!("color:rgb({},{},{})", r, g, b));
i += 4;
}
"48" if i + 4 < parts.len() && parts[i + 1] == "2" => {
let r = parts[i + 2];
let g = parts[i + 3];
let b = parts[i + 4];
css.push(format!("background:rgb({},{},{})", r, g, b));
i += 4;
}
"30" => css.push("color:#000".to_string()),
"31" => css.push("color:#c00".to_string()),
"32" => css.push("color:#0c0".to_string()),
"33" => css.push("color:#cc0".to_string()),
"34" => css.push("color:#00c".to_string()),
"35" => css.push("color:#c0c".to_string()),
"36" => css.push("color:#0cc".to_string()),
"37" => css.push("color:#ccc".to_string()),
"90" => css.push("color:#666".to_string()),
"91" => css.push("color:#f66".to_string()),
"92" => css.push("color:#6f6".to_string()),
"93" => css.push("color:#ff6".to_string()),
"94" => css.push("color:#66f".to_string()),
"95" => css.push("color:#f6f".to_string()),
"96" => css.push("color:#6ff".to_string()),
"97" => css.push("color:#fff".to_string()),
_ => {}
}
i += 1;
}
css.join(";")
}
pub fn inspect_vsf_html(data: &[u8]) -> Result<String, String> {
colored::control::set_override(true);
#[cfg(not(target_arch = "wasm32"))]
std::env::set_var("COLORTERM", "truecolor");
let terminal_output = inspect_vsf(data)?;
Ok(ansi_to_html(&terminal_output))
}
pub fn inspect_vsf_plain(data: &[u8]) -> Result<String, String> {
let terminal_output = inspect_vsf(data)?;
Ok(strip_ansi(&terminal_output))
}
pub fn inspect_section_html(data: &[u8]) -> Result<String, String> {
colored::control::set_override(true);
#[cfg(not(target_arch = "wasm32"))]
std::env::set_var("COLORTERM", "truecolor");
let terminal_output = inspect_section(data)?;
Ok(ansi_to_html(&terminal_output))
}
pub fn strip_ansi(input: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next();
while let Some(&pc) = chars.peek() {
if pc == 'm' {
chars.next();
break;
}
chars.next();
}
}
} else {
output.push(c);
}
}
output
}
const BOX_VERT: &str = "│"; const BOX_CORNER: &str = "╰"; const BOX_TEE: &str = "├"; const BOX_HORIZ: &str = "─";
fn col_uint() -> (u8, u8, u8) {
THEME.uint
} fn col_sint() -> (u8, u8, u8) {
THEME.sint
} fn col_float() -> (u8, u8, u8) {
THEME.float
} fn col_time() -> (u8, u8, u8) {
THEME.time
} fn col_text() -> (u8, u8, u8) {
THEME.text
} fn col_hash() -> (u8, u8, u8) {
THEME.crypto
} fn col_sig() -> (u8, u8, u8) {
THEME.crypto
} fn col_key() -> (u8, u8, u8) {
THEME.crypto
} fn col_wrap() -> (u8, u8, u8) {
THEME.binary
} fn col_tensor() -> (u8, u8, u8) {
THEME.binary
} fn col_colour() -> (u8, u8, u8) {
THEME.colour
} fn col_ro() -> (u8, u8, u8) {
THEME.renderable
} fn col_hint() -> (u8, u8, u8) {
THEME.hint
} fn col_label() -> (u8, u8, u8) {
THEME.label
} fn col_comment() -> (u8, u8, u8) {
THEME.comment
} fn col_punct() -> (u8, u8, u8) {
THEME.punct
} fn col_tree() -> (u8, u8, u8) {
THEME.tree
} fn col_pass() -> (u8, u8, u8) {
THEME.pass
} fn col_fail() -> (u8, u8, u8) {
THEME.fail
} fn col_size() -> (u8, u8, u8) {
THEME.size
}
fn tree_vert() -> ColoredString {
let (r, g, b) = col_tree();
BOX_VERT.truecolor(r, g, b)
}
fn tree_corner() -> ColoredString {
let (r, g, b) = col_tree();
BOX_CORNER.truecolor(r, g, b)
}
fn tree_corner_line() -> String {
let (r, g, b) = col_tree();
format!(
"{}{}",
BOX_CORNER.truecolor(r, g, b),
BOX_HORIZ.truecolor(r, g, b)
)
}
fn tree_tee() -> ColoredString {
let (r, g, b) = col_tree();
BOX_TEE.truecolor(r, g, b)
}
fn tree_tee_line() -> String {
let (r, g, b) = col_tree();
format!(
"{}{}",
BOX_TEE.truecolor(r, g, b),
BOX_HORIZ.truecolor(r, g, b)
)
}
#[allow(dead_code)]
const RO_TOP: &str = "╭─"; #[allow(dead_code)]
const RO_MID: &str = "├─"; #[allow(dead_code)]
const RO_BOT: &str = "╰─"; #[allow(dead_code)]
const RO_VERT: &str = "│"; #[allow(dead_code)]
const RO_SPACE: &str = " ";
fn ro_field(value: String, label: &str) -> String {
let (r, g, b) = col_hint();
format!(" {} {}", value, label.truecolor(r, g, b))
}
fn colour_hint(vsf: &VsfType) -> &'static str {
match vsf {
VsfType::rcr => " VSF Red",
VsfType::rcn => " VSF Green",
VsfType::rcb => " VSF Blue",
VsfType::rcw => " VSF White",
VsfType::rck => " VSF Black",
_ => "",
}
}
#[cfg(feature = "spirix")]
#[allow(dead_code)]
fn format_node_compact(node: &crate::types::Node) -> String {
use crate::types::NodeKind;
let kind_name = match &node.kind {
NodeKind::Box(_) => "Box",
NodeKind::Container(_) => "Container",
NodeKind::Circle(_) => "Circle",
NodeKind::Line(_) => "Line",
NodeKind::Text(_) => "Text",
NodeKind::Button(_) => "Button",
NodeKind::Path(_) => "Path",
NodeKind::Image(_) => "Image",
NodeKind::Surface(_) => "Surface",
};
let mut parts = vec![kind_name.to_string()];
if !node.transform.is_identity() {
let mut transform_parts = Vec::new();
if node.transform.translate.is_some() {
transform_parts.push("translate");
}
if node.transform.rotate.is_some() {
transform_parts.push("rotate");
}
if node.transform.scale.is_some() {
transform_parts.push("scale");
}
if node.transform.origin.is_some() {
transform_parts.push("origin");
}
if !transform_parts.is_empty() {
parts.push(format!("[{}]", transform_parts.join(" + ")));
}
}
match node.children.len() {
0 => {}
1 => parts.push("(1 child)".to_string()),
n => parts.push(format!("({} children)", n)),
}
parts.join(" ")
}
fn format_children(children: &[VsfType]) -> String {
if children.is_empty() {
return format!(
"{}{}",
trc("(", col_punct()),
trc(")", col_punct())
);
}
let mut result = String::from(format!("{}\n", trc("(", col_punct())));
for child in children {
let child_str = format_value_literal(child);
for line in child_str.lines() {
result.push_str(&format!(" {}\n", line));
}
}
result.push_str(&format!(" {}", trc(")", col_punct())));
result
}
#[allow(dead_code)]
fn format_vsf_universal(vsf: &VsfType, indent_level: usize, label: Option<&str>) -> String {
let indent = " ".repeat(indent_level);
let hint_colour = (64, 64, 64);
let value = format_value_literal(vsf);
let label_str = if let Some(l) = label {
format!(
" {}",
l.truecolor(hint_colour.0, hint_colour.1, hint_colour.2)
)
} else {
String::new()
};
format!("{}{}{}", indent, value, label_str)
}
const HEX_BYTES_PER_LINE: usize = 32;
const HEX_MAX_HEAD: usize = 1024;
const HEX_MAX_TAIL: usize = 1024;
fn format_hex_lines(data: &[u8]) -> Vec<String> {
let hex_str = hex::encode(data).to_uppercase();
let chars_per_line = HEX_BYTES_PER_LINE * 2;
if data.len() <= HEX_BYTES_PER_LINE {
vec![hex_str]
} else {
hex_str
.as_bytes()
.chunks(chars_per_line)
.map(|chunk| std::str::from_utf8(chunk).unwrap().to_string())
.collect()
}
}
fn format_hex_head_tail(data: &[u8]) -> (Vec<String>, Option<String>, Vec<String>) {
let total = data.len();
if total <= HEX_MAX_HEAD + HEX_MAX_TAIL {
(format_hex_lines(data), None, vec![])
} else {
let head = format_hex_lines(&data[..HEX_MAX_HEAD]);
let omitted = total - HEX_MAX_HEAD - HEX_MAX_TAIL;
let notice = format!("... {} bytes omitted ...", omitted);
let tail = format_hex_lines(&data[total - HEX_MAX_TAIL..]);
(head, Some(notice), tail)
}
}
fn format_crypto_literal(type_name: &str, data: &[u8]) -> String {
let (head, notice, tail) = format_hex_head_tail(data);
if head.len() == 1 && notice.is_none() {
format!("{}{{{}}}0x{}", type_name, data.len(), head[0])
} else {
let mut parts = head;
if let Some(n) = notice {
parts.push(n);
parts.extend(tail);
}
format!("{}{{{}}}0x\n{}", type_name, data.len(), parts.join("\n"))
}
}
fn crypto_type_colour(type_name: &str) -> (u8, u8, u8) {
match type_name.chars().next() {
Some('h') => col_hash(), Some('g') => col_sig(), Some('k') => col_key(), Some('a') => col_hash(), Some('v') => col_wrap(), _ => col_hint(), }
}
fn format_crypto_hex(type_name: &str, data: &[u8]) -> String {
let wire_len = if data.len() > 0 { data.len() - 1 } else { 0 };
let size_str = format!("3⦉{}⦊", wire_len);
let col = crypto_type_colour(type_name);
let (head, notice, tail) = format_hex_head_tail(data);
let mut lines: Vec<String> = head.iter().map(|l| l.white().to_string()).collect();
if let Some(n) = notice {
lines.push(n.dimmed().to_string());
lines.extend(tail.iter().map(|l| l.white().to_string()));
}
format!(
"{}{}{}{}\n{}\n{}",
type_name.truecolor(col.0, col.1, col.2),
tc(&size_str, col_punct()),
tc("⦉", col_punct()),
trc("G^0*", col_punct()),
lines.join("\n"),
tc("⦊", col_punct())
)
}
fn format_crypto_wrap(algo: u8, data: &[u8]) -> String {
let wire_len = if data.len() > 0 { data.len() - 1 } else { 0 };
let size_str = format!("3⦉{}⦊", wire_len);
let (head, notice, tail) = format_hex_head_tail(data);
if head.len() == 1 && notice.is_none() {
format!(
"{}{}{}{}",
trc(format!("v{}", algo as char), col_wrap()),
tc(&size_str, col_punct()),
tc("0x", col_punct()),
head[0].white()
)
} else {
let mut lines: Vec<String> = head.iter().map(|l| l.white().to_string()).collect();
if let Some(n) = notice {
lines.push(n.dimmed().to_string());
lines.extend(tail.iter().map(|l| l.white().to_string()));
}
format!(
"{}{}{}\n{}",
trc(format!("v{}", algo as char), col_wrap()),
tc(&size_str, col_punct()),
tc("0x", col_punct()),
lines.join("\n")
)
}
}
fn size_marker(len: usize) -> char {
if len <= 255 {
'3'
} else if len <= 65535 {
'4'
} else if len <= 0xFFFFFFFF {
'5'
} else {
'6'
}
}
fn opcode_hint(a: u8, b: u8) -> Option<&'static str> {
let opcode = [a, b];
match &opcode {
b"ps" => Some("push"),
b"pp" => Some("pop"),
b"dp" => Some("dup"),
b"dn" => Some("dup n"),
b"sw" => Some("swap"),
b"rt" => Some("rotate"),
b"la" => Some("alloc locals"),
b"lg" => Some("get local"),
b"ls" => Some("set local"),
b"lt" => Some("tee local"),
b"ad" => Some("add"),
b"sb" => Some("sub"),
b"ml" => Some("mul"),
b"dv" => Some("div"),
b"rc" => Some("recip"),
b"md" => Some("mod"),
b"ng" => Some("negate"),
b"ab" => Some("abs"),
b"sq" => Some("sqrt"),
b"pw" => Some("pow"),
b"mn" => Some("min"),
b"mx" => Some("max"),
b"cm" => Some("clamp"),
b"fl" => Some("floor"),
b"cl" => Some("ceil"),
b"rn" => Some("round"),
b"fa" => Some("frac"),
b"lp" => Some("lerp"),
b"sn" => Some("sin"),
b"cs" => Some("cos"),
b"tn" => Some("tan"),
b"is" => Some("asin"),
b"ic" => Some("acos"),
b"ia" => Some("atan"),
b"at" => Some("atan2"),
b"eq" => Some("equal"),
b"ne" => Some("not equal"),
b"lo" => Some("less than"),
b"le" => Some("less or equal"),
b"gt" => Some("greater than"),
b"ge" => Some("greater or equal"),
b"an" => Some("and"),
b"or" => Some("or"),
b"nt" => Some("not"),
b"ba" => Some("bit and"),
b"bo" => Some("bit or"),
b"bx" => Some("bit xor"),
b"bn" => Some("bit not"),
b"ty" => Some("typeof"),
b"ts" => Some("to scalar"),
b"tu" => Some("to uint"),
b"tx" => Some("to string"),
b"cr" => Some("clear"),
b"fr" => Some("fill rect"),
b"sr" => Some("stroke rect"),
b"fc" => Some("fill circle"),
b"so" => Some("stroke circle"),
b"dl" => Some("draw line"),
b"dt" => Some("draw text"),
b"sf" => Some("set font"),
b"ca" => Some("rgba"),
b"cb" => Some("rgb"),
b"ci" => Some("colour lerp"),
b"cn" => Some("call"),
b"cd" => Some("call indirect"),
b"re" => Some("return"),
b"rv" => Some("return value"),
b"jm" => Some("jump"),
b"ji" => Some("jump if"),
b"jz" => Some("jump if zero"),
b"hl" => Some("halt"),
b"db" => Some("debug print"),
b"ds" => Some("debug stack"),
b"np" => Some("nop"),
_ => None,
}
}
pub fn format_value_literal(vsf: &VsfType) -> String {
match vsf {
VsfType::d(s) => {
format!(
"{}{}{}{}{}{}",
trc("d", col_text()),
tc(&size_marker(s.len()).to_string(), col_punct()),
tc("⦉", col_punct()),
s.len().to_string().white(),
tc("⦊", col_punct()),
s.white().bold()
)
}
VsfType::l(s) => {
format!(
"{}{}{}{}{}{}",
trc("l", col_text()),
tc(&size_marker(s.len()).to_string(), col_punct()),
tc("⦉", col_punct()),
s.len().to_string().white(),
tc("⦊", col_punct()),
s.white()
)
}
VsfType::x(s) => {
format!(
"{}{}{}{}{}\"{}\"",
trc("x", col_text()),
tc(&size_marker(s.len()).to_string(), col_punct()),
tc("⦉", col_punct()),
s.len().to_string().white(),
tc("⦊", col_punct()),
s.escape_default().to_string().white()
)
}
VsfType::u0(b) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("0", col_punct()),
tc("⦉", col_punct()),
if *b { "1" } else { "0" }.white(),
tc("⦊", col_punct())
),
VsfType::u3(v) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::u4(v) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("4", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::u5(v) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("5", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::u6(v) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("6", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::u7(v) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc("7", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::u(v, _) => format!(
"{}{}{}{}{}",
trc("u", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc(&size_marker(v.unsigned_abs()).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i3(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i4(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc("4", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i5(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc("5", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i6(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc("6", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::i7(v) => format!(
"{}{}{}{}{}",
trc("i", col_sint()),
tc("7", col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::f5(v) => format!(
"{}{}{}{}{}",
trc("f", col_float()),
tc("5", col_punct()),
tc("⦉", col_punct()),
format!("{:.6}", v).white(),
tc("⦊", col_punct())
),
VsfType::f6(v) => format!(
"{}{}{}{}{}",
trc("f", col_float()),
tc("6", col_punct()),
tc("⦉", col_punct()),
format!("{:.10}", v).white(),
tc("⦊", col_punct())
),
VsfType::n(v) => format!(
"{}{}{}{}{}",
trc("n", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::b(v, _) => format!(
"{}{}{}{}{}",
trc("b", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::o(v) => format!(
"{}{}{}{}{}",
trc("o", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::z(v) => format!(
"{}{}{}{}{}",
trc("z", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::y(v) => format!(
"{}{}{}{}{}",
trc("y", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::L(v, _) => format!(
"{}{}{}{}{}",
trc("L", col_uint()),
tc(&size_marker(*v).to_string(), col_punct()),
tc("⦉", col_punct()),
v.to_string().white(),
tc("⦊", col_punct())
),
VsfType::hp(h) => format_crypto_hex("hp", h),
VsfType::hb(h) => format_crypto_hex("hb", h),
VsfType::hs(h) => format_crypto_hex("hs", h),
VsfType::hm(h) => format_crypto_hex("hm", h),
VsfType::hg(h) => format_crypto_hex("hg", h),
VsfType::hc(h) => format_crypto_hex("hc", h), VsfType::hk(h) => format_crypto_hex("hk", h), VsfType::hP(h) => format_crypto_hex("hP", h), VsfType::ke(k) => format_crypto_hex("ke", k), VsfType::kx(k) => format_crypto_hex("kx", k), VsfType::kp(k) => format_crypto_hex("kp", k), VsfType::kc(k) => format_crypto_hex("kc", k), VsfType::ka(k) => format_crypto_hex("ka", k), VsfType::kk(k) => format_crypto_hex("kk", k), VsfType::kf(k) => format_crypto_hex("kf", k), VsfType::kn(k) => format_crypto_hex("kn", k), VsfType::kl(k) => format_crypto_hex("kl", k), VsfType::kh(k) => format_crypto_hex("kh", k), VsfType::kd(k) => format_crypto_hex("kd", k), VsfType::km(k) => format_crypto_hex("km", k), VsfType::kb(k) => format_crypto_hex("kb", k), VsfType::ksx(k) => format_crypto_hex("ksx", k), VsfType::ksp(k) => format_crypto_hex("ksp", k), VsfType::ksk(k) => format_crypto_hex("ksk", k), VsfType::ksf(k) => format_crypto_hex("ksf", k), VsfType::ksn(k) => format_crypto_hex("ksn", k), VsfType::ksl(k) => format_crypto_hex("ksl", k), VsfType::ksh(k) => format_crypto_hex("ksh", k), VsfType::ksm(k) => format_crypto_hex("ksm", k), VsfType::ge(s) => format_crypto_hex("ge", s), VsfType::gp(s) => format_crypto_hex("gp", s), VsfType::gd(s) => format_crypto_hex("gd", s), VsfType::gs(s) => format_crypto_hex("gs", s), VsfType::gf(s) => format_crypto_hex("gf", s), #[allow(deprecated)]
VsfType::gr(s) => format_crypto_hex("gr", s), VsfType::ah(m) => format_crypto_hex("ah", m), VsfType::ap(m) => format_crypto_hex("ap", m), VsfType::ab(m) => format_crypto_hex("ab", m), VsfType::ac(m) => format_crypto_hex("ac", m), VsfType::v(algo, data) => {
#[cfg(feature = "spirix")]
if *algo == b't' {
return format!(
"{}{}{}{}",
trc("vt", col_wrap()),
tc("3", col_punct()),
tc(&format!("{{{}}}", data.len()), col_punct()),
" (deprecated - use ro* types)".dimmed()
);
}
format_crypto_wrap(*algo, data)
}
VsfType::t_u3(tensor) => {
let dims = tensor.shape.len();
let shape_encoded: String = tensor
.shape
.iter()
.map(|d| {
format!(
"{}{}{}{}",
tc("3", col_punct()),
tc("⦉", col_punct()),
d.to_string().white(),
tc("⦊", col_punct())
)
})
.collect();
let preview_len = tensor.data.len().min(32);
let values_preview: String = tensor.data[..preview_len]
.iter()
.map(|b| b.to_string())
.collect::<Vec<_>>()
.join(",");
let ellipsis = if tensor.data.len() > 32 { ",..." } else { "" };
let shape_hint = tensor
.shape
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join("×");
format!(
"{}{}{}{}{}{} {} {}{}{}{}{}",
trc("t", col_tensor()),
tc("3", col_punct()),
tc("⦉", col_punct()),
dims.to_string().white(),
tc("⦊", col_punct()),
trc("u3", col_uint()),
shape_encoded,
tc("[", col_punct()),
values_preview.white(),
tc(ellipsis, col_punct()),
tc("]", col_punct()),
format!(" {}", tc(&format!("{} u8 tensor", shape_hint), col_hint()))
)
}
VsfType::e(et) => {
let formatted = format_eagle_time(et);
let type_marker = match et {
EtType::u(_) => "eu",
EtType::i(_) => "ei",
EtType::f5(_) => "ef5",
EtType::f6(_) => "ef6",
};
format!(
"{}{}{}{}",
trc(type_marker, col_time()),
tc("⦉", col_punct()),
formatted.white(),
tc("⦊", col_punct())
)
}
VsfType::op(a, b) => {
let base = format!(
"{}{}{}{}",
tc("{", col_punct()),
trc(char::from(*a).to_string(), col_uint()),
trc(char::from(*b).to_string(), col_uint()),
tc("}", col_punct())
);
if let Some(hint) = opcode_hint(*a, *b) {
format!("{} {}", base, trc(hint, col_hint()))
} else {
base
}
}
#[cfg(feature = "spirix")]
VsfType::s33(s) => format!("{}{}", trc("s33", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s34(s) => format!("{}{}", trc("s34", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s35(s) => format!("{}{}", trc("s35", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s36(s) => format!("{}{}", trc("s36", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s37(s) => format!("{}{}", trc("s37", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s43(s) => format!("{}{}", trc("s43", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s44(s) => format!("{}{}", trc("s44", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s45(s) => format!("{}{}", trc("s45", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s46(s) => format!("{}{}", trc("s46", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s47(s) => format!("{}{}", trc("s47", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s53(s) => format!("{}{}", trc("s53", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s54(s) => format!("{}{}", trc("s54", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s55(s) => format!("{}{}", trc("s55", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s56(s) => format!("{}{}", trc("s56", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s57(s) => format!("{}{}", trc("s57", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s63(s) => format!("{}{}", trc("s63", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s64(s) => format!("{}{}", trc("s64", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s65(s) => format!("{}{}", trc("s65", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s66(s) => format!("{}{}", trc("s66", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s67(s) => format!("{}{}", trc("s67", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s73(s) => format!("{}{}", trc("s73", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s74(s) => format!("{}{}", trc("s74", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s75(s) => format!("{}{}", trc("s75", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s76(s) => format!("{}{}", trc("s76", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::s77(s) => format!("{}{}", trc("s77", col_uint()), s),
#[cfg(feature = "spirix")]
VsfType::c33(c) => format!("{}{}", trc("c33", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c34(c) => format!("{}{}", trc("c34", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c35(c) => format!("{}{}", trc("c35", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c36(c) => format!("{}{}", trc("c36", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c37(c) => format!("{}{}", trc("c37", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c43(c) => format!("{}{}", trc("c43", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c44(c) => format!("{}{}", trc("c44", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c45(c) => format!("{}{}", trc("c45", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c46(c) => format!("{}{}", trc("c46", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c47(c) => format!("{}{}", trc("c47", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c53(c) => format!("{}{}", trc("c53", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c54(c) => format!("{}{}", trc("c54", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c55(c) => format!("{}{}", trc("c55", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c56(c) => format!("{}{}", trc("c56", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c57(c) => format!("{}{}", trc("c57", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c63(c) => format!("{}{}", trc("c63", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c64(c) => format!("{}{}", trc("c64", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c65(c) => format!("{}{}", trc("c65", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c66(c) => format!("{}{}", trc("c66", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c67(c) => format!("{}{}", trc("c67", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c73(c) => format!("{}{}", trc("c73", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c74(c) => format!("{}{}", trc("c74", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c75(c) => format!("{}{}", trc("c75", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c76(c) => format!("{}{}", trc("c76", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::c77(c) => format!("{}{}", trc("c77", col_uint()), c),
#[cfg(feature = "spirix")]
VsfType::rob(pos, size, fill, stroke, children) => {
let mut result = format!("rob {}", trc("rectangle", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
if !children.is_empty() {
result.push_str(&format!(
"\n{}",
ro_field(format_children(children), "children")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::roc(center, radius, fill, stroke) => {
let mut result = format!("roc {}", trc("circle", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", center), "center")));
result.push_str(&format!("\n{}", ro_field(format!("{}", radius), "radius")));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::ron(pos, size, children) => {
let mut result = format!("ron {}", trc("container", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
result.push_str(&format!(
"\n{}",
ro_field(format_children(children), "children")
));
result
}
#[cfg(feature = "spirix")]
VsfType::roe(center, size, fill, stroke) => {
let mut result = format!("roe {}", trc("ellipse", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", center), "center")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::rol(start, end, width, colour) => {
let mut result = format!("rol {}", trc("line", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", start), "start")));
result.push_str(&format!("\n{}", ro_field(format!("{}", end), "end")));
result.push_str(&format!("\n{}", ro_field(format!("{}", width), "width")));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", colour), "colour")
));
result
}
#[cfg(feature = "spirix")]
VsfType::rop(commands, fill, stroke) => {
let mut result = format!("rop {}", trc("path", col_ro()));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", commands), "commands")
));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::roo(points, width, colour, closed) => {
let mut result = format!("roo {}", trc("polyline", col_ro()));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", points), "points")
));
result.push_str(&format!("\n{}", ro_field(format!("{}", width), "width")));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", colour), "colour")
));
result.push_str(&format!("\n{}", ro_field(format!("{}", closed), "closed")));
result
}
#[cfg(feature = "spirix")]
VsfType::ror(controls, knots, degree, fill, stroke) => {
let mut result = format!("ror {}", trc("NURBS", col_ro()));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", controls), "controls")
));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", knots), "knots")));
result.push_str(&format!("\n{}", ro_field(format!("{}", degree), "degree")));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::rox(points, spline_type, fill, stroke) => {
let mut result = format!("rox {}", trc("spline", col_ro()));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", points), "points")
));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", spline_type), "type")
));
let fill_str = match fill {
Fill::Solid(colour) => {
format!(
"Solid({}{})",
match colour.as_ref() {
VsfType::rcr => "rcr",
VsfType::rcn => "rcn",
VsfType::rcb => "rcb",
VsfType::rcw => "rcw",
VsfType::rck => "rck",
_ => "?",
},
trc(colour_hint(colour.as_ref()), col_colour())
)
}
_ => format!("{:?}", fill),
};
result.push_str(&format!("\n{}", ro_field(fill_str, "fill")));
if stroke.is_some() {
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", stroke), "stroke")
));
}
result
}
#[cfg(feature = "spirix")]
VsfType::rot(pos, text, size, colour, style) => {
let mut result = format!("rot {}", trc("text", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", text), "text")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", colour), "colour")));
if let Some(s) = style {
let align_str = match s.align {
Some(1) => "left",
Some(2) => "right",
_ => "center",
};
result.push_str(&format!("\n{}", ro_field(align_str.to_string(), "align")));
if let Some(v) = s.leading { result.push_str(&format!("\n{}", ro_field(format!("{}", v), "leading"))); }
if let Some(v) = s.kerning { result.push_str(&format!("\n{}", ro_field(format!("{}", v), "kerning"))); }
if let Some(v) = s.weight { result.push_str(&format!("\n{}", ro_field(format!("{}", v), "weight"))); }
if let Some(v) = s.tilt { result.push_str(&format!("\n{}", ro_field(format!("{}", v), "tilt"))); }
if let Some(v) = s.wrap { result.push_str(&format!("\n{}", ro_field(format!("{}", v), "wrap"))); }
if let Some(h) = s.font { result.push_str(&format!("\n{}", ro_field(hex::encode(h), "font hash"))); }
}
result
}
#[cfg(feature = "spirix")]
VsfType::rou(pos, size, label, variant, colour) => {
let mut result = format!("rou {}", trc("button", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", label), "label")));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", variant), "variant")
));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", colour), "colour")
));
result
}
#[cfg(feature = "spirix")]
VsfType::roi(pos, size, handle, tint) => {
let mut result = format!("roi {}", trc("image", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
result.push_str(&format!("\n{}", ro_field(format!("{}", handle), "handle")));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", tint), "tint")));
result
}
#[cfg(feature = "spirix")]
VsfType::rof(pos, size, handle) => {
let mut result = format!("rof {}", trc("surface", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", pos), "position")));
result.push_str(&format!("\n{}", ro_field(format!("{}", size), "size")));
result.push_str(&format!("\n{}", ro_field(format!("{}", handle), "handle")));
result
}
#[cfg(feature = "spirix")]
VsfType::rom(shape, children) => {
let mut result = format!("rom {}", trc("mask", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", shape), "shape")));
result.push_str(&format!(
"\n{}",
ro_field(format_children(children), "children")
));
result
}
#[cfg(feature = "spirix")]
VsfType::row(transform, children) => {
let mut result = format!("row {}", trc("group", col_ro()));
let transform_str = {
let mut parts = Vec::new();
if let Some(t) = &transform.translate {
parts.push(format!("translate: {}", t));
}
if let Some(r) = &transform.rotate {
parts.push(format!("rotate: {} rad", r));
}
if let Some(s) = &transform.scale {
parts.push(format!("scale: {}", s));
}
if let Some(o) = &transform.origin {
parts.push(format!("origin: {}", o));
}
if parts.is_empty() {
"identity".to_string()
} else {
parts.join(", ")
}
};
result.push_str(&format!("\n{}", ro_field(transform_str, "transform")));
result.push_str(&format!(
"\n{}",
ro_field(format_children(children), "children")
));
result
}
#[cfg(feature = "spirix")]
VsfType::rog(variant, stops) => {
let mut result = format!("rog {}", trc("gradient", col_ro()));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", variant), "variant")
));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", stops), "stops")));
result
}
#[cfg(feature = "spirix")]
VsfType::rok(width, colour, join, cap) => {
let mut result = format!("rok {}", trc("stroke", col_ro()));
result.push_str(&format!("\n{}", ro_field(format!("{}", width), "width")));
result.push_str(&format!(
"\n{}",
ro_field(format!("{:?}", colour), "colour")
));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", join), "join")));
result.push_str(&format!("\n{}", ro_field(format!("{:?}", cap), "cap")));
result
}
_ => format!("{:?}", vsf),
}
}
pub struct LabelInfo {
pub name: String,
pub hash: Option<VsfType>,
pub signature: Option<VsfType>,
pub key: Option<VsfType>,
pub wrap: Option<VsfType>, pub offset: usize,
pub size: usize,
pub child_count: usize,
pub inline_values: Vec<VsfType>, }
pub fn format_bytes(bytes: usize) -> String {
const KB: f64 = 1024.0;
const MB: f64 = KB * 1024.0;
const GB: f64 = MB * 1024.0;
const TB: f64 = GB * 1024.0;
const PB: f64 = TB * 1024.0;
let bytes_f64 = bytes as f64;
if bytes_f64 >= PB {
let pb = bytes_f64 / PB;
if pb >= 100.0 {
format!("{:.1} PB", pb)
} else if pb >= 10.0 {
format!("{:.2} PB", pb)
} else {
format!("{:.3} PB", pb)
}
} else if bytes_f64 >= TB {
let tb = bytes_f64 / TB;
if tb >= 100.0 {
format!("{:.1} TB", tb)
} else if tb >= 10.0 {
format!("{:.2} TB", tb)
} else {
format!("{:.3} TB", tb)
}
} else if bytes_f64 >= GB {
let gb = bytes_f64 / GB;
if gb >= 100.0 {
format!("{:.1} GB", gb)
} else if gb >= 10.0 {
format!("{:.2} GB", gb)
} else {
format!("{:.3} GB", gb)
}
} else if bytes_f64 >= MB {
let mb = bytes_f64 / MB;
if mb >= 100.0 {
format!("{:.1} MB", mb)
} else if mb >= 10.0 {
format!("{:.2} MB", mb)
} else {
format!("{:.3} MB", mb)
}
} else if bytes_f64 >= KB {
let kb = bytes_f64 / KB;
if kb >= 100.0 {
format!("{:.1} KB", kb)
} else if kb >= 10.0 {
format!("{:.2} KB", kb)
} else {
format!("{:.3} KB", kb)
}
} else {
format!("{} Bytes", bytes)
}
}
pub fn format_number(n: usize) -> String {
let s = n.to_string();
let mut result = String::new();
let chars: Vec<char> = s.chars().collect();
for (i, c) in chars.iter().enumerate() {
if i > 0 && (chars.len() - i) % 3 == 0 {
result.push(' ');
}
result.push(*c);
}
result
}
pub fn format_eagle_time(et: &EtType) -> String {
let eagle_time = EagleTime::new(et.clone());
let dt_utc = match eagle_time.to_datetime_opt() {
Some(dt) => dt,
None => {
return match et {
EtType::u(v) => format!("eu{{{}}}", v),
EtType::i(v) => format!("ei{{{}}}", v),
EtType::f5(v) => format!("ef5{{{}}}", v),
EtType::f6(v) => format!("ef6{{{}}}", v),
};
}
};
let dt = dt_utc.with_timezone(&Local);
let seconds_f64 = eagle_time.to_seconds_f64();
let milliseconds = ((seconds_f64.fract().abs() * 1000.0) as u32) % 1000;
let year = dt.year();
let month = dt.month();
let day = dt.day();
let hour = dt.hour();
let minute = dt.minute();
let second = dt.second();
let (hour_12, am_pm) = if hour == 0 {
(12, "AM")
} else if hour < 12 {
(hour, "AM")
} else if hour == 12 {
(12, "PM")
} else {
(hour - 12, "PM")
};
let month_name = match month {
1 => "JAN",
2 => "FEB",
3 => "MAR",
4 => "APR",
5 => "MAY",
6 => "JUN",
7 => "JUL",
8 => "AUG",
9 => "SEP",
10 => "OCT",
11 => "NOV",
12 => "DEC",
_ => "UNK",
};
format!(
"{}-{}-{:02} {}:{:02}:{:02}.{:03} {}",
year, month_name, day, hour_12, minute, second, milliseconds, am_pm
)
}
pub fn hex_preview(bytes: &[u8]) -> String {
bytes
.iter()
.take(4)
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join("")
}
pub fn format_value(vsf: &VsfType) -> String {
match vsf {
VsfType::u0(b) => format!("{}", b),
VsfType::u(v, _) => format!("{}", v),
VsfType::u3(v) => format!("{}", v),
VsfType::u4(v) => format!("{}", v),
VsfType::u5(v) => format!("{}", v),
VsfType::u6(v) => format!("{}", v),
VsfType::u7(v) => format!("{}", v),
VsfType::i(v) => format!("{}", v),
VsfType::i3(v) => format!("{}", v),
VsfType::i4(v) => format!("{}", v),
VsfType::i5(v) => format!("{}", v),
VsfType::i6(v) => format!("{}", v),
VsfType::i7(v) => format!("{}", v),
VsfType::f5(v) => format!("{:.4}", v),
VsfType::f6(v) => format!("{:.8}", v),
VsfType::x(s) => format!("\"{}\"", s.escape_default()),
VsfType::p(tensor) => {
let shape_str = tensor
.shape
.iter()
.map(|d| format_number(*d))
.collect::<Vec<_>>()
.join(" × ");
format!(
"{}, {}-bit packed tensor ({} Bytes)",
shape_str,
tensor.bit_depth,
format_number(tensor.data.len())
)
}
VsfType::t_u3(tensor) => {
if tensor.shape == vec![16] && tensor.data.len() == 16 {
let bytes: [u8; 16] = tensor.data.as_slice().try_into().unwrap_or([0u8; 16]);
let ipv6 = std::net::Ipv6Addr::from(bytes);
format!("t_u3{{{}}}", ipv6)
} else {
let shape_str = tensor
.shape
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join("×");
let preview_len = tensor.data.len().min(64);
let hex_preview = tensor.data[..preview_len]
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join("");
let ellipsis = if tensor.data.len() > 64 { "..." } else { "" };
format!(
"t_u3[{}]({} bytes)0x{}{}",
shape_str,
tensor.data.len(),
hex_preview,
ellipsis
)
}
}
VsfType::t_f5(tensor) => {
let shape_str = tensor
.shape
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join("×");
format!("t_f5[{}] {} elements", shape_str, tensor.data.len())
}
VsfType::w(coord) => {
let (lat, lon) = coord.to_lat_lon();
format!("({:.4}°N, {:.4}°W)", lat, lon)
}
VsfType::e(et) => {
match et {
EtType::u(v) => format!("eu{{{}}}", v),
EtType::i(v) => format!("ei{{{}}}", v),
EtType::f5(v) => format!("ef5{{{:.2}}}", v),
EtType::f6(v) => format!("ef6{{{:.2}}}", v),
}
}
VsfType::hp(hash) => format_crypto_literal("hp", hash),
VsfType::hb(hash) => format_crypto_literal("hb", hash),
VsfType::hs(hash) => format_crypto_literal("hs", hash),
VsfType::hm(hash) => format_crypto_literal("hm", hash),
VsfType::hg(hash) => format_crypto_literal("hg", hash),
VsfType::hP(hash) => format_crypto_literal("hP", hash),
VsfType::ge(sig) => format_crypto_literal("ge", sig),
VsfType::gp(sig) => format_crypto_literal("gp", sig),
VsfType::gr(sig) => format_crypto_literal("gr", sig),
VsfType::ke(key) => format_crypto_literal("ke", key),
VsfType::kx(key) => format_crypto_literal("kx", key),
VsfType::kp(key) => format_crypto_literal("kp", key),
VsfType::kc(key) => format_crypto_literal("kc", key),
VsfType::ka(key) => format_crypto_literal("ka", key),
VsfType::kk(key) => format_crypto_literal("kk", key), VsfType::kf(key) => format_crypto_literal("kf", key), VsfType::kn(key) => format_crypto_literal("kn", key), VsfType::kl(key) => format_crypto_literal("kl", key), VsfType::kh(key) => format_crypto_literal("kh", key), VsfType::ah(mac) => format_crypto_literal("ah", mac),
VsfType::ap(mac) => format_crypto_literal("ap", mac),
VsfType::ab(mac) => format_crypto_literal("ab", mac),
VsfType::ac(mac) => format_crypto_literal("ac", mac),
VsfType::v(algo, data) => format_crypto_literal(&format!("v{}", *algo as char), data),
VsfType::d(name) => format!("d\"{}\"", name),
VsfType::l(s) => s.clone(),
VsfType::o(offset) => format!("o[{}]", offset),
VsfType::n(count) => format!("n[{}]", count),
VsfType::b(size, _) => format!("b[{}]", size),
VsfType::op(a, b) => format!("{{{}{}}}", char::from(*a), char::from(*b)),
#[cfg(feature = "spirix")]
VsfType::s33(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s34(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s35(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s36(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s37(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s43(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s44(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s45(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s46(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s47(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s53(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s54(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s55(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s56(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s57(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s63(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s64(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s65(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s66(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s67(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s73(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s74(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s75(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s76(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::s77(s) => format!("{}", s),
#[cfg(feature = "spirix")]
VsfType::c33(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c34(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c35(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c36(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c37(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c43(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c44(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c45(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c46(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c47(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c53(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c54(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c55(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c56(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c57(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c63(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c64(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c65(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c66(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c67(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c73(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c74(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c75(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c76(c) => format!("{}", c),
#[cfg(feature = "spirix")]
VsfType::c77(c) => format!("{}", c),
_ => format!("{:?}", vsf),
}
}
pub fn format_value_short(vsf: &VsfType) -> String {
match vsf {
VsfType::p(tensor) => {
let shape_str = tensor
.shape
.iter()
.map(|d| format_number(*d))
.collect::<Vec<_>>()
.join(" × ");
format!(
"{}, {}-bit packed tensor ({} Bytes)",
shape_str,
tensor.bit_depth,
format_number(tensor.data.len())
)
}
VsfType::x(s) if s.len() > 30 => format!("\"{}\"...", s[..27].escape_default()),
VsfType::hp(hash) => format_crypto_hex("hp", hash),
VsfType::hb(hash) => format_crypto_hex("hb", hash),
VsfType::hs(hash) => format_crypto_hex("hs", hash),
VsfType::hm(hash) => format_crypto_hex("hm", hash),
VsfType::hg(hash) => format_crypto_hex("hg", hash),
VsfType::hP(hash) => format_crypto_hex("hP", hash),
VsfType::ke(key) => format_crypto_hex("ke", key),
VsfType::kx(key) => format_crypto_hex("kx", key),
VsfType::kp(key) => format_crypto_hex("kp", key),
VsfType::kc(key) => format_crypto_hex("kc", key),
VsfType::ka(key) => format_crypto_hex("ka", key),
VsfType::kk(key) => format_crypto_hex("kk", key), VsfType::kf(key) => format_crypto_hex("kf", key), VsfType::kn(key) => format_crypto_hex("kn", key), VsfType::kl(key) => format_crypto_hex("kl", key), VsfType::kh(key) => format_crypto_hex("kh", key), VsfType::ge(sig) => format_crypto_hex("ge", sig),
VsfType::gp(sig) => format_crypto_hex("gp", sig),
VsfType::gr(sig) => format_crypto_hex("gr", sig),
VsfType::v(algo, data) => {
#[cfg(feature = "spirix")]
if *algo == b't' {
return format!(
"{}{}{}{}",
trc("vt", col_wrap()),
tc("3", col_punct()),
tc(&format!("{{{}}}", data.len()), col_punct()),
" (deprecated - use ro* types)".dimmed()
);
}
format_crypto_wrap(*algo, data)
}
_ => format_value(vsf),
}
}
pub fn parse_section_fields(data: &[u8], label: &LabelInfo) -> Result<Vec<VsfField>, String> {
let mut pointer = label.offset;
let mut fields = Vec::new();
if pointer >= data.len() {
return Err(format!(
"Offset {} beyond file length {}",
pointer,
data.len()
));
}
if data[pointer] == b'>' {
pointer += 1;
}
if data[pointer] != b'[' {
return Err(format!(
"Expected '[' at offset {}, found {:02x} ('{}')",
pointer, data[pointer], data[pointer] as char
));
}
pointer += 1;
if pointer < data.len() && data[pointer] != b'(' {
let section_name_type = parse(data, &mut pointer)
.map_err(|e| format!("Failed to parse section name: {}", e))?;
let _section_name = match section_name_type {
VsfType::d(name) => name,
_ => return Err("Expected d type for section name".to_string()),
};
let _ = parse(data, &mut pointer); let _ = parse(data, &mut pointer); }
for i in 0..label.child_count {
if pointer >= data.len() {
return Err(format!(
"Unexpected end of file at field {}/{}",
i, label.child_count
));
}
let field =
VsfField::parse(data, &mut pointer).map_err(|e| format!("Field {}: {}", i, e))?;
fields.push(field);
}
Ok(fields)
}
pub fn try_parse_section_fields(data: &[u8], offset: usize) -> Result<Vec<VsfField>, String> {
let mut pointer = offset;
let mut fields = Vec::new();
if pointer >= data.len() {
return Err("Offset beyond file length".into());
}
if data[pointer] != b'[' {
return Err("Expected '[' at section start".into());
}
pointer += 1;
if pointer < data.len() && data[pointer] != b'(' {
let section_name_type = parse(data, &mut pointer)
.map_err(|e| format!("Failed to parse section name: {}", e))?;
let _section_name = match section_name_type {
VsfType::d(_) => {}
_ => return Err("Expected d type for section name".into()),
};
let _ = parse(data, &mut pointer); let _ = parse(data, &mut pointer); }
while pointer < data.len() && data[pointer] != b']' {
let field = VsfField::parse(data, &mut pointer)?;
fields.push(field);
}
Ok(fields)
}
pub fn labels_from_header(header: &VsfHeader) -> Vec<LabelInfo> {
header
.fields
.iter()
.map(|field| LabelInfo {
name: field.name.clone(),
hash: field.hash.clone(),
signature: field.signature.clone(),
key: field.key.clone(),
wrap: None,
offset: field.offset_bytes,
size: field.size_bytes,
child_count: field.child_count,
inline_values: field.inline_values.clone(),
})
.collect()
}
pub fn inspect_vsf(data: &[u8]) -> Result<String, String> {
if data.len() < 4 {
return Err("Data too short for Versatile Storage Format".into());
}
if &data[0..3] != "RÅ".as_bytes() || data[3] != b'<' {
return Err("Invalid VSF magic number".into());
}
let (header, actual_header_size) = VsfHeader::decode(data)?;
let labels = labels_from_header(&header);
let mut pointer = 4; let _ = parse(data, &mut pointer).map_err(|e| format!("Failed to parse version: {}", e))?;
let _ =
parse(data, &mut pointer).map_err(|e| format!("Failed to parse backward compat: {}", e))?;
let header_length_type =
parse(data, &mut pointer).map_err(|e| format!("Failed to parse header length: {}", e))?;
let header_length_bytes = match header_length_type {
VsfType::b(bytes, _) => bytes,
_ => 0,
};
let file_length_bytes = if pointer < data.len() && data[pointer] == b'L' {
let file_length_type =
parse(data, &mut pointer).map_err(|e| format!("Failed to parse file length: {}", e))?;
match file_length_type {
VsfType::L(bytes, _) => Some(bytes),
_ => None,
}
} else {
None };
let mut out = String::new();
out.push_str("RÅ\n");
out.push_str(&format!(
"< {}\n",
tc("Versatile Storage Format", col_tree())
));
out.push_str(&format!(
" {}{}{}{}{} {}\n",
trc("z", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
header.version.to_string().white(),
tc("⦊", col_punct()),
tc("version", col_tree())
));
out.push_str(&format!(
" {}{}{}{}{} {}\n",
trc("y", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
header.backward_compat.to_string().white(),
tc("⦊", col_punct()),
tc("backward compat", col_tree())
));
if let VsfType::e(ref et) = header.creation_time {
let type_suffix = match et {
crate::types::EtType::u(_) => "u".to_string(),
crate::types::EtType::i(_) => "i".to_string(),
crate::types::EtType::f5(_) => "f5".to_string(),
crate::types::EtType::f6(_) => "f6".to_string(),
};
out.push_str(&format!(
" {}{}{}{}{}\n",
trc("e", col_time()),
trc(type_suffix, col_punct()),
tc("⦉", col_punct()),
format_eagle_time(et).white(),
tc("⦊", col_punct())
));
}
let header_size_valid = header_length_bytes == actual_header_size;
if header_size_valid {
out.push_str(&format!(
" {}{}{}{}{} {} {} {}\n",
trc("b", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
header_length_bytes.to_string().white(),
tc("⦊", col_punct()),
tc("Header size", col_hint()),
tc("Bytes", col_hint()),
tc("✓", col_pass())
));
} else {
out.push_str(&format!(
" {}{}{}{}{} {} {} {} {}\n",
trc("b", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
trc(header_length_bytes.to_string(), col_fail()),
tc("⦊", col_punct()),
tc("Header size", col_hint()),
tc("Bytes", col_hint()),
tc("✗ MISMATCH", col_fail()),
trc(format!("(actual: {})", actual_header_size), col_hint())
));
}
if let Some(file_len) = file_length_bytes {
let actual_len = data.len();
let length_valid = file_len == actual_len;
if length_valid {
out.push_str(&format!(
" {}{}{}{}{} {} {} {}\n",
trc("L", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
file_len.to_string().white(),
tc("⦊", col_punct()),
tc("File length", col_hint()),
tc("Bytes", col_hint()),
tc("✓", col_pass())
));
} else {
out.push_str(&format!(
" {}{}{}{}{} {} {} {} {}\n",
trc("L", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
trc(file_len.to_string(), col_fail()),
tc("⦊", col_punct()),
tc("File length", col_hint()),
tc("Bytes", col_hint()),
tc("✗ MISMATCH", col_fail()),
trc(format!("(actual: {})", actual_len), col_hint())
));
}
}
if let VsfType::hp(ref hash) = header.provenance_hash {
out.push_str(&format!(
" {}{}{}{}{} {} {}\n",
trc("hp", col_hash()),
tc("3", col_punct()),
tc("⦉", col_punct()),
format!("{}", hash.len() - 1).white(),
tc("⦊", col_punct()),
tc("BLAKE3 provenance hash", col_hint()),
trc(format!("({} Bytes)", hash.len()), col_hint()),
));
let hash_lines = format_hex_lines(hash);
for line in &hash_lines {
out.push_str(&format!(" {}\n", line.white()));
}
}
if let Some(VsfType::ke(ref key)) = header.signer_pubkey {
out.push_str(&format!(
" {}{}{}{}{} {} {}\n",
trc("ke", col_key()),
tc("3", col_punct()),
tc("⦉", col_punct()),
format!("{}", key.len() - 1).white(),
tc("⦊", col_punct()),
tc("Ed25519 signer pubkey", col_hint()),
trc(format!("({} Bytes)", key.len()), col_hint()),
));
let key_lines = format_hex_lines(key);
for line in &key_lines {
out.push_str(&format!(" {}\n", line.white()));
}
}
if let Some(VsfType::ge(ref sig)) = header.signature {
out.push_str(&format!(
" {}{}{}{}{} {} {}\n",
trc("ge", col_sig()),
tc("3", col_punct()),
tc("⦉", col_punct()),
format!("{}", sig.len() - 1).white(),
tc("⦊", col_punct()),
tc("Ed25519 signature", col_hint()),
trc(format!("({} Bytes)", sig.len()), col_hint()),
));
let sig_lines = format_hex_lines(sig);
for line in &sig_lines {
out.push_str(&format!(" {}\n", line.white()));
}
}
if let Some(VsfType::hb(ref hash)) = header.rolling_hash {
out.push_str(&format!(
" {}{}{}{}{} {} {}\n",
trc("hb", col_hash()),
tc("3", col_punct()),
tc("⦉", col_punct()),
format!("{}", hash.len() - 1).white(), tc("⦊", col_punct()),
tc("BLAKE3 rolling hash", col_hint()),
trc(format!("({} Bytes)", hash.len()), col_hint()),
));
let hash_lines = format_hex_lines(hash);
for line in &hash_lines {
out.push_str(&format!(" {}\n", line.white()));
}
if let Ok(computed) = crate::verification::compute_file_hash(data) {
if computed.as_slice() == hash.as_slice() {
out.push_str(&format!(
" {} {}\n",
tc("Verification:", col_hint()),
tc("PASS", col_pass())
));
} else {
out.push_str(&format!(
" {} {}\n",
tc("Verification:", col_hint()),
tc("FAIL", col_fail())
));
}
}
}
out.push_str(&format!(
" {}{}{}{}{} {}\n",
trc("n", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
labels.len().to_string().white(),
tc("⦊", col_punct()),
tc("labels", col_hint())
));
for label in &labels {
out.push_str(&format!(" {}", tc("(", col_punct())));
out.push_str(&format!(
"{}{}{}{}{}{}\n",
trc("d", col_text()),
tc("3", col_punct()),
tc("⦉", col_punct()),
label.name.len().to_string().white(),
tc("⦊", col_punct()),
label.name.white().bold()
));
if label.size == 0 {
if !label.inline_values.is_empty() {
out.push_str(" ");
out.push_str(&format!("{}", tc(":", col_punct())));
for (i, val) in label.inline_values.iter().enumerate() {
if i > 0 {
out.push_str(&format!("{}", tc(",", col_punct())));
}
out.push_str(&format!("{}", format_value_literal(val)));
}
out.push_str("\n");
}
} else {
out.push_str(&format!(
" {}{}{}{}{}\n",
trc("o", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
label.offset.to_string().white(),
tc("⦊", col_punct())
));
out.push_str(&format!(
" {}{}{}{}{}\n",
trc("b", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
label.size.to_string().white(),
tc("⦊", col_punct())
));
out.push_str(&format!(
" {}{}{}{}{}\n",
trc("n", col_uint()),
tc("3", col_punct()),
tc("⦉", col_punct()),
label.child_count.to_string().white(),
tc("⦊", col_punct())
));
}
out.push_str(&format!(" {}\n", tc(")", col_punct())));
}
let has_nonempty_sections = labels.iter().any(|l| l.size > 0);
if has_nonempty_sections {
out.push_str(&format!("{}{}\n", tc(">", col_tree()), tc("╮", col_tree())));
} else {
out.push_str(&format!("{}\n", tc(">", col_tree())));
}
let nonempty_labels: Vec<_> = labels.iter().filter(|l| l.size > 0).collect();
for (i, label) in nonempty_labels.iter().enumerate() {
let is_last = i == nonempty_labels.len() - 1;
let connector = if is_last {
format!(" {}", tree_corner())
} else {
format!(" {}", tree_tee())
};
if label.size < 1024 * 1024 {
out.push_str(&format!("{}{}\n", connector, tc("[", col_tree())));
} else {
out.push_str(&format!(
"{}{}{} {}{}{}{}{}{}{}b{}{}\n",
connector,
tc("[", col_tree()),
label.name.white().bold(),
tc("n", col_label()),
tc("⦉", col_comment()),
trc(label.child_count.to_string(), col_size()),
tc("⦊", col_comment()),
" ".normal(),
tc("b", col_label()),
tc("⦉", col_comment()),
trc(label.size.to_string(), col_size()),
tc("⦊", col_comment())
));
}
let field_prefix = if is_last {
" "
} else {
&format!(" {} ", tree_vert())
};
let fields_result = if label.child_count == 0 {
try_parse_section_fields(data, label.offset)
} else {
parse_section_fields(data, label)
};
match fields_result {
Ok(fields) if fields.is_empty() => {
let section_start = label.offset;
let section_end = section_start + label.size;
if section_end <= data.len() && section_end > section_start {
let section_data = &data[section_start..section_end];
let mut ptr = 0;
if ptr < section_data.len() && section_data[ptr] == b'[' {
ptr += 1;
if parse(section_data, &mut ptr).is_ok() {
if ptr < section_data.len() && section_data[ptr] == b']' {
} else {
let content_data = §ion_data[ptr..];
let hex_preview: String = content_data
.iter()
.take(32)
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ");
let suffix = if content_data.len() > 32 { "..." } else { "" };
out.push_str(&format!(
"{} {}{}\n",
field_prefix,
trc(hex_preview, col_hint()),
suffix
));
}
}
}
}
}
Ok(fields) => {
for (j, field) in fields.iter().enumerate() {
let is_field_last = j == fields.len() - 1;
let field_connector = if is_field_last {
tree_corner_line()
} else {
tree_tee_line()
};
let continuation_bar = if is_field_last {
" "
} else {
&format!("{} ", tree_vert())
};
let name_literal = format_value_literal(&VsfType::d(field.name.clone()));
let values_literal: Vec<String> = field
.values
.iter()
.map(|v| format_value_literal(v))
.collect();
if values_literal.len() == 1 {
let val = &values_literal[0];
if val.contains('\n') {
let hex_indent = format!("{}{} ", field_prefix, continuation_bar);
let formatted =
val.replace('\n', &format!("\n{}", hex_indent));
out.push_str(&format!(
"{}{}{}{} {} {}{}\n",
field_prefix,
field_connector,
tc("(", col_tree()),
name_literal,
tc(":", col_tree()),
formatted,
tc(")", col_tree()),
));
} else {
out.push_str(&format!(
"{}{}{}{} {} {}{}\n",
field_prefix,
field_connector,
tc("(", col_tree()),
name_literal,
tc(":", col_tree()),
val,
tc(")", col_tree()),
));
}
} else {
out.push_str(&format!(
"{}{}{}{}:\n",
field_prefix,
field_connector,
tc("(", col_tree()),
name_literal,
));
let mut line_buffer = String::new();
let mut prev_was_opcode = false;
for (k, val_vsf) in field.values.iter().enumerate() {
let val = &values_literal[k];
let is_val_last = k == values_literal.len() - 1;
let is_opcode = matches!(val_vsf, VsfType::op(_, _));
if is_opcode && !line_buffer.is_empty() {
let lines: Vec<&str> = line_buffer.lines().collect();
for (i, line) in lines.iter().enumerate() {
if i == 0 {
out.push_str(&format!(" {}\n", line));
} else {
out.push_str(&format!(" {}\n", line));
}
}
line_buffer.clear();
}
if prev_was_opcode && !is_opcode && !line_buffer.is_empty() {
line_buffer.push('\n');
}
line_buffer.push_str(val);
prev_was_opcode = is_opcode;
if is_val_last {
let lines: Vec<&str> = line_buffer.lines().collect();
if lines.is_empty() {
out.push_str(&format!(" {}\n", tc(")", col_tree())));
} else {
for (i, line) in lines.iter().enumerate() {
if i == lines.len() - 1 {
out.push_str(&format!(
" {}{}\n",
line,
tc(")", col_tree())
));
} else {
out.push_str(&format!(" {}\n", line));
}
}
}
}
}
}
}
}
Err(e) => {
out.push_str(&format!("{} <parse error: {}>\n", field_prefix, e));
let section_start = label.offset;
let section_end = section_start + label.size;
if section_end <= data.len() && section_end > section_start {
let section_data = &data[section_start..section_end];
let hex_preview: String = section_data
.iter()
.take(32)
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ");
let suffix = if section_data.len() > 32 { "..." } else { "" };
out.push_str(&format!(
"{} {}{}\n",
field_prefix,
trc(hex_preview, col_hint()),
suffix
));
}
}
}
let tree_suffix = if is_last {
" ".to_string()
} else {
format!(" {} ", tree_vert())
};
out.push_str(&format!(
"{}{}\n",
tree_suffix,
tc("]", col_tree())
));
if !is_last {
out.push_str(&format!(" {}\n", tree_vert()));
}
}
Ok(out)
}
pub fn inspect_section(data: &[u8]) -> Result<String, String> {
if data.is_empty() || data[0] != b'[' {
return Err("Not a section fragment (doesn't start with '[')".into());
}
let mut out = String::new();
let mut pointer = 1usize;
let section_name = match parse(data, &mut pointer) {
Ok(VsfType::d(name)) => name,
Ok(_) => return Err("Expected d type for section name".into()),
Err(e) => return Err(format!("Failed to parse section name: {}", e)),
};
let mut length_hint: Option<usize> = None;
let mut count_hint: Option<usize> = None;
if pointer < data.len() && data[pointer] == b'n' {
match parse(data, &mut pointer) {
Ok(VsfType::n(count)) => count_hint = Some(count),
_ => {}
}
if pointer < data.len() && data[pointer] == b'b' {
match parse(data, &mut pointer) {
Ok(VsfType::b(len, _)) => length_hint = Some(len),
_ => {}
}
}
}
let mut header = format!("{}{}", tc("[", col_label()), section_name.white().bold());
if let Some(len) = length_hint {
header.push_str(&format!(
" {} {}",
len.to_string().white(),
tc("Bytes", col_label())
));
}
if let Some(count) = count_hint {
header.push_str(&format!(
" {} {}",
count.to_string().white(),
tc(if count == 1 { "field" } else { "fields" }, col_label())
));
}
header.push('\n');
out.push_str(&header);
let mut fields: Vec<VsfField> = Vec::new();
while pointer < data.len() && data[pointer] != b']' {
if data[pointer] == b'(' {
match VsfField::parse(data, &mut pointer) {
Ok(field) => fields.push(field),
Err(e) => {
out.push_str(&format!(" {} <parse error: {}>\n", tree_corner(), e));
break;
}
}
} else {
pointer += 1;
}
}
let comma = tc(",", col_label()).to_string();
let pipe = tc("┃", col_tree()).to_string();
for (i, field) in fields.iter().enumerate() {
let is_last_field = i == fields.len() - 1;
let connector = if is_last_field {
tree_corner_line()
} else {
tree_tee_line()
};
let continuation = if is_last_field {
" ".to_string()
} else {
format!("{} ", pipe)
};
let name_literal = format_value_literal(&VsfType::d(field.name.clone()));
out.push_str(&format!(
" {} {}{} {} ",
connector,
tc("(", col_label()),
name_literal,
tc(":", col_label())
));
for (vi, val) in field.values.iter().enumerate() {
let is_last_val = vi == field.values.len() - 1;
let val_str = format_value_literal(val);
if vi == 0 {
if val_str.contains('\n') {
let parts: Vec<&str> = val_str.split('\n').collect();
out.push_str(parts[0]);
out.push('\n');
for (hi, hex_line) in parts[1..].iter().enumerate() {
out.push_str(&format!(" {} {}", continuation, hex_line));
if hi == parts.len() - 2 {
if !is_last_val {
out.push_str(&comma);
}
}
out.push('\n');
}
} else {
out.push_str(&val_str);
if !is_last_val {
out.push_str(&comma);
}
out.push('\n');
}
} else {
if val_str.contains('\n') {
let parts: Vec<&str> = val_str.split('\n').collect();
out.push_str(&format!(" {} {}", continuation, parts[0]));
out.push('\n');
for (hi, hex_line) in parts[1..].iter().enumerate() {
out.push_str(&format!(" {} {}", continuation, hex_line));
if hi == parts.len() - 2 && !is_last_val {
out.push_str(&comma);
}
out.push('\n');
}
} else {
out.push_str(&format!(" {} {}", continuation, val_str));
if !is_last_val {
out.push_str(&comma);
}
out.push('\n');
}
}
}
out.push_str(&format!(" {} {}\n", continuation, tc(")", col_label())));
}
out.push_str(&format!("{}\n", tc("]", col_tree())));
if length_hint.is_some() || count_hint.is_some() {
let actual_len = if pointer < data.len() && data[pointer] == b']' {
pointer + 1 } else {
pointer
};
let mut validation = String::new();
let mut valid = true;
if let Some(expected_len) = length_hint {
if actual_len == expected_len {
validation.push_str(
&trc(format!(" {}B", actual_len), col_pass()).to_string(),
);
} else {
validation.push_str(
&trc(format!(" {}B/{}", actual_len, expected_len), col_fail()).to_string(),
);
valid = false;
}
}
if let Some(expected_count) = count_hint {
validation.push_str(
&trc(format!(" n={}", expected_count), col_hint()).to_string(),
);
}
if valid {
out.push_str(&format!(" {}\n", tc("✓", col_pass())));
} else {
out.push_str(&format!(
" {} MISMATCH{}\n",
tc("✗", col_fail()),
validation
));
}
}
Ok(out)
}
pub fn hex_dump(data: &[u8]) -> String {
let mut out = String::new();
for (i, chunk) in data.chunks(16).enumerate() {
out.push_str(&format!("{:08x}: ", i * 16));
for (j, byte) in chunk.iter().enumerate() {
if j == 8 {
out.push(' ');
}
out.push_str(&format!("{:02x} ", byte));
}
let padding = 16 - chunk.len();
for j in 0..padding {
if chunk.len() + j == 8 {
out.push(' ');
}
out.push_str(" ");
}
out.push(' ');
for byte in chunk {
if *byte >= 0x20 && *byte < 0x7f {
out.push(*byte as char);
} else {
out.push('.');
}
}
out.push('\n');
}
out
}