appcui_proc_macro 0.2.7

Procedural macros for the AppCUI TUI framework.
Documentation
use super::control_builder::ControlBuilder;
use crate::parameter_parser::*;
use proc_macro::*;

static FLAGS: FlagsSignature = FlagsSignature::new(&[
    "ScrollBars",
    "SearchBar",
    "HideHeader",
    "ShowAddress",
    "ShowIntervalNames",
    "NoPanelDimming",
    "ShowAsciiStrings",
    "ShowUtf16AsciiStrings",
    "DecodeUTF8Characters",
    "ReadOnly",
    "TabSwitchesActivePanel",
]);

static OFFSET_FORMAT: FlagsSignature = FlagsSignature::new(&["Hex", "Dec"]);
static ENDIAN: FlagsSignature = FlagsSignature::new(&["Little", "Big"]);

static POSILITIONAL_PARAMETERS: &[PositionalParameter] = &[
    PositionalParameter::new("type", ParamType::String),
];
static NAMED_PARAMETERS: &[NamedParameter] = &[
    NamedParameter::new("type", "type", ParamType::String),
    NamedParameter::new("class", "type", ParamType::String),
    NamedParameter::new("flags", "flags", ParamType::Flags),
    NamedParameter::new("left-scroll-margin", "lsm", ParamType::Integer),
    NamedParameter::new("lsm", "lsm", ParamType::Integer),
    NamedParameter::new("top-scroll-margin", "tsm", ParamType::Integer),
    NamedParameter::new("tsm", "tsm", ParamType::Integer),
    NamedParameter::new("offset-format", "offset", ParamType::String),
    NamedParameter::new("offset", "offset", ParamType::String),
    NamedParameter::new("address-format", "offset", ParamType::String),
    NamedParameter::new("endian", "endian", ParamType::String),
    NamedParameter::new("format", "format", ParamType::String),
    NamedParameter::new("data-format", "format", ParamType::String),
    NamedParameter::new("representation", "format", ParamType::String),
    NamedParameter::new("columns", "columns", ParamType::String),
    NamedParameter::new("columns-count", "columns", ParamType::String),
    NamedParameter::new("codepage", "codepage", ParamType::String),
    NamedParameter::new("cp", "codepage", ParamType::String),
    NamedParameter::new("address-width", "address-width", ParamType::Integer),
    NamedParameter::new("aw", "address-width", ParamType::Integer),
    NamedParameter::new("address-name", "address-name", ParamType::String),
    NamedParameter::new("address", "address-name", ParamType::String),
    NamedParameter::new("interval-name-width", "interval-name-width", ParamType::Integer),
    NamedParameter::new("inw", "interval-name-width", ParamType::Integer),
    NamedParameter::new("interval-name-title", "interval-name-title", ParamType::String),
    NamedParameter::new("interval-title", "interval-name-title", ParamType::String),
    NamedParameter::new("intervals", "intervals", ParamType::String),
    NamedParameter::new("selection-start", "selection-start", ParamType::String),
    NamedParameter::new("selection-end", "selection-end", ParamType::String),
];

fn data_representation_format_code(value: &str) -> String {
    if value.contains("::") {
        return value.to_string();
    }
    match value {
        "Oct" => "bufferview::DataRepresentationFormat::Oct".to_string(),
        "Bin" => "bufferview::DataRepresentationFormat::Bin".to_string(),
        "Char" => "bufferview::DataRepresentationFormat::Char".to_string(),
        "Hex" | "Hex(Byte)" => {
            "bufferview::DataRepresentationFormat::Hex(bufferview::HexFormat::Byte)".to_string()
        }
        "Hex(Word)" => "bufferview::DataRepresentationFormat::Hex(bufferview::HexFormat::Word)".to_string(),
        "Hex(DWord)" => "bufferview::DataRepresentationFormat::Hex(bufferview::HexFormat::DWord)".to_string(),
        "Hex(QWord)" => "bufferview::DataRepresentationFormat::Hex(bufferview::HexFormat::QWord)".to_string(),
        "UInt(U8)" | "UInt" => {
            "bufferview::DataRepresentationFormat::UInt(bufferview::UIntFormat::U8)".to_string()
        }
        "UInt(U16)" => "bufferview::DataRepresentationFormat::UInt(bufferview::UIntFormat::U16)".to_string(),
        "UInt(U32)" => "bufferview::DataRepresentationFormat::UInt(bufferview::UIntFormat::U32)".to_string(),
        "UInt(U64)" => "bufferview::DataRepresentationFormat::UInt(bufferview::UIntFormat::U64)".to_string(),
        "Int(I8)" | "Int" => "bufferview::DataRepresentationFormat::Int(bufferview::IntFormat::I8)".to_string(),
        "Int(I16)" => "bufferview::DataRepresentationFormat::Int(bufferview::IntFormat::I16)".to_string(),
        "Int(I32)" => "bufferview::DataRepresentationFormat::Int(bufferview::IntFormat::I32)".to_string(),
        "Int(I64)" => "bufferview::DataRepresentationFormat::Int(bufferview::IntFormat::I64)".to_string(),
        "Float(Scientific32)" | "Float" => {
            "bufferview::DataRepresentationFormat::Float(bufferview::FloatFormat::Scientific32)".to_string()
        }
        "Float(Scientific64)" => {
            "bufferview::DataRepresentationFormat::Float(bufferview::FloatFormat::Scientific64)".to_string()
        }
        "Float(E4M3)" => "bufferview::DataRepresentationFormat::Float(bufferview::FloatFormat::E4M3)".to_string(),
        "Float(E5M2)" => "bufferview::DataRepresentationFormat::Float(bufferview::FloatFormat::E5M2)".to_string(),
        other => panic!(
            "Unknown data format: '{other}' !\nSupported shorthand values are: Hex, Hex(Word), Oct, Bin, Char, UInt(U8), Int(I8), Float(Scientific32), ..."
        ),
    }
}

fn columns_count_code(value: &str) -> String {
    if value.contains("::") {
        return value.to_string();
    }
    if value == "Auto" {
        return "bufferview::ColumnsCount::Auto".to_string();
    }
    if let Some(n) = value.strip_prefix("Fixed(").and_then(|s| s.strip_suffix(')')) {
        return format!("bufferview::ColumnsCount::Fixed({n})");
    }
    if let Ok(n) = value.parse::<u8>() {
        return format!("bufferview::ColumnsCount::Fixed({n})");
    }
    panic!("Unknown columns value: '{value}' ! Use Auto, Fixed(N), or an integer from 1 to 255.");
}

fn codepage_code(value: &str) -> String {
    if value.contains("::") {
        return value.to_string();
    }
    match value {
        "Default" => r#"bufferview::Codepage::new("Default")"#.to_string(),
        "ASCII" => "bufferview::Codepage::ASCII".to_string(),
        "CP437" => "bufferview::Codepage::CP437".to_string(),
        "WINDOWS_1252" => "bufferview::Codepage::WINDOWS_1252".to_string(),
        other => format!(r#"bufferview::Codepage::new("{other}")"#),
    }
}

pub(crate) fn create(input: TokenStream) -> TokenStream {
    let mut cb = ControlBuilder::new("bufferview", input, POSILITIONAL_PARAMETERS, NAMED_PARAMETERS, true);
    cb.init_control_with_template("BufferView", "new", "type");
    cb.add_layout();
    cb.add_flags_parameter("flags", "bufferview::Flags", &FLAGS);
    cb.finish_control_initialization();

    if cb.has_parameter("offset") {
        cb.add("control.set_offset_format(");
        cb.add_enum_parameter("offset", "bufferview::OffsetFormat", &OFFSET_FORMAT, None);
        cb.add_line(");");
    }

    if cb.has_parameter("endian") {
        cb.add("control.set_endian(");
        cb.add_enum_parameter("endian", "bufferview::Endian", &ENDIAN, None);
        cb.add_line(");");
    }

    if cb.has_parameter("format") {
        let format = cb.get_value("format").unwrap().to_string();
        cb.add_line(
            format!(
                "control.set_data_representation_format({});",
                data_representation_format_code(&format)
            )
            .as_str(),
        );
    }

    if cb.has_parameter("columns") {
        if let Some(columns) = cb.get_i32("columns") {
            if !(1..=255).contains(&columns) {
                panic!("Parameter `columns` must be between 1 and 255, got {columns}");
            }
            cb.add_line(format!("control.set_columns_count(bufferview::ColumnsCount::Fixed({columns}));").as_str());
        } else {
            let columns = cb.get_value("columns").unwrap().to_string();
            cb.add_line(format!("control.set_columns_count({});", columns_count_code(&columns)).as_str());
        }
    }

    if cb.has_parameter("codepage") {
        let codepage = cb.get_value("codepage").unwrap().to_string();
        cb.add_line(format!("control.set_codepage({});", codepage_code(&codepage)).as_str());
    }

    if let Some(width) = cb.get_i32("address-width") {
        if width < 1 {
            panic!("Parameter `address-width` can not be less than 1");
        }
        cb.add_line(format!("control.set_address_width({width} as u32);").as_str());
    }

    if cb.has_parameter("address-name") {
        cb.add("control.set_address_name(");
        cb.add_string_parameter("address-name", None);
        cb.add_line(");");
    }

    if let Some(width) = cb.get_i32("interval-name-width") {
        if width < 1 {
            panic!("Parameter `interval-name-width` can not be less than 1");
        }
        cb.add_line(format!("control.set_interval_name_width({width} as u32);").as_str());
    }

    if cb.has_parameter("interval-name-title") {
        cb.add("control.set_interval_name_title(");
        cb.add_string_parameter("interval-name-title", None);
        cb.add_line(");");
    }

    if cb.has_parameter("intervals") {
        let intervals = cb.get_value("intervals").unwrap().to_string();
        cb.add_line(format!("control.set_intervals({intervals});").as_str());
    }

    if cb.has_parameter("selection-start") || cb.has_parameter("selection-end") {
        if !(cb.has_parameter("selection-start") && cb.has_parameter("selection-end")) {
            panic!("Both `selection-start` and `selection-end` must be provided together");
        }
        let start = cb.get_value("selection-start").unwrap().to_string();
        let end = cb.get_value("selection-end").unwrap().to_string();
        cb.add_line(format!("control.set_selection({start}, {end});").as_str());
    }

    cb.add_scroll_margin_setup("lsm", "tsm");
    cb.add_basecontrol_operations();
    cb.into()
}