tson 0.1.2

Terse JSON - a compact binary JSON format for microcontrollers and constrained environments
Documentation
extern crate alloc;

mod error;
mod tson; // tson.rs re-exports all modules (structure, encode, decode, stream)

// Binary target module tree - mirrors lib.rs so `crate::encode` etc. resolve.
#[cfg(feature = "json")]
mod compile;
mod decode;
#[cfg(feature = "json")]
mod decompile;
mod encode;
mod stream;
mod structure;

use std::io::Read;

/// Recursively format a TsonData value for display.
fn format_tson_data(data: &tson::TsonData, indent: usize) -> String {
    use tson::TsonData;
    match data {
        TsonData::Null => "null".to_string(),
        TsonData::Bool(b) => format!("{}", b),
        TsonData::Int(i) => format!("{}", i),
        TsonData::UInt(u) => format!("{}", u),
        TsonData::Float(f) => format!("{:.2}", f),
        TsonData::String(s) => {
            if s.len() > 40 {
                format!("\"{}\"({}B)", &s[..40], s.len())
            } else {
                format!("\"{}\"", s)
            }
        }
        TsonData::StrRef(idx) => format!("StrRef({})", idx),
        TsonData::Array(_, _, items) => {
            let pad = "  ".repeat(indent + 1);
            let mut out = format!("Array[{}]", items.len());
            for item in items.iter().take(5) {
                out.push_str(&format!(
                    "\n{}  {}",
                    pad,
                    format_tson_data(item, indent + 1)
                ));
            }
            if items.len() > 5 {
                out.push_str(&format!("\n{}  … +{} more", pad, items.len() - 5));
            }
            out
        }
        TsonData::Object(_, fields) => {
            let pad = "  ".repeat(indent + 1);
            let mut out = format!("Object({} fields)", fields.len());
            for field in fields.iter().take(8) {
                out.push_str(&format!(
                    "\n{}  {}",
                    pad,
                    format_tson_data(field, indent + 1)
                ));
            }
            if fields.len() > 8 {
                out.push_str(&format!("\n{}  … +{} more", pad, fields.len() - 8));
            }
            out
        }
    }
}

fn is_json_file_name(file_name: &str) -> bool {
    file_name.ends_with(".json")
}

fn is_tson_file_name(file_name: &str) -> bool {
    file_name.ends_with(".tson")
}

fn print_usage(program: &str) {
    eprintln!("Usage:");
    eprintln!("  {program} <file.json>              Compile JSON -> TSON binary");
    eprintln!("  {program} <file.tson>              Decompile TSON -> JSON text");
    eprintln!("  {program} -s <file.tson>           Stream-print entries (debug)");
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        print_usage(&args[0]);
        std::process::exit(1);
    }

    let stream_mode = args.len() > 1 && args[1] == "-s";
    let target_idx = if stream_mode { 2 } else { 1 };

    if target_idx >= args.len() {
        print_usage(&args[0]);
        std::process::exit(1);
    }

    let tson_file = &args[target_idx];

    if !std::path::Path::new(tson_file).exists() {
        eprintln!("File not found: {tson_file}");
        std::process::exit(1);
    }
    if !std::path::Path::new(tson_file).is_file() {
        eprintln!("Not a file: {tson_file}");
        std::process::exit(1);
    }

    // Streaming mode
    if stream_mode {
        let mut buf = Vec::new();
        match std::fs::File::open(tson_file) {
            Ok(mut f) => {
                if let Err(e) = f.read_to_end(&mut buf) {
                    eprintln!("IO error: {e}");
                    std::process::exit(1);
                }
            }
            Err(e) => {
                eprintln!("Failed to open file: {e}");
                std::process::exit(1);
            }
        }

        match tson::TsonStreamReader::new(&buf) {
            Ok(reader) => {
                // Header
                println!("Header version: {}", reader.header().version);
                println!(
                    "  Def offset: {}   Dict offset: {}   Data offset: {}",
                    reader.header().blk_definition,
                    reader.header().blk_dict,
                    reader.header().blk_data
                );

                // Definitions
                println!("Definitions: {}", reader.definitions().len());
                for def in reader.definitions() {
                    print!("  #{}: {:?}", def.index, def.def_type);
                    if let Some(ref fields) = def.fields {
                        print!(" ({} fields)", fields.len());
                        for (name, ftype) in fields {
                            print!(" {}:{:?}", name, ftype);
                        }
                    }
                    if let Some(et) = def.elem_type {
                        print!(" <{:?}>", et);
                    }
                    println!();
                }

                // Dict block
                let dict = reader.dict();
                println!("Dict entries: {}", dict.len());
                for (i, s) in dict.iter().enumerate() {
                    let truncated = if s.len() > 60 {
                        format!("{}…({}B)", &s[..60], s.len())
                    } else {
                        s.clone()
                    };
                    println!("  [{}] {}", i, truncated);
                }

                // Data entries (stream)
                println!("Entries (streaming):");
                let mut entry_idx = 0u32;
                for result in reader {
                    match result {
                        Ok(chunk) => {
                            println!(
                                "  [{}] def=#{}: {}",
                                entry_idx,
                                chunk.definition_index,
                                format_tson_data(&chunk.data, 4)
                            );
                            entry_idx += 1;
                        }
                        Err(e) => {
                            eprintln!("  Stream error: {e}");
                            break;
                        }
                    }
                }
            }
            Err(e) => {
                eprintln!("Failed to parse TSON: {e}");
                std::process::exit(1);
            }
        }
        return;
    }

    // Compile / Decompile mode
    let file = match std::fs::File::open(tson_file) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("Failed to open file: {e}");
            std::process::exit(1);
        }
    };

    if is_json_file_name(tson_file) {
        #[cfg(feature = "json")]
        {
            match tson::compile_json_file(file) {
                Ok(doc) => {
                    // Encode to binary and write alongside the JSON file
                    let tson_path = tson_file.replace(".json", ".tson");
                    match tson::to_bytes(&doc) {
                        Ok(binary) => {
                            if let Err(e) = std::fs::write(&tson_path, &binary) {
                                eprintln!("Failed to write {tson_path}: {e}");
                                std::process::exit(1);
                            }
                            println!(
                                "Compiled {} -> {} ({} bytes, {} defs, {} entries)",
                                tson_file,
                                tson_path,
                                binary.len(),
                                doc.definitions.len(),
                                doc.data.len()
                            );
                        }
                        Err(e) => {
                            eprintln!("Encode error: {e}");
                            std::process::exit(1);
                        }
                    }
                }
                Err(e) => {
                    eprintln!("Compile error: {e}");
                    std::process::exit(1);
                }
            }
        }
        #[cfg(not(feature = "json"))]
        {
            eprintln!("JSON compilation requires the 'json' feature (build without --no-default-features)");
            std::process::exit(1);
        }
    } else if is_tson_file_name(tson_file) {
        #[cfg(feature = "json")]
        {
            match tson::decompile_tson_file(file) {
                Ok(value) => match serde_json::to_string_pretty(&value) {
                    Ok(json_str) => println!("{json_str}"),
                    Err(e) => {
                        eprintln!("JSON serialization error: {e}");
                        std::process::exit(1);
                    }
                },
                Err(e) => {
                    eprintln!("Decompile error: {e}");
                    std::process::exit(1);
                }
            }
        }
        #[cfg(not(feature = "json"))]
        {
            let mut buf = Vec::new();
            use std::io::Read;
            if let Ok(mut f) = std::fs::File::open(tson_file) {
                let _ = f.read_to_end(&mut buf);
            }
            match tson::from_bytes(&buf) {
                Ok(doc) => {
                    println!(
                        "TSON decoded: {} entries, {} definitions",
                        doc.data.len(),
                        doc.definitions.len()
                    );
                }
                Err(e) => {
                    eprintln!("Decompile error: {e}");
                    std::process::exit(1);
                }
            }
        }
    } else {
        eprintln!("Unknown file extension (use .json or .tson): {tson_file}");
        std::process::exit(1);
    }
}