ldtk-codegen 1.0.2

Generate typed rust code from LDtk Project
use crate::definitions::*;

fn preprocess_header(header: &str, preferences: &Preferences) -> String {
    header.replace(
        "[SERDE]",
        if preferences.serde {
            "serde::Serialize, serde::Deserialize, "
        } else {
            ""
        },
    )
}

pub fn generate_defs(
    preferences: &Preferences,
    definitions: &mut RsDefinitions,
    project: &LdtkJson,
    code: &mut Scope,
) -> Result<()> {
    let header = preprocess_header(include_str!("templates/header.rs"), preferences);
    let header = header.replace(
        "define_vectors!();",
        &if let Some(vector_type) = &preferences.vector {
            format!(
                            r"
type UVec2 = {};
type IVec2 = {};
type FVec2 = {};
",
            vector_type.replace("<T>", "<u32>"),
            vector_type.replace("<T>", "<i32>"),
            vector_type.replace("<T>", "<f32>"),
                        )
        } else {
            preprocess_header(include_str!("templates/math.rs"), preferences)
        },
    );
    let header = header.replace(
        "define_colors!();",
        &if let Some(color_type) = &preferences.color {
            format!("type Color = {color_type};")
        } else {
            preprocess_header(include_str!("templates/color.rs"), preferences)
        },
    );
    macro_rules! replace_get_layer {
        ($header: ident, $mutability: literal) => {
            $header.replace(
                concat!(
                    "            LAYER_INDEX => GET_LAYER",
                    $mutability,
                    "!(),\n"
                ),
                &project
                    .defs
                    .layers
                    .iter()
                    .filter(|layer| matches!(layer.purple_type, Type::Entities))
                    .enumerate()
                    .map(|(index, layer)| {
                        format!(
                            concat!(
                                "            {} => level.{}.get",
                                $mutability,
                                "(self.entity),\n"
                            ),
                            index,
                            preferences.to_case(&layer.identifier, Case::Snake)
                        )
                    })
                    .collect::<String>(),
            )
        };
    }
    let header = replace_get_layer!(header, "");
    let header = replace_get_layer!(header, "_mut");
    code.raw(header);

    for tileset in &project.defs.tilesets {
        definitions.tilesets.insert(
            tileset.uid,
            RsTilesetDefinition {
                tile_size: tileset.tile_grid_size as _,
            },
        );
    }

    code.raw("/* --- Definitions --- */");
    code.raw("/* Enums */");
    for enum_json in &project.defs.enums {
        let enum_rs = code.new_enum(&enum_json.identifier).vis("pub");
        derive_rust_object!(enum_rs preferences.serde, Hash !partial Eq, Ord);
        for value in &enum_json.values {
            enum_rs.new_variant(&value.id);
        }

        macro_rules! generate_get_const {
            ($impl: ident $fn: ident -> $ret: ty; $variant: ident => $line: expr) => {
                $impl
                    .new_fn(stringify!($fn))
                    .vis("pub")
                    .arg_ref_self()
                    .ret(stringify!($ret))
                    .push_block({
                        let mut match_block = Block::new("match self");
                        for $variant in &enum_json.values {
                            match_block.line(format!("Self::{} => {},", $variant.id, $line));
                        }
                        match_block
                    });
            };
        }

        let enum_impl = code.new_impl(&enum_json.identifier);
        generate_get_const!(enum_impl color -> Option<Color>; variant => if variant.color >= 0 {
            format!("Some(<Color as ColorImpl>::from_hex({}))", variant.color)
        } else {
            "None".to_owned()
        });

        if let Some(tileset) = enum_json.icon_tileset_uid {
            enum_impl.associate_const("TILESET_ID", "TilesetID", tileset.to_string(), "pub");
            generate_get_const!(enum_impl icon -> Option<UVec2>; variant => if let Some(tile) = &variant.tile_rect {
                let tileset = definitions.tilesets.get(&tileset).context("Enum icon tileset was not found!")?;
                format!("Some(<UVec2 as VectorImpl>::new({} as _, {} as _))", tile.x as u32 / tileset.tile_size, tile.y as u32 / tileset.tile_size)
            } else {
                "None".to_owned()
            });
        }
    }
    Ok(())
}