resource-fork-types 0.1.0

Support for reading common resource fork types in rust
Documentation
use change_case::*;
use fourcc_rs::fourcc;
use macintosh_utils::FourCC;

use super::{Template, TemplateItem, TemplateType};

pub(crate) struct TemplateSerializer {
    supporting_types: Vec<String>,
}

impl TemplateSerializer {
    pub fn new() -> Self {
        Self {
            supporting_types: Vec::new(),
        }
    }

    pub fn serialize(&mut self, tpl: &Template) -> String {
        let struct_name = pascal_case(&tpl.0.name());
        let iter = tpl.1.clone().into_iter().peekable();
        let fields: Vec<String> = self.build_struct_fields(iter);

        format!(
            "use binrw::binread;
use resource_fork::Resource;

{}

#[binread]
#[derive(Debug, serde::Serialize, Resource)]
#[resource(code = \"{}\")]
#[br(big)]
pub struct {} {{
    {}
}}
",
            self.supporting_types.join("\n\n"),
            tpl.0.name(),
            struct_name,
            fields.join(",\n    ")
        )
    }

    fn build_struct_fields<Iter: Iterator<Item = TemplateItem>>(
        &mut self,
        mut iter: Iter,
    ) -> Vec<String> {
        let mut fields = Vec::new();

        loop {
            let Some((label, type_name)) = iter.next() else {
                return fields;
            };

            let mut current = String::new();
            let field_index = fields.len();
            let field_name = if label.is_empty() {
                format!("field_{}", field_index)
            } else {
                snake_case(&label)
            };

            if type_name == fourcc!("BOOL") {
                if !label.is_empty() {
                    current.push_str(format!("/// {}\n    ", label.replace("\r", "")).as_str());
                }
                current.push_str("#[br(map(|input:u16| input != 0))]\n    ");
                current.push_str(format!("pub {}: bool", field_name).as_str());
                fields.push(current);
                continue;
            }

            if let Some(simple_type) = type_name.simple_type() {
                if !label.is_empty() {
                    current.push_str(format!("/// {}\n    ", label.replace("\r", "")).as_str());
                }
                current.push_str(format!("pub {}: {}", field_name, simple_type).as_str());
                fields.push(current);
                continue;
            }

            if type_name.is_list() {
                let Some((_, fourcc!("LSTC"))) = iter.next() else {
                    panic!("Could not find start of counted list item");
                };
                let mut children: Vec<(String, FourCC)> = Vec::new();
                let mut stack = 1;
                loop {
                    let Some((label, item)) = iter.next() else {
                        panic!("Found unterminated list");
                    };

                    if item == fourcc!("LSTE") {
                        stack -= 1;
                    }

                    if stack == 0 {
                        break;
                    }

                    if item == fourcc!("LSTC") || item == fourcc!("LSTB") {
                        stack += 1;
                    }

                    children.push((label, item));
                }

                let item_struct_name = format!("{}Item", pascal_case(&field_name));
                let item_fields = self.build_struct_fields(children.into_iter());

                self.supporting_types.push(format!(
                    "
#[binread]
#[derive(Debug, serde::Serialize)]
#[br(big)]
pub struct {} {{
    {}
}}",
                    item_struct_name,
                    item_fields.join(",\n    ")
                ));

                let count_type = type_name.list_count_type().unwrap_or("todo!()");

                current.push_str("#[br(temp)]\n    ");
                current.push_str(format!("count_{}: {},\n    ", field_index, count_type).as_str());

                if !label.is_empty() {
                    current.push_str(format!("/// {}\n    ", label.replace("\r", "")).as_str());
                }
                current.push_str(format!("#[br(count(count_{}))]\n    ", field_index).as_str());
                current.push_str(format!("pub {}: Vec<{}>", field_name, item_struct_name).as_str());
                fields.push(current);
                continue;
            }

            current.push_str(
                format!(
                    "pub {}: todo!(\"{} not supported yet\")",
                    field_name,
                    type_name.name()
                )
                .as_str(),
            );

            fields.push(current);
        }
    }
}