From c7d9bc541b4f70cdb6ed9a1d35b018deaed07dad Mon Sep 17 00:00:00 2001 From: Maximiliano Duthey Date: Mon, 21 Jul 2025 12:11:07 -0300 Subject: [PATCH 1/2] feat: support dynamic options, static files and multiple templates --- src/commands/bindgen.rs | 214 +++++++++++++++++++++++++--------------- src/config.rs | 7 +- 2 files changed, 134 insertions(+), 87 deletions(-) diff --git a/src/commands/bindgen.rs b/src/commands/bindgen.rs index 6e0ad5f..17d84a1 100644 --- a/src/commands/bindgen.rs +++ b/src/commands/bindgen.rs @@ -1,7 +1,7 @@ use std::io::Read; use std::{collections::HashMap, path::PathBuf}; -use crate::config::{BindingOptions, Config, KnownChain, TrpConfig}; +use crate::config::{Config, KnownChain, TrpConfig}; use clap::Args as ClapArgs; use miette::IntoDiagnostic; use serde::{Deserialize, Serialize, Serializer}; @@ -16,16 +16,24 @@ use zip::ZipArchive; #[derive(ClapArgs)] pub struct Args {} +#[derive(Debug, Deserialize)] +struct IncludeOptions { + name: String, + value: serde_json::Value, + path: String, +} + /// Configuration structure for bindgen templates #[derive(Debug, Deserialize)] struct BindgenConfig { - protocol_files: Option>, + default_path: Option, + include: Option>, } /// Structure returned by load_github_templates containing handlebars and optional config struct TemplateBundle { handlebars: Handlebars<'static>, - config: Option, + static_files: Vec<(String, String)> } fn make_helper(name: &'static str, f: F) -> impl handlebars::HelperDef + Send + Sync + 'static @@ -81,7 +89,7 @@ fn register_handlebars_helpers(handlebars: &mut Handlebars<'_>) { /// 6. Registers each found template with Handlebars, using its path relative to `bindgen/` (without the `.hbs` extension) /// /// Returns a TemplateBundle containing the Handlebars registry and optional configuration. -async fn load_github_templates(github_url: &str) -> miette::Result { +async fn load_github_templates(github_url: &str, temp_dir: &TempDir, options: &HashMap) -> miette::Result { // Parse GitHub URL let parts: Vec<&str> = github_url.split('/').collect(); if parts.len() < 2 { @@ -113,8 +121,6 @@ async fn load_github_templates(github_url: &str) -> miette::Result miette::Result = None; + let mut bindgen_path = PathBuf::new(); + + // Get root_dir + let root_dir_name = archive.name_for_index(0).unwrap_or(""); + + bindgen_path.push(root_dir_name); + bindgen_path.push("bindgen/"); + + // Check for trix-bindgen.toml in the root directory + let toml_name = bindgen_path.join("trix-bindgen.toml").to_string_lossy().to_string(); + + if let Ok(mut config_file) = archive.by_name(&toml_name) { + let mut config_content = String::new(); + config_file.read_to_string(&mut config_content).into_diagnostic()?; + + config = toml::from_str::(&config_content) + .into_diagnostic() + .ok(); + } + + // Update bindgen_path based on config include options + if let Some(config) = &config { + let mut path_found = false; + if let Some(includes) = &config.include { + for include in includes { + // Extract the key after "options." from include.name + let option_key = include.name.strip_prefix("options.").unwrap_or(&include.name); + // Check if options has the key corresponding to the extracted key + if let Some(option_value) = options.get(option_key) { + // Check if the value matches include.value + if option_value == &include.value { + // Concatenate include.path to bindgen_path + bindgen_path.push(include.path.clone()); + path_found = true; + break; // Use the first matching include + } + } + } + } + + if !path_found { + if let Some(default_path) = &config.default_path { + bindgen_path.push(default_path); + } + } + } + // Register handlebars templates let mut handlebars = Handlebars::new(); - let mut config: Option = None; + let mut static_files = Vec::new(); + + let bindgen_path_string = bindgen_path.to_string_lossy().to_string(); + let archive_bindgen_index = archive.index_for_name(&bindgen_path_string).unwrap_or(0); - for i in 0..archive.len() { + // Skip files that are not in the bindgen_path or are the bindgen_path itself + for i in archive_bindgen_index..archive.len() { let mut file = archive.by_index(i).into_diagnostic()?; let name = file.name().to_owned(); + // If the file is a directory or doesn't belong to bindgen_path we want, skip it + if file.is_dir() { + continue; + } + + if !name.starts_with(&bindgen_path_string) { + break; // Stop processing if we reach a file outside the bindgen path + } + // Check for trix-bindgen.toml in bindgen directory - if name.contains("bindgen") && name.ends_with("trix-bindgen.toml") { - let mut config_content = String::new(); - file.read_to_string(&mut config_content).into_diagnostic()?; + if name.ends_with("trix-bindgen.toml") { + // let mut config_content = String::new(); + // file.read_to_string(&mut config_content).into_diagnostic()?; - config = toml::from_str::(&config_content) - .into_diagnostic() - .ok(); + // config = toml::from_str::(&config_content) + // .into_diagnostic() + // .ok(); continue; } - if name.contains("bindgen") && name.ends_with(".hbs") { - // Remove everything before "bindgen/" and strip ".hbs" extension - let template_name = name - .split_once("bindgen/") - .map(|x| x.1) - .unwrap_or(&name) - .strip_suffix(".hbs") - .unwrap_or_else(|| name.split('/').next_back().unwrap_or(&name)); + // Remove everything before "bindgen/" and strip ".hbs" extension + let template_name = name + .strip_prefix(&bindgen_path_string) + .unwrap_or(&name); + + if name.ends_with(".hbs") { + let template_name = template_name.strip_suffix(".hbs").unwrap_or(&name); let mut template_content = String::new(); file.read_to_string(&mut template_content) @@ -163,12 +229,23 @@ async fn load_github_templates(github_url: &str) -> miette::Result); @@ -208,6 +285,7 @@ struct HandlebarsData { transactions: Vec, headers: HashMap, env_vars: HashMap, + options: HashMap, } struct Job { @@ -217,6 +295,7 @@ struct Job { trp_endpoint: String, trp_headers: HashMap, env_args: HashMap, + options: HashMap, } fn generate_arguments( @@ -271,6 +350,7 @@ fn generate_arguments( transactions, headers, env_vars, + options: job.options.clone(), }) } @@ -279,56 +359,44 @@ async fn execute_bindgen( github_url: &str, get_type_for_field: fn(&tx3_lang::ir::Type) -> String, version: &str, - binding_options: &Option, ) -> miette::Result<()> { - let template_bundle = load_github_templates(github_url).await?; + // Create a temporary directory to extract files + let temp_dir = TempDir::new().into_diagnostic()?; + let template_bundle = load_github_templates(github_url, &temp_dir, &job.options).await?; // Create the destination directory if it doesn't exist std::fs::create_dir_all(&job.dest_path).into_diagnostic()?; let handlebars_params = generate_arguments(job, get_type_for_field, version)?; - let standalone = binding_options.as_ref().and_then(|opts| opts.standalone).unwrap_or(false); - - let all_files = template_bundle - .handlebars - .get_templates() - .keys() - .cloned() - .collect(); - - let templates_to_process = if standalone { - all_files - } else { - // If not standalone, use the config's protocol_files if available - template_bundle - .config - .as_ref() - .and_then(|c| c.protocol_files.clone()) - .unwrap_or_else(|| { - all_files - }) - }; - - // Process only the selected templates - for template_file in templates_to_process { - let template_name = template_file.strip_suffix(".hbs").unwrap_or(&template_file); - - if template_bundle - .handlebars - .get_template(template_name) - .is_some() - { - let template_content = template_bundle - .handlebars - .render(template_name, &handlebars_params) - .unwrap(); - let output_path = job.dest_path.join(&template_name); - std::fs::write(&output_path, template_content).unwrap(); - // println!("Generated file: {}", output_path.display()); + let handlebars_template_iter = template_bundle.handlebars.get_templates().iter(); + + for (name, _) in handlebars_template_iter { + let template_content = template_bundle.handlebars.render(name, &handlebars_params).unwrap(); + if template_content.is_empty() { + // Skip empty templates + continue; } + let output_path = job.dest_path.join(name); + if let Some(parent) = output_path.parent() { + // Create parent directories if they don't exist + std::fs::create_dir_all(parent).into_diagnostic()?; + } + std::fs::write(&output_path, template_content).into_diagnostic()?; + // println!("Generated file: {}", output_path.display()); + } + + // Copy static files to the destination directory + for (src_path, file_destination) in &template_bundle.static_files { + let dest_path = job.dest_path.join(file_destination); + if let Some(parent) = dest_path.parent() { + std::fs::create_dir_all(parent).into_diagnostic()?; + } + std::fs::copy(src_path, dest_path).into_diagnostic()?; + // println!("Copied static file: {}", dest_path.display()); } + Ok(()) } @@ -353,6 +421,7 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { trp_endpoint: profile.url.clone(), trp_headers: profile.headers.clone(), env_args: HashMap::new(), + options: bindgen.options.clone().unwrap_or_default(), }; match bindgen.plugin.as_str() { @@ -362,7 +431,6 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { "tx3-lang/rust-sdk", |_| "ArgValue".to_string(), &config.protocol.version, - &bindgen.options, ) .await?; println!("Rust bindgen successful"); @@ -371,21 +439,8 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { execute_bindgen( &job, "tx3-lang/web-sdk", - |ty| match ty { - tx3_lang::ir::Type::Int => "number".to_string(), - tx3_lang::ir::Type::Address => "string".to_string(), - tx3_lang::ir::Type::Bool => "boolean".to_string(), - tx3_lang::ir::Type::Bytes => "Uint8Array".to_string(), - tx3_lang::ir::Type::UtxoRef => "string".to_string(), - tx3_lang::ir::Type::List => "any[]".to_string(), - tx3_lang::ir::Type::Undefined => "any".to_string(), - tx3_lang::ir::Type::Unit => "void".to_string(), - tx3_lang::ir::Type::AnyAsset => "any".to_string(), - tx3_lang::ir::Type::Utxo => "any".to_string(), - tx3_lang::ir::Type::Custom(name) => name.clone(), - }, + |_| "ArgValue".to_string(), &config.protocol.version, - &bindgen.options, ) .await?; println!("Typescript bindgen successful"); @@ -408,7 +463,6 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { tx3_lang::ir::Type::Utxo => "Any".to_string(), }, &config.protocol.version, - &bindgen.options, ) .await?; println!("Python bindgen successful"); @@ -431,7 +485,6 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { tx3_lang::ir::Type::Undefined => "interface{}".to_string(), }, &config.protocol.version, - &bindgen.options, ) .await?; println!("Go bindgen successful"); @@ -442,7 +495,6 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { plugin, |_| "ArgValue".to_string(), // Default type for unknown plugins &config.protocol.version, - &bindgen.options, ) .await?; println!("{} bindgen successful", &plugin); diff --git a/src/config.rs b/src/config.rs index 3a6e065..44cd711 100644 --- a/src/config.rs +++ b/src/config.rs @@ -196,16 +196,11 @@ impl From for U5cConfig { } } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BindingOptions { - pub standalone: Option, -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub struct BindingsConfig { pub plugin: String, pub output_dir: PathBuf, - pub options: Option, + pub options: Option>, } impl Config { From a296d09c794ea04bf85714300e125bd00d9572cb Mon Sep 17 00:00:00 2001 From: Maximiliano Duthey Date: Wed, 23 Jul 2025 11:58:36 -0300 Subject: [PATCH 2/2] feat: replace bindings plugin by template and new structure and expose typeFor helper on handlebars --- src/commands/bindgen.rs | 281 ++++++++++++++++++---------------------- src/commands/init.rs | 5 +- src/config.rs | 63 ++++++++- 3 files changed, 191 insertions(+), 158 deletions(-) diff --git a/src/commands/bindgen.rs b/src/commands/bindgen.rs index 17d84a1..87eac04 100644 --- a/src/commands/bindgen.rs +++ b/src/commands/bindgen.rs @@ -1,10 +1,10 @@ use std::io::Read; use std::{collections::HashMap, path::PathBuf}; -use crate::config::{Config, KnownChain, TrpConfig}; +use crate::config::{BindingsTemplateConfig, Config, KnownChain, TrpConfig}; use clap::Args as ClapArgs; use miette::IntoDiagnostic; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Serialize, Serializer}; use tx3_lang::Protocol; use convert_case::{Case, Casing}; @@ -16,20 +16,6 @@ use zip::ZipArchive; #[derive(ClapArgs)] pub struct Args {} -#[derive(Debug, Deserialize)] -struct IncludeOptions { - name: String, - value: serde_json::Value, - path: String, -} - -/// Configuration structure for bindgen templates -#[derive(Debug, Deserialize)] -struct BindgenConfig { - default_path: Option, - include: Option>, -} - /// Structure returned by load_github_templates containing handlebars and optional config struct TemplateBundle { handlebars: Handlebars<'static>, @@ -53,6 +39,72 @@ where } } +fn parse_type_from_string(type_str: &str) -> Result { + match type_str { + "Int" => Ok(tx3_lang::ir::Type::Int), + "Bool" => Ok(tx3_lang::ir::Type::Bool), + "Bytes" => Ok(tx3_lang::ir::Type::Bytes), + "Unit" => Ok(tx3_lang::ir::Type::Unit), + "Address" => Ok(tx3_lang::ir::Type::Address), + "UtxoRef" => Ok(tx3_lang::ir::Type::UtxoRef), + "AnyAsset" => Ok(tx3_lang::ir::Type::AnyAsset), + "Utxo" => Ok(tx3_lang::ir::Type::Utxo), + "Undefined" => Ok(tx3_lang::ir::Type::Undefined), + "List" => Ok(tx3_lang::ir::Type::List), + _ => { + // You would need to create a CustomId here. This depends on how CustomId is defined + // Ok(tx3_lang::ir::Type::Custom(CustomId { value: type_str.to_string() })) + Ok(tx3_lang::ir::Type::Custom(type_str.to_string())) // Keep your current implementation if CustomId is not accessible + } + } +} + +fn get_type_for_language(type_: &tx3_lang::ir::Type, language: &str) -> String { + match language { + "rust" => "ArgValue".to_string(), + "typescript" => match &type_ { + tx3_lang::ir::Type::Int => "bigint | number".to_string(), + tx3_lang::ir::Type::Bool => "boolean".to_string(), + tx3_lang::ir::Type::Bytes => "Uint8Array".to_string(), + tx3_lang::ir::Type::Unit => "void".to_string(), + tx3_lang::ir::Type::Address => "string".to_string(), + tx3_lang::ir::Type::UtxoRef => "string".to_string(), + tx3_lang::ir::Type::List => "any[]".to_string(), + tx3_lang::ir::Type::Custom(name) => name.clone(), + tx3_lang::ir::Type::AnyAsset => "string".to_string(), + tx3_lang::ir::Type::Utxo => "any".to_string(), + tx3_lang::ir::Type::Undefined => "any".to_string(), + }, + "python" => match &type_ { + tx3_lang::ir::Type::Int => "int".to_string(), + tx3_lang::ir::Type::Bool => "bool".to_string(), + tx3_lang::ir::Type::Bytes => "bytes".to_string(), + tx3_lang::ir::Type::Unit => "None".to_string(), + tx3_lang::ir::Type::List => "list[Any]".to_string(), + tx3_lang::ir::Type::Address => "str".to_string(), + tx3_lang::ir::Type::UtxoRef => "str".to_string(), + tx3_lang::ir::Type::Custom(name) => name.clone(), + tx3_lang::ir::Type::AnyAsset => "str".to_string(), + tx3_lang::ir::Type::Undefined => "Any".to_string(), + tx3_lang::ir::Type::Utxo => "Any".to_string(), + }, + "go" => match &type_ { + tx3_lang::ir::Type::Int => "int64".to_string(), + tx3_lang::ir::Type::Bool => "bool".to_string(), + tx3_lang::ir::Type::Bytes => "[]byte".to_string(), + tx3_lang::ir::Type::Unit => "struct{}".to_string(), + tx3_lang::ir::Type::Address => "string".to_string(), + tx3_lang::ir::Type::UtxoRef => "string".to_string(), + tx3_lang::ir::Type::List => "[]interface{}".to_string(), + tx3_lang::ir::Type::Custom(name) => name.clone(), + tx3_lang::ir::Type::AnyAsset => "string".to_string(), + tx3_lang::ir::Type::Utxo => "interface{}".to_string(), + tx3_lang::ir::Type::Undefined => "interface{}".to_string(), + }, + _ => "ArgValue".to_string(), // Default fallback + } +} + // Register any custom helpers here /// An array of helper functions for converting strings to various case styles. /// @@ -76,6 +128,33 @@ fn register_handlebars_helpers(handlebars: &mut Handlebars<'_>) { handlebars.register_helper(name, Box::new(make_helper(name, func))); } // Add more helpers as needed + + // Register helper to convert ir types to language types. + handlebars.register_helper("typeFor", Box::new( + |h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output| { + let type_param = h.param(0).ok_or_else(|| RenderErrorReason::ParamNotFoundForIndex("typeFor", 0))?; + let lang_param = h.param(1).ok_or_else(|| RenderErrorReason::ParamNotFoundForIndex("typeFor", 1))?; + + let type_str = type_param + .value() + .as_str() + .ok_or_else(|| RenderErrorReason::InvalidParamType("Expected type as string"))?; + + let type_ = parse_type_from_string(type_str) + .map_err(|_| RenderErrorReason::InvalidParamType("Failed to parse type"))?; + + + let language = lang_param + .value() + .as_str() + .ok_or_else(|| RenderErrorReason::InvalidParamType("Expected language as string"))?; + + let output_type = get_type_for_language(&type_, language); + + out.write(&output_type)?; + Ok(()) + } + )); } /// Loads Handlebars templates from a GitHub repository ZIP archive. @@ -89,7 +168,7 @@ fn register_handlebars_helpers(handlebars: &mut Handlebars<'_>) { /// 6. Registers each found template with Handlebars, using its path relative to `bindgen/` (without the `.hbs` extension) /// /// Returns a TemplateBundle containing the Handlebars registry and optional configuration. -async fn load_github_templates(github_url: &str, temp_dir: &TempDir, options: &HashMap) -> miette::Result { +async fn load_github_templates(github_url: &str, temp_dir: &TempDir, path: &str) -> miette::Result { // Parse GitHub URL let parts: Vec<&str> = github_url.split('/').collect(); if parts.len() < 2 { @@ -108,7 +187,7 @@ async fn load_github_templates(github_url: &str, temp_dir: &TempDir, options: &H owner, repo, branch ); - println!("Reading template from https://github.com/{}", github_url); + println!("Reading template from https://github.com/{}/{} (ref: {})", owner, repo, branch); // Download the zip file let client = Client::new(); @@ -131,53 +210,28 @@ async fn load_github_templates(github_url: &str, temp_dir: &TempDir, options: &H let file = std::fs::File::open(&zip_path).into_diagnostic()?; let mut archive = ZipArchive::new(file).into_diagnostic()?; - let mut config: Option = None; let mut bindgen_path = PathBuf::new(); - + // Get root_dir let root_dir_name = archive.name_for_index(0).unwrap_or(""); - + bindgen_path.push(root_dir_name); - bindgen_path.push("bindgen/"); - - // Check for trix-bindgen.toml in the root directory - let toml_name = bindgen_path.join("trix-bindgen.toml").to_string_lossy().to_string(); + bindgen_path.push(path); + // Ensure the bindgen path ends with a separator + bindgen_path.push(""); - if let Ok(mut config_file) = archive.by_name(&toml_name) { - let mut config_content = String::new(); - config_file.read_to_string(&mut config_content).into_diagnostic()?; + // let mut config: Option = None; + // Check for trix-bindgen.toml in the directory + // let toml_name = bindgen_path.join("trix-bindgen.toml").to_string_lossy().to_string(); - config = toml::from_str::(&config_content) - .into_diagnostic() - .ok(); - } - - // Update bindgen_path based on config include options - if let Some(config) = &config { - let mut path_found = false; - if let Some(includes) = &config.include { - for include in includes { - // Extract the key after "options." from include.name - let option_key = include.name.strip_prefix("options.").unwrap_or(&include.name); - // Check if options has the key corresponding to the extracted key - if let Some(option_value) = options.get(option_key) { - // Check if the value matches include.value - if option_value == &include.value { - // Concatenate include.path to bindgen_path - bindgen_path.push(include.path.clone()); - path_found = true; - break; // Use the first matching include - } - } - } - } + // if let Ok(mut config_file) = archive.by_name(&toml_name) { + // let mut config_content = String::new(); + // config_file.read_to_string(&mut config_content).into_diagnostic()?; - if !path_found { - if let Some(default_path) = &config.default_path { - bindgen_path.push(default_path); - } - } - } + // config = toml::from_str::(&config_content) + // .into_diagnostic() + // .ok(); + // } // Register handlebars templates let mut handlebars = Handlebars::new(); @@ -191,23 +245,12 @@ async fn load_github_templates(github_url: &str, temp_dir: &TempDir, options: &H let mut file = archive.by_index(i).into_diagnostic()?; let name = file.name().to_owned(); - // If the file is a directory or doesn't belong to bindgen_path we want, skip it - if file.is_dir() { - continue; - } - if !name.starts_with(&bindgen_path_string) { break; // Stop processing if we reach a file outside the bindgen path } - // Check for trix-bindgen.toml in bindgen directory - if name.ends_with("trix-bindgen.toml") { - // let mut config_content = String::new(); - // file.read_to_string(&mut config_content).into_diagnostic()?; - - // config = toml::from_str::(&config_content) - // .into_diagnostic() - // .ok(); + // If the file is a directory or its the trix-bindgen.toml, skip it + if file.is_dir() || name.ends_with("trix-bindgen.toml") { continue; } @@ -262,7 +305,7 @@ impl Serialize for BytesHex { #[derive(Serialize)] struct TxParameter { name: String, - type_name: String, + type_name: tx3_lang::ir::Type, } #[derive(Serialize)] @@ -300,7 +343,6 @@ struct Job { fn generate_arguments( job: &Job, - get_type_for_field: fn(&tx3_lang::ir::Type) -> String, version: &str, ) -> miette::Result { let transactions = job @@ -315,7 +357,7 @@ fn generate_arguments( .iter() .map(|(key, type_)| TxParameter { name: key.as_str().to_case(Case::Camel), - type_name: get_type_for_field(type_), + type_name: type_.clone(), }) .collect(); @@ -356,18 +398,19 @@ fn generate_arguments( async fn execute_bindgen( job: &Job, - github_url: &str, - get_type_for_field: fn(&tx3_lang::ir::Type) -> String, + template_config: &BindingsTemplateConfig, version: &str, ) -> miette::Result<()> { // Create a temporary directory to extract files let temp_dir = TempDir::new().into_diagnostic()?; - let template_bundle = load_github_templates(github_url, &temp_dir, &job.options).await?; + let github_url = format!("{}/{}", &template_config.repo, template_config.r#ref.as_deref().unwrap_or("main")); + + let template_bundle = load_github_templates(&github_url, &temp_dir, &template_config.path).await?; // Create the destination directory if it doesn't exist std::fs::create_dir_all(&job.dest_path).into_diagnostic()?; - let handlebars_params = generate_arguments(job, get_type_for_field, version)?; + let handlebars_params = generate_arguments(job, version)?; let handlebars_template_iter = template_bundle.handlebars.get_templates().iter(); @@ -424,82 +467,12 @@ pub async fn run(_args: Args, config: &Config) -> miette::Result<()> { options: bindgen.options.clone().unwrap_or_default(), }; - match bindgen.plugin.as_str() { - "rust" => { - execute_bindgen( - &job, - "tx3-lang/rust-sdk", - |_| "ArgValue".to_string(), - &config.protocol.version, - ) - .await?; - println!("Rust bindgen successful"); - } - "typescript" => { - execute_bindgen( - &job, - "tx3-lang/web-sdk", - |_| "ArgValue".to_string(), - &config.protocol.version, - ) - .await?; - println!("Typescript bindgen successful"); - } - "python" => { - execute_bindgen( - &job, - "tx3-lang/python-sdk", - |ty| match ty { - tx3_lang::ir::Type::Int => "int".to_string(), - tx3_lang::ir::Type::Bool => "bool".to_string(), - tx3_lang::ir::Type::Bytes => "bytes".to_string(), - tx3_lang::ir::Type::Unit => "None".to_string(), - tx3_lang::ir::Type::List => "list[Any]".to_string(), - tx3_lang::ir::Type::Address => "str".to_string(), - tx3_lang::ir::Type::UtxoRef => "str".to_string(), - tx3_lang::ir::Type::Custom(name) => name.clone(), - tx3_lang::ir::Type::AnyAsset => "str".to_string(), - tx3_lang::ir::Type::Undefined => "Any".to_string(), - tx3_lang::ir::Type::Utxo => "Any".to_string(), - }, - &config.protocol.version, - ) - .await?; - println!("Python bindgen successful"); - } - "go" => { - execute_bindgen( - &job, - "tx3-lang/go-sdk", - |ty| match ty { - tx3_lang::ir::Type::Int => "int64".to_string(), - tx3_lang::ir::Type::Bool => "bool".to_string(), - tx3_lang::ir::Type::Bytes => "Bytes".to_string(), - tx3_lang::ir::Type::Unit => "struct{}".to_string(), - tx3_lang::ir::Type::Address => "string".to_string(), - tx3_lang::ir::Type::UtxoRef => "string".to_string(), - tx3_lang::ir::Type::List => "[]interface{}".to_string(), - tx3_lang::ir::Type::Custom(name) => name.clone(), - tx3_lang::ir::Type::AnyAsset => "string".to_string(), - tx3_lang::ir::Type::Utxo => "interface{}".to_string(), - tx3_lang::ir::Type::Undefined => "interface{}".to_string(), - }, - &config.protocol.version, - ) - .await?; - println!("Go bindgen successful"); - } - plugin => { - execute_bindgen( - &job, - plugin, - |_| "ArgValue".to_string(), // Default type for unknown plugins - &config.protocol.version, - ) - .await?; - println!("{} bindgen successful", &plugin); - } - }; + execute_bindgen( + &job, + &bindgen.template, + &config.protocol.version, + ).await?; + println!("Bindgen successful"); } Ok(()) diff --git a/src/commands/init.rs b/src/commands/init.rs index 7d60742..7f2a78d 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::config::{BindingsConfig, Config, ProfilesConfig, ProtocolConfig, RegistryConfig}; +use crate::config::{BindingsConfig, BindingsTemplateConfig, Config, ProfilesConfig, ProtocolConfig, RegistryConfig}; use clap::Args as ClapArgs; use inquire::{Confirm, MultiSelect, Text}; use miette::IntoDiagnostic; @@ -130,7 +130,8 @@ pub fn run(args: Args, config: Option<&Config>) -> miette::Result<()> { .iter() .map(|binding| BindingsConfig { output_dir: PathBuf::from(format!("./gen/{}", binding.to_string().to_lowercase())), - plugin: binding.to_string().to_lowercase(), + plugin: None, // Deprecated + template: BindingsTemplateConfig::from_plugin(binding.to_lowercase().as_str()), options: None, }) .collect(), diff --git a/src/config.rs b/src/config.rs index 44cd711..a9db415 100644 --- a/src/config.rs +++ b/src/config.rs @@ -196,9 +196,59 @@ impl From for U5cConfig { } } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BindingsTemplateConfig { + pub repo: String, + pub path: String, + pub r#ref: Option, // default: main +} + +impl Default for BindingsTemplateConfig { + fn default() -> Self { + Self { + repo: String::new(), + path: "bindgen".to_string(), + r#ref: None, + } + } +} + +impl BindingsTemplateConfig { + // Unify the creation of BindingsTemplateConfig from plugin name + pub fn from_plugin(plugin: &str) -> Self { + match plugin { + "typescript" => BindingsTemplateConfig { + repo: "tx3-lang/web-sdk".to_string(), + path: "bindgen/client-lib".to_string(), + r#ref: None, + }, + "rust" => BindingsTemplateConfig { + repo: "tx3-lang/rust-sdk".to_string(), + path: "bindgen".to_string(), + r#ref: None, + }, + "python" => BindingsTemplateConfig { + repo: "tx3-lang/python-sdk".to_string(), + path: "bindgen".to_string(), + r#ref: None, + }, + "go" => BindingsTemplateConfig { + repo: "tx3-lang/go-sdk".to_string(), + path: "bindgen".to_string(), + r#ref: None, + }, + _ => BindingsTemplateConfig::default() + } + } +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct BindingsConfig { - pub plugin: String, + // Deprecated field, use template instead + #[serde(skip_serializing_if = "Option::is_none")] + pub plugin: Option, + #[serde(default)] + pub template: BindingsTemplateConfig, pub output_dir: PathBuf, pub options: Option>, } @@ -206,7 +256,16 @@ pub struct BindingsConfig { impl Config { pub fn load(path: &PathBuf) -> miette::Result { let contents = std::fs::read_to_string(path).into_diagnostic()?; - let config = toml::from_str(&contents).into_diagnostic()?; + let mut config: Config = toml::from_str(&contents).into_diagnostic()?; + + // Post-process bindings to handle backward compatibility + // Eventually, this should be removed once deprecated plugin option is removed + for binding in &mut config.bindings { + if binding.template.repo.is_empty() && binding.plugin.is_some() { + binding.template = BindingsTemplateConfig::from_plugin(binding.plugin.as_ref().unwrap()); + } + } + Ok(config) }