rustbridge-cli 0.6.2

Build tool and code generator for rustbridge
//! Code generation for host languages

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

/// Run the generate command
pub fn run(lang: &str, output: &str, manifest_path: Option<String>) -> Result<()> {
    let manifest_file = manifest_path.unwrap_or_else(|| "rustbridge.toml".to_string());

    println!("Generating {} bindings...", lang);
    println!("Manifest: {}", manifest_file);
    println!("Output: {}", output);

    // Load and validate manifest
    let manifest = Manifest::from_file(&manifest_file)?;
    manifest.validate()?;

    // Create output directory
    fs::create_dir_all(output).context("Failed to create output directory")?;

    // Generate based on language
    match lang.to_lowercase().as_str() {
        "java" => generate_java(&manifest, output)?,
        "kotlin" => generate_java(&manifest, output)?, // Same as Java for now
        "csharp" | "c#" => generate_csharp(&manifest, output)?,
        "python" => generate_python(&manifest, output)?,
        _ => anyhow::bail!(
            "Unsupported language: {}. Supported: java, csharp, python",
            lang
        ),
    }

    println!("\n✓ Generation complete!");
    Ok(())
}

/// Generate Java bindings
fn generate_java(manifest: &Manifest, output: &str) -> Result<()> {
    let plugin_name = &manifest.plugin.name;
    let class_name = to_pascal_case(plugin_name);

    // Create package directory
    let package_dir = Path::new(output).join("com/rustbridge/generated");
    fs::create_dir_all(&package_dir)?;

    // Generate plugin interface
    let interface_code = format!(
        r#"// Generated by rustbridge - DO NOT EDIT
package com.rustbridge.generated;

import com.rustbridge.core.Plugin;
import com.rustbridge.core.PluginException;

/**
 * {plugin_name} plugin interface
 * Version: {version}
 */
public interface {class_name} extends Plugin {{
{methods}
}}
"#,
        plugin_name = plugin_name,
        version = manifest.plugin.version,
        class_name = class_name,
        methods = generate_java_methods(manifest),
    );

    let interface_path = package_dir.join(format!("{}.java", class_name));
    fs::write(&interface_path, interface_code)?;
    println!("  Generated: {:?}", interface_path);

    // Generate loader class
    let loader_code = format!(
        r#"// Generated by rustbridge - DO NOT EDIT
package com.rustbridge.generated;

import com.rustbridge.core.PluginLoader;
import com.rustbridge.core.PluginConfig;

/**
 * Loader for {plugin_name} plugin
 */
public class {class_name}Loader {{
    private static final String LIBRARY_NAME = "{plugin_name}";

    /**
     * Load the plugin with default configuration
     */
    public static {class_name} load() {{
        return load(PluginConfig.defaults());
    }}

    /**
     * Load the plugin with custom configuration
     */
    public static {class_name} load(PluginConfig config) {{
        return PluginLoader.load(LIBRARY_NAME, config, {class_name}.class);
    }}
}}
"#,
        plugin_name = plugin_name,
        class_name = class_name,
    );

    let loader_path = package_dir.join(format!("{}Loader.java", class_name));
    fs::write(&loader_path, loader_code)?;
    println!("  Generated: {:?}", loader_path);

    Ok(())
}

/// Generate Java method signatures from message definitions
fn generate_java_methods(manifest: &Manifest) -> String {
    let mut methods = String::new();

    for (tag, def) in &manifest.messages {
        let method_name = tag_to_method_name(tag);
        let description = def.description.as_deref().unwrap_or("Handle request");

        methods.push_str(&format!(
            r#"
    /**
     * {description}
     * Type tag: {tag}
     */
    String {method_name}(String request) throws PluginException;
"#,
            description = description,
            tag = tag,
            method_name = method_name,
        ));
    }

    methods
}

/// Generate C# bindings
fn generate_csharp(manifest: &Manifest, output: &str) -> Result<()> {
    let plugin_name = &manifest.plugin.name;
    let class_name = to_pascal_case(plugin_name);

    let code = format!(
        r#"// Generated by rustbridge - DO NOT EDIT
using System;
using RustBridge.Core;

namespace RustBridge.Generated
{{
    /// <summary>
    /// {plugin_name} plugin interface
    /// Version: {version}
    /// </summary>
    public interface I{class_name} : IPlugin
    {{
{methods}
    }}

    /// <summary>
    /// Loader for {plugin_name} plugin
    /// </summary>
    public static class {class_name}Loader
    {{
        private const string LibraryName = "{plugin_name}";

        public static I{class_name} Load() => Load(PluginConfig.Defaults);

        public static I{class_name} Load(PluginConfig config)
        {{
            return PluginLoader.Load<I{class_name}>(LibraryName, config);
        }}
    }}
}}
"#,
        plugin_name = plugin_name,
        version = manifest.plugin.version,
        class_name = class_name,
        methods = generate_csharp_methods(manifest),
    );

    let file_path = Path::new(output).join(format!("{}.cs", class_name));
    fs::write(&file_path, code)?;
    println!("  Generated: {:?}", file_path);

    Ok(())
}

/// Generate C# method signatures
fn generate_csharp_methods(manifest: &Manifest) -> String {
    let mut methods = String::new();

    for (tag, def) in &manifest.messages {
        let method_name = to_pascal_case(&tag_to_method_name(tag));
        let description = def.description.as_deref().unwrap_or("Handle request");

        methods.push_str(&format!(
            r#"        /// <summary>{description}</summary>
        /// <remarks>Type tag: {tag}</remarks>
        string {method_name}(string request);

"#,
            description = description,
            tag = tag,
            method_name = method_name,
        ));
    }

    methods
}

/// Generate Python bindings
fn generate_python(manifest: &Manifest, output: &str) -> Result<()> {
    let plugin_name = &manifest.plugin.name;
    let class_name = to_pascal_case(plugin_name);

    let code = format!(
        r#"# Generated by rustbridge - DO NOT EDIT
"""
{plugin_name} plugin bindings
Version: {version}
"""

from typing import Optional
from rustbridge import Plugin, PluginConfig, PluginLoader


class {class_name}(Plugin):
    """
    {description}
    """
{methods}

class {class_name}Loader:
    """Loader for {plugin_name} plugin"""

    LIBRARY_NAME = "{plugin_name}"

    @classmethod
    def load(cls, config: Optional[PluginConfig] = None) -> {class_name}:
        """Load the plugin with optional configuration"""
        if config is None:
            config = PluginConfig.defaults()
        return PluginLoader.load(cls.LIBRARY_NAME, config, {class_name})
"#,
        plugin_name = plugin_name,
        version = manifest.plugin.version,
        class_name = class_name,
        description = manifest
            .plugin
            .description
            .as_deref()
            .unwrap_or("Plugin implementation"),
        methods = generate_python_methods(manifest),
    );

    let file_path = Path::new(output).join(format!("{}.py", plugin_name.replace('-', "_")));
    fs::write(&file_path, code)?;
    println!("  Generated: {:?}", file_path);

    Ok(())
}

/// Generate Python method signatures
fn generate_python_methods(manifest: &Manifest) -> String {
    let mut methods = String::new();

    for (tag, def) in &manifest.messages {
        let method_name = tag_to_method_name(tag).replace('-', "_");
        let description = def.description.as_deref().unwrap_or("Handle request");

        methods.push_str(&format!(
            r#"
    def {method_name}(self, request: str) -> str:
        """
        {description}

        Type tag: {tag}
        """
        return self._call("{tag}", request)
"#,
            description = description,
            tag = tag,
            method_name = method_name,
        ));
    }

    methods
}

/// Convert a string to PascalCase
fn to_pascal_case(s: &str) -> String {
    s.split(['-', '_'])
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                None => String::new(),
                Some(first) => first.to_uppercase().chain(chars).collect(),
            }
        })
        .collect()
}

/// Convert a type tag to a method name
fn tag_to_method_name(tag: &str) -> String {
    tag.replace(['.', '-'], "_")
}

#[cfg(test)]
#[path = "generate/generate_tests.rs"]
mod generate_tests;