mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Godot scene (.tscn) parsing and generation
//!
//! The .tscn format is a text-based format with sections:
//! - [gd_scene] header with metadata
//! - [ext_resource] external resource references
//! - [sub_resource] embedded resources
//! - [node] scene node hierarchy

mod parser;
mod types;

#[allow(unused_imports)]
pub use types::{ExtResource, GodotScene, SceneNode, SubResource};

use anyhow::{Context, Result};
use std::fs;
use std::path::Path;

impl GodotScene {
    /// Load scene from .tscn file
    pub fn load(path: &Path) -> Result<Self> {
        let content =
            fs::read_to_string(path).with_context(|| format!("Failed to read scene file: {}", path.display()))?;

        Self::parse(&content)
    }

    /// Save scene to .tscn file
    pub fn save(&self, path: &Path) -> Result<()> {
        let content = self.to_string();
        fs::write(path, content).with_context(|| format!("Failed to write scene file: {}", path.display()))?;
        Ok(())
    }

    /// Convert scene to .tscn format
    #[allow(clippy::inherent_to_string)]
    pub fn to_string(&self) -> String {
        let mut output = String::new();

        // Header
        let uid_part = self.uid.as_ref().map(|u| format!(" uid=\"{}\"", u)).unwrap_or_default();
        output.push_str(&format!(
            "[gd_scene load_steps={} format={}{}]\n\n",
            self.load_steps, self.format_version, uid_part
        ));

        // External resources
        for (i, res) in self.ext_resources.iter().enumerate() {
            output.push_str(&format!(
                "[ext_resource type=\"{}\" path=\"{}\" id=\"{}\"]\n",
                res.resource_type, res.path, res.id
            ));
            if i == self.ext_resources.len() - 1 {
                output.push('\n');
            }
        }

        // Sub-resources
        for res in &self.sub_resources {
            output.push_str(&format!(
                "[sub_resource type=\"{}\" id=\"{}\"]\n",
                res.resource_type, res.id
            ));
            for (key, value) in &res.properties {
                output.push_str(&format!("{} = {}\n", key, value));
            }
            output.push('\n');
        }

        // Nodes
        for node in &self.nodes {
            self.write_node(&mut output, node);
        }

        output
    }

    /// Write a single node to output
    fn write_node(&self, output: &mut String, node: &SceneNode) {
        output.push_str("[node");
        output.push_str(&format!(" name=\"{}\"", node.name));
        output.push_str(&format!(" type=\"{}\"", node.node_type));

        if let Some(parent) = &node.parent {
            output.push_str(&format!(" parent=\"{}\"", parent));
        }

        if let Some(instance) = &node.instance {
            output.push_str(&format!(" instance=ExtResource(\"{}\")", instance));
        }

        output.push_str("]\n");

        // Script
        if let Some(script) = &node.script {
            output.push_str(&format!("script = {}\n", script));
        }

        // Properties
        for (key, value) in &node.properties {
            output.push_str(&format!("{} = {}\n", key, value));
        }

        output.push('\n');
    }

    /// Add external resource
    pub fn add_ext_resource(&mut self, resource_type: &str, path: &str) -> String {
        let id = (self.ext_resources.len() + 1).to_string();
        self.ext_resources.push(ExtResource {
            id: id.clone(),
            resource_type: resource_type.to_string(),
            path: path.to_string(),
        });
        self.load_steps += 1;
        id
    }

    /// Add node to scene
    pub fn add_node(&mut self, node: SceneNode) {
        self.nodes.push(node);
    }

    /// Find node by name
    pub fn find_node(&self, name: &str) -> Option<&SceneNode> {
        self.nodes.iter().find(|n| n.name == name)
    }

    /// Find node by name (mutable)
    pub fn find_node_mut(&mut self, name: &str) -> Option<&mut SceneNode> {
        self.nodes.iter_mut().find(|n| n.name == name)
    }
}