mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Godot scene parser
//!
//! Functions for parsing .tscn file format into structured data.

use super::types::{ExtResource, GodotScene, SceneNode, SubResource};
use anyhow::Result;
use std::collections::HashMap;

impl GodotScene {
    /// Parse .tscn content
    pub fn parse(content: &str) -> Result<Self> {
        let mut scene = Self::new();
        let mut current_section = String::new();
        let mut current_properties: HashMap<String, String> = HashMap::new();

        for line in content.lines() {
            let line = line.trim();

            // Skip empty lines and comments
            if line.is_empty() || line.starts_with(';') {
                continue;
            }

            // Section headers
            if line.starts_with('[') && line.ends_with(']') {
                // Process previous section
                if !current_section.is_empty() {
                    process_section(&current_section, &current_properties, &mut scene)?;
                }

                // Extract section header and inline attributes
                let section_content = &line[1..line.len() - 1];

                // Parse inline attributes (e.g., [gd_scene load_steps=2 format=3])
                let parts: Vec<&str> = section_content.split_whitespace().collect();
                current_section = parts[0].to_string();
                current_properties.clear();

                // Parse inline key=value pairs
                for part in &parts[1..] {
                    if let Some(eq_pos) = part.find('=') {
                        let key = part[..eq_pos].to_string();
                        let value = part[eq_pos + 1..].to_string();
                        current_properties.insert(key, value);
                    }
                }
                continue;
            }

            // Properties (key = value)
            if let Some(eq_pos) = line.find('=') {
                let key = line[..eq_pos].trim().to_string();
                let value = line[eq_pos + 1..].trim().to_string();
                current_properties.insert(key, value);
            }
        }

        // Process final section
        if !current_section.is_empty() {
            process_section(&current_section, &current_properties, &mut scene)?;
        }

        Ok(scene)
    }
}

/// Process a parsed section
fn process_section(section: &str, properties: &HashMap<String, String>, scene: &mut GodotScene) -> Result<()> {
    if section.starts_with("gd_scene") {
        // Parse header
        if let Some(format_str) = properties.get("format") {
            scene.format_version = format_str.parse().unwrap_or(3);
        }
        if let Some(load_steps_str) = properties.get("load_steps") {
            scene.load_steps = load_steps_str.parse().unwrap_or(1);
        }
        scene.uid = properties.get("uid").map(|s| s.trim_matches('"').to_string());
    } else if section.starts_with("ext_resource") {
        // Parse external resource
        let resource_type = properties
            .get("type")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();
        let path = properties
            .get("path")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();
        let id = properties
            .get("id")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();

        scene.ext_resources.push(ExtResource {
            id,
            resource_type,
            path,
        });
    } else if section.starts_with("sub_resource") {
        // Parse sub-resource
        let resource_type = properties
            .get("type")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();
        let id = properties
            .get("id")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();

        let mut props = properties.clone();
        props.remove("type");
        props.remove("id");

        scene.sub_resources.push(SubResource {
            id,
            resource_type,
            properties: props,
        });
    } else if section.starts_with("node") {
        // Parse node
        let name = properties
            .get("name")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();
        let node_type = properties
            .get("type")
            .map(|s| s.trim_matches('"').to_string())
            .unwrap_or_default();
        let parent = properties.get("parent").map(|s| s.trim_matches('"').to_string());
        let instance = properties.get("instance").map(|s| s.trim_matches('"').to_string());
        let script = properties.get("script").map(|s| s.to_string());

        let mut props = properties.clone();
        props.remove("name");
        props.remove("type");
        props.remove("parent");
        props.remove("instance");
        props.remove("script");

        scene.nodes.push(SceneNode {
            name,
            node_type,
            parent,
            instance,
            script,
            properties: props,
        });
    }

    Ok(())
}