use crate::manifest::Manifest;
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
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);
let manifest = Manifest::from_file(&manifest_file)?;
manifest.validate()?;
fs::create_dir_all(output).context("Failed to create output directory")?;
match lang.to_lowercase().as_str() {
"java" => generate_java(&manifest, output)?,
"kotlin" => generate_java(&manifest, output)?, "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(())
}
fn generate_java(manifest: &Manifest, output: &str) -> Result<()> {
let plugin_name = &manifest.plugin.name;
let class_name = to_pascal_case(plugin_name);
let package_dir = Path::new(output).join("com/rustbridge/generated");
fs::create_dir_all(&package_dir)?;
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);
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(())
}
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
}
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(())
}
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
}
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(())
}
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
}
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()
}
fn tag_to_method_name(tag: &str) -> String {
tag.replace(['.', '-'], "_")
}
#[cfg(test)]
#[path = "generate/generate_tests.rs"]
mod generate_tests;