base-d 3.0.34

Universal base encoder: Encode binary data to 33+ dictionaries including RFC standards, hieroglyphs, emoji, and more
Documentation
use crate::cli::{
    args::{SteleArgs, SteleCommand, SteleDecodeArgs, SteleEncodeArgs, SteleMode},
    global::GlobalArgs,
};
use base_d::{
    DetectedMode, DictionaryRegistry, decode_stele, decode_stele_path, detect_stele_mode,
    encode_markdown_stele, encode_markdown_stele_ascii, encode_markdown_stele_light,
    encode_markdown_stele_markdown, encode_markdown_stele_readable, encode_stele,
    encode_stele_ascii, encode_stele_light, encode_stele_path, encode_stele_readable,
};
use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;

pub fn handle(
    args: SteleArgs,
    _global: &GlobalArgs,
    _config: &DictionaryRegistry,
) -> Result<(), Box<dyn std::error::Error>> {
    match args.command {
        Some(SteleCommand::Encode(encode_args)) => handle_encode(encode_args),
        Some(SteleCommand::Decode(decode_args)) => handle_decode(decode_args),
        None => {
            // Implicit encode mode with top-level args
            let encode_args = SteleEncodeArgs {
                mode: args.mode.unwrap_or_default(),
                output: args.output,
                input: args.input,
                multiline: args.multiline,
                markdown: args.markdown,
            };
            handle_encode(encode_args)
        }
    }
}

fn handle_encode(args: SteleEncodeArgs) -> Result<(), Box<dyn std::error::Error>> {
    let input_text = read_input(args.input.as_deref())?;
    let minify = !args.multiline;

    // Auto-detect mode if set to Auto (only for JSON, not markdown)
    let mode = match args.mode {
        SteleMode::Auto if !args.markdown => {
            let detected = detect_stele_mode(input_text.trim());
            match detected {
                DetectedMode::Full => SteleMode::Full,
                DetectedMode::Path => SteleMode::Path,
            }
        }
        SteleMode::Auto => SteleMode::None, // Default to readable for markdown
        other => other,
    };

    let output = if args.markdown {
        // Markdown document → stele
        match mode {
            SteleMode::Auto => unreachable!("Auto should be resolved by now"),
            SteleMode::None => encode_markdown_stele_readable(input_text.trim(), minify)?,
            SteleMode::Light => encode_markdown_stele_light(input_text.trim(), minify)?,
            SteleMode::Full => encode_markdown_stele(input_text.trim(), minify)?,
            SteleMode::Path => {
                return Err("Path mode is not supported for markdown input".into());
            }
            SteleMode::Ascii => encode_markdown_stele_ascii(input_text.trim())?,
            SteleMode::Markdown => encode_markdown_stele_markdown(input_text.trim())?,
        }
    } else {
        // JSON → stele (existing behavior)
        match mode {
            SteleMode::Auto => unreachable!("Auto should be resolved by now"),
            SteleMode::None => encode_stele_readable(input_text.trim(), minify)?,
            SteleMode::Light => encode_stele_light(input_text.trim(), minify)?,
            SteleMode::Full => encode_stele(input_text.trim(), minify)?,
            SteleMode::Path => encode_stele_path(input_text.trim())?,
            SteleMode::Ascii => encode_stele_ascii(input_text.trim())?,
            SteleMode::Markdown => {
                return Err("Markdown mode requires --markdown flag (markdown input only)".into());
            }
        }
    };

    write_output(&output, args.output.as_ref())?;
    Ok(())
}

fn handle_decode(args: SteleDecodeArgs) -> Result<(), Box<dyn std::error::Error>> {
    let input_text = read_input(args.input.as_deref())?;
    let trimmed = input_text.trim();

    // Auto-detect path mode: has ┃ separators but no ◉ row markers
    let has_row_marker = trimmed.lines().any(|line| line.starts_with(""));
    let has_field_sep = trimmed.contains('');

    let is_path_mode = has_field_sep && !has_row_marker;

    let output = if is_path_mode {
        decode_stele_path(trimmed)?
    } else {
        decode_stele(trimmed, args.pretty)?
    };

    write_output(&output, args.output.as_ref())?;
    Ok(())
}

fn read_input(input: Option<&str>) -> Result<String, Box<dyn std::error::Error>> {
    if let Some(input_str) = input {
        // Try as file path first
        if let Ok(content) = fs::read_to_string(input_str) {
            Ok(content)
        } else {
            // Treat as literal input string
            Ok(input_str.to_string())
        }
    } else {
        // Read from stdin
        let mut buffer = String::new();
        io::stdin().read_to_string(&mut buffer)?;
        Ok(buffer)
    }
}

fn write_output(
    content: &str,
    output_path: Option<&PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
    if let Some(path) = output_path {
        fs::write(path, content.as_bytes())?;
    } else {
        println!("{}", content);
    }
    Ok(())
}