From c3bfae66766061d7c199bc2e1f3e12a18d06e047 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:25:53 +0300 Subject: [PATCH 01/20] doc update --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cef0abd..bc316ca 100644 --- a/README.md +++ b/README.md @@ -150,13 +150,24 @@ export interface User { } ``` -## Attributes +## Struct-Level Attributes -The `ts_bind` attribute supports the following optional arguments: +The `ts_bind` attribute supports the following optional arguments for the entire struct: -| Argument | Description | -| -------- | ------------------------------- | -| `rename` | Rename the generated interface. | +| Argument | Description | +| ------------ | ------------------------------- | +| `rename` | Rename the generated interface. | +| `rename_all` | Rename all fields by case. | +| `export` | Custom export path. | + +### Field-Level Attributes + +The `ts_bind` attribute supports the following optional arguments for individual fields: + +| Argument | Description | +| -------- | ----------------- | +| `rename` | Rename the field. | +| `skip` | Skip the field. | ```rust #[derive(TsBind)] From 13f11c8b65d39f61a24bd0b43c0769969f16756f Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:40:04 +0300 Subject: [PATCH 02/20] fix --- crates/macros/src/parsers/struc.rs | 42 ++++++++---------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/crates/macros/src/parsers/struc.rs b/crates/macros/src/parsers/struc.rs index 5bd5f6f..6ed5b37 100644 --- a/crates/macros/src/parsers/struc.rs +++ b/crates/macros/src/parsers/struc.rs @@ -1,7 +1,4 @@ -use syn::{ - meta::ParseNestedMeta, Attribute, Data, DeriveInput, Expr, Fields, Ident, Lit, LitStr, Meta, - MetaNameValue, Type, -}; +use syn::{meta::ParseNestedMeta, Attribute, Data, DeriveInput, Fields, Ident, LitStr, Type}; #[derive(Default)] pub struct FieldAttributes { @@ -39,40 +36,23 @@ fn parse_field_attributes(attrs: &[Attribute]) -> anyhow::Result { - let path = &meta_name_value.path; - if path.is_ident("rename") { - field_attrs.rename = get_meta_name_value(&meta_name_value)?; - } - } - Meta::Path(meta_path) => { - if meta_path.is_ident("skip") { - field_attrs.skip = true; - } - } - Meta::List(_meta_list) => {} + attr.parse_nested_meta(|meta| { + let path = &meta.path; + if path.is_ident("rename") { + field_attrs.rename = + Some(get_nested_value(&meta).expect("Failed to parse rename attribute")); } - } + if path.is_ident("skip") { + field_attrs.skip = true; + } + Ok(()) + })?; } } Ok(field_attrs) } -pub fn get_meta_name_value(rename_meta: &MetaNameValue) -> anyhow::Result> { - if let Expr::Lit(lit) = &rename_meta.value { - if let Lit::Str(lit_str) = &lit.lit { - return Ok(Some(lit_str.value())); - } - } else { - return Err(anyhow::anyhow!("rename attribute must be a string literal")); - } - - Ok(None) -} - pub fn get_nested_value(meta: &ParseNestedMeta) -> anyhow::Result { let value = meta.value()?; let s: LitStr = value.parse()?; From c4c90e7e4f55f4665c7a2b730dca86cef9d20511 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:40:51 +0300 Subject: [PATCH 03/20] readme update --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc316ca..416f951 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,10 @@ export interface User { The library is far from complete. Here are some of the features that are planned: +- [x] `#[ts_bind(export = "path/to/export")]` custom export path. +- [x] `#[ts_bind(rename_all = "camelCase")]` attribute to rename all fields. +- [x] `#[ts_bind(skip)]` attribute to skip fields. - [ ] Support for enums. -- [ ] `#[ts_bind(export = "path/to/export")]` custom export path. -- [ ] `#[ts_bind(rename_all = "camelCase")]` attribute to rename all fields. -- [ ] `#[ts_bind(skip)]` attribute to skip fields. - [ ] `#[ts_bind(skip_if = "condition")]` attribute to skip fields based on a condition. ## Contributing From 5f34962e0cb0756cd4206971c0aecec77107e886 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:26:15 +0300 Subject: [PATCH 04/20] abstracted --- crates/macros/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 98d065a..319388b 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -55,8 +55,14 @@ impl StructAttributes { pub fn ts_bind_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let attrs = &input.attrs; + match gen_ts_code(&input) { + Ok(ts_code) => ts_code, + Err(e) => e.to_compile_error(), + } +} +fn gen_ts_code(input: &DeriveInput) -> anyhow::Result { + let attrs = &input.attrs; let mut struct_attrs = StructAttributes::new(); attrs.iter().for_each(|attr| { if attr.path().is_ident("ts_bind") { @@ -110,13 +116,7 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { input.ident.to_string() }; - let fields = parse_struct_fields(&input); - - if let Err(e) = fields { - return e.to_compile_error(); - } - - let fields = fields.unwrap(); + let fields = parse_struct_fields(&input)?; let mut ts_bind = String::from(format!("\nexport interface {} {{\n", name)); let mut imports = Vec::new(); @@ -161,7 +161,7 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { write_to_file(&lib_path, &ts_bind); - quote! {}.into() + Ok(quote! {}.into()) } fn write_to_file(path: &PathBuf, content: &str) { From bb78dfc4b7b70a2a19b5de22d1eaa72a4251024c Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:29:39 +0300 Subject: [PATCH 05/20] rename all --- crates/macros/src/lib.rs | 26 ++------------------------ crates/macros/src/rename_all.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 crates/macros/src/rename_all.rs diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 319388b..4364d19 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -3,41 +3,19 @@ use std::{ path::PathBuf, }; -use convert_case::{Case, Casing}; use error::ToCompileError; use parsers::struc::{get_nested_value, parse_struct_fields}; use proc_macro::TokenStream; use quote::quote; +use rename_all::RenameAll; use syn::{parse_macro_input, DeriveInput}; use ts::ts_map::ts_rs_map; mod error; mod parsers; +mod rename_all; mod ts; -#[derive(Debug)] -enum RenameAll { - CamelCase, - SnakeCase, - UpperCase, - LowerCase, - PascalCase, - // TODO: kebab - //KebabCase, -} - -impl RenameAll { - pub fn to_case(&self, s: &str) -> String { - match self { - Self::CamelCase => s.to_case(Case::Camel), - Self::SnakeCase => s.to_case(Case::Snake), - Self::UpperCase => s.to_case(Case::Upper), - Self::LowerCase => s.to_case(Case::Lower), - Self::PascalCase => s.to_case(Case::Pascal), - } - } -} - #[derive(Default, Debug)] struct StructAttributes { pub rename_all: Option, diff --git a/crates/macros/src/rename_all.rs b/crates/macros/src/rename_all.rs new file mode 100644 index 0000000..2e14c61 --- /dev/null +++ b/crates/macros/src/rename_all.rs @@ -0,0 +1,24 @@ +use convert_case::{Case, Casing}; + +#[derive(Debug)] +pub enum RenameAll { + CamelCase, + SnakeCase, + UpperCase, + LowerCase, + PascalCase, + // TODO: kebab + //KebabCase, +} + +impl RenameAll { + pub fn to_case(&self, s: &str) -> String { + match self { + Self::CamelCase => s.to_case(Case::Camel), + Self::SnakeCase => s.to_case(Case::Snake), + Self::UpperCase => s.to_case(Case::Upper), + Self::LowerCase => s.to_case(Case::Lower), + Self::PascalCase => s.to_case(Case::Pascal), + } + } +} From 1807a7b647540c811a5f46bcce7e171c91e0561f Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:31:52 +0300 Subject: [PATCH 06/20] struct attrs --- crates/macros/src/lib.rs | 17 +++-------------- crates/macros/src/struct_attrs.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 crates/macros/src/struct_attrs.rs diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 4364d19..7fa7146 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -8,27 +8,16 @@ use parsers::struc::{get_nested_value, parse_struct_fields}; use proc_macro::TokenStream; use quote::quote; use rename_all::RenameAll; +use struct_attrs::StructAttrs; use syn::{parse_macro_input, DeriveInput}; use ts::ts_map::ts_rs_map; mod error; mod parsers; mod rename_all; +mod struct_attrs; mod ts; -#[derive(Default, Debug)] -struct StructAttributes { - pub rename_all: Option, - pub rename: Option, - pub export: Option, -} - -impl StructAttributes { - pub fn new() -> Self { - Self::default() - } -} - #[proc_macro_derive(TsBind, attributes(ts_bind))] pub fn ts_bind_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -41,7 +30,7 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { fn gen_ts_code(input: &DeriveInput) -> anyhow::Result { let attrs = &input.attrs; - let mut struct_attrs = StructAttributes::new(); + let mut struct_attrs = StructAttrs::new(); attrs.iter().for_each(|attr| { if attr.path().is_ident("ts_bind") { attr.parse_nested_meta(|meta| { diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs new file mode 100644 index 0000000..83343fd --- /dev/null +++ b/crates/macros/src/struct_attrs.rs @@ -0,0 +1,15 @@ +use crate::rename_all::RenameAll; +use std::path::PathBuf; + +#[derive(Default, Debug)] +pub struct StructAttrs { + pub rename_all: Option, + pub rename: Option, + pub export: Option, +} + +impl StructAttrs { + pub fn new() -> Self { + Self::default() + } +} From fb3ed8669969c9c3fd776f993d94685f49d82345 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:34:42 +0300 Subject: [PATCH 07/20] parse struct attrs --- crates/macros/src/lib.rs | 50 ++-------------------------- crates/macros/src/struct_attrs.rs | 54 ++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 7fa7146..c06939d 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -4,10 +4,9 @@ use std::{ }; use error::ToCompileError; -use parsers::struc::{get_nested_value, parse_struct_fields}; +use parsers::struc::parse_struct_fields; use proc_macro::TokenStream; use quote::quote; -use rename_all::RenameAll; use struct_attrs::StructAttrs; use syn::{parse_macro_input, DeriveInput}; use ts::ts_map::ts_rs_map; @@ -31,51 +30,8 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { fn gen_ts_code(input: &DeriveInput) -> anyhow::Result { let attrs = &input.attrs; let mut struct_attrs = StructAttrs::new(); - attrs.iter().for_each(|attr| { - if attr.path().is_ident("ts_bind") { - attr.parse_nested_meta(|meta| { - let path = &meta.path; - if path.is_ident("rename") { - let value = get_nested_value(&meta).expect("Failed to parse rename attribute"); - - struct_attrs.rename = Some(value); - } - if path.is_ident("rename_all") { - let value = - get_nested_value(&meta).expect("Failed to parse rename_all attribute"); - - match value.as_str() { - "camelCase" => { - struct_attrs.rename_all = Some(RenameAll::CamelCase); - } - "snake_case" => { - struct_attrs.rename_all = Some(RenameAll::SnakeCase); - } - "UPPERCASE" => { - struct_attrs.rename_all = Some(RenameAll::UpperCase); - } - "lowercase" => { - struct_attrs.rename_all = Some(RenameAll::LowerCase); - } - "PascalCase" => { - struct_attrs.rename_all = Some(RenameAll::PascalCase); - } - _ => { - panic!("Invalid attribute name: {}", value); - } - } - } - if path.is_ident("export") { - let value = get_nested_value(&meta).expect("Failed to parse export attribute"); - - struct_attrs.export = Some(PathBuf::from(value)); - } - - Ok(()) - }) - .expect("Failed to parse nested meta"); - } - }); + + struct_attrs.parse_attrs(attrs); let name = if let Some(rename) = struct_attrs.rename { rename diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 83343fd..77ba820 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -1,4 +1,6 @@ -use crate::rename_all::RenameAll; +use syn::Attribute; + +use crate::{parsers::struc::get_nested_value, rename_all::RenameAll}; use std::path::PathBuf; #[derive(Default, Debug)] @@ -12,4 +14,54 @@ impl StructAttrs { pub fn new() -> Self { Self::default() } + + pub fn parse_attrs(&mut self, attrs: &Vec) { + attrs.iter().for_each(|attr| { + if attr.path().is_ident("ts_bind") { + attr.parse_nested_meta(|meta| { + let path = &meta.path; + if path.is_ident("rename") { + let value = + get_nested_value(&meta).expect("Failed to parse rename attribute"); + + self.rename = Some(value); + } + if path.is_ident("rename_all") { + let value = + get_nested_value(&meta).expect("Failed to parse rename_all attribute"); + + match value.as_str() { + "camelCase" => { + self.rename_all = Some(RenameAll::CamelCase); + } + "snake_case" => { + self.rename_all = Some(RenameAll::SnakeCase); + } + "UPPERCASE" => { + self.rename_all = Some(RenameAll::UpperCase); + } + "lowercase" => { + self.rename_all = Some(RenameAll::LowerCase); + } + "PascalCase" => { + self.rename_all = Some(RenameAll::PascalCase); + } + _ => { + panic!("Invalid attribute name: {}", value); + } + } + } + if path.is_ident("export") { + let value = + get_nested_value(&meta).expect("Failed to parse export attribute"); + + self.export = Some(PathBuf::from(value)); + } + + Ok(()) + }) + .expect("Failed to parse nested meta"); + } + }); + } } From c440ec74a27ec5dcc5ba8a6c497c6223200b18ce Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:50:06 +0300 Subject: [PATCH 08/20] gen ts code --- crates/macros/src/lib.rs | 74 +++++++---------------------- crates/macros/src/parsers/struc.rs | 6 +-- crates/macros/src/ts/gen_ts_code.rs | 50 +++++++++++++++++++ crates/macros/src/ts/mod.rs | 1 + 4 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 crates/macros/src/ts/gen_ts_code.rs diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index c06939d..0db889f 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,15 +1,14 @@ -use std::{ - fs::{create_dir_all, write}, - path::PathBuf, -}; - use error::ToCompileError; use parsers::struc::parse_struct_fields; use proc_macro::TokenStream; use quote::quote; +use std::{ + fs::{create_dir_all, write}, + path::PathBuf, +}; use struct_attrs::StructAttrs; use syn::{parse_macro_input, DeriveInput}; -use ts::ts_map::ts_rs_map; +use ts::gen_ts_code::gen_ts_code; mod error; mod parsers; @@ -21,66 +20,30 @@ mod ts; pub fn ts_bind_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - match gen_ts_code(&input) { + match handle_derive(&input) { Ok(ts_code) => ts_code, Err(e) => e.to_compile_error(), } } -fn gen_ts_code(input: &DeriveInput) -> anyhow::Result { +fn handle_derive(input: &DeriveInput) -> anyhow::Result { let attrs = &input.attrs; - let mut struct_attrs = StructAttrs::new(); + let mut struct_attrs = StructAttrs::new(); struct_attrs.parse_attrs(attrs); - let name = if let Some(rename) = struct_attrs.rename { - rename - } else { - input.ident.to_string() - }; + let name = input.ident.to_string(); + let struct_name = struct_attrs.rename.as_ref().unwrap_or(&name); let fields = parse_struct_fields(&input)?; - let mut ts_bind = String::from(format!("\nexport interface {} {{\n", name)); - let mut imports = Vec::new(); - for (ident, ty, attrs) in fields.iter() { - if attrs.skip { - continue; - } + let ts_bind = gen_ts_code(&struct_name, &fields, &struct_attrs)?; - let field_name = if let Some(rename_all) = &struct_attrs.rename_all { - rename_all.to_case(&ident.to_string()) - } else { - ident.to_string() - }; - - let field_name = attrs.rename.as_ref().unwrap_or(&field_name); - - let map_result = ts_rs_map(ty, &mut imports); - - ts_bind.push_str(&format!(" {}: {};\n", field_name, map_result)); - } - - ts_bind.push_str("}"); - - sort_imports(&mut imports); - for to_import in imports { - ts_bind = format!( - "import type {{ {} }} from \"./{}\";\n{}", - to_import, to_import, ts_bind - ); - } - - ts_bind = format!( - "// This file was automatically generated by ts_bind, do not modify it manually\n{}", - ts_bind - ); - - let lib_path = if let Some(export_path) = struct_attrs.export { - export_path.join(format!("{}.ts", name)) - } else { - PathBuf::new().join("bindings").join(format!("{}.ts", name)) - }; + let lib_path = struct_attrs.export.unwrap_or_else(|| { + PathBuf::new() + .join("bindings") + .join(format!("{}.ts", struct_name)) + }); write_to_file(&lib_path, &ts_bind); @@ -91,8 +54,3 @@ fn write_to_file(path: &PathBuf, content: &str) { create_dir_all(path.parent().unwrap()).unwrap(); write(path, content).unwrap(); } - -fn sort_imports(imports: &mut Vec) { - imports.sort(); - imports.dedup(); -} diff --git a/crates/macros/src/parsers/struc.rs b/crates/macros/src/parsers/struc.rs index 6ed5b37..603b19e 100644 --- a/crates/macros/src/parsers/struc.rs +++ b/crates/macros/src/parsers/struc.rs @@ -12,9 +12,9 @@ impl FieldAttributes { } } -pub fn parse_struct_fields( - input: &DeriveInput, -) -> anyhow::Result> { +pub type ParsedField = (Ident, Type, FieldAttributes); + +pub fn parse_struct_fields(input: &DeriveInput) -> anyhow::Result> { let mut fields_info = Vec::new(); if let Data::Struct(data_struct) = &input.data { diff --git a/crates/macros/src/ts/gen_ts_code.rs b/crates/macros/src/ts/gen_ts_code.rs new file mode 100644 index 0000000..a9a590f --- /dev/null +++ b/crates/macros/src/ts/gen_ts_code.rs @@ -0,0 +1,50 @@ +use super::ts_map::ts_rs_map; +use crate::{parsers::struc::ParsedField, struct_attrs::StructAttrs}; + +pub fn gen_ts_code( + struct_name: &str, + fields: &Vec, + struct_attrs: &StructAttrs, +) -> anyhow::Result { + let mut ts_bind = String::from(format!("\nexport interface {} {{\n", struct_name)); + let mut imports = Vec::new(); + for (ident, ty, attrs) in fields.iter() { + if attrs.skip { + continue; + } + + let field_name = if let Some(rename_all) = &struct_attrs.rename_all { + rename_all.to_case(&ident.to_string()) + } else { + ident.to_string() + }; + + let field_name = attrs.rename.as_ref().unwrap_or(&field_name); + + let map_result = ts_rs_map(ty, &mut imports); + + ts_bind.push_str(&format!(" {}: {};\n", field_name, map_result)); + } + + ts_bind.push_str("}"); + + sorter(&mut imports); + for to_import in imports { + ts_bind = format!( + "import type {{ {} }} from \"./{}\";\n{}", + to_import, to_import, ts_bind + ); + } + + ts_bind = format!( + "// This file was automatically generated by ts_bind, do not modify it manually\n{}", + ts_bind + ); + + Ok(ts_bind) +} + +fn sorter(imports: &mut Vec) { + imports.sort(); + imports.dedup(); +} diff --git a/crates/macros/src/ts/mod.rs b/crates/macros/src/ts/mod.rs index 67f5253..b1f1224 100644 --- a/crates/macros/src/ts/mod.rs +++ b/crates/macros/src/ts/mod.rs @@ -1 +1,2 @@ +pub mod gen_ts_code; pub mod ts_map; From 53f6b0b1b5a727ea3d2af2cc6703d6e77e61a6ef Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:59:20 +0300 Subject: [PATCH 09/20] abstractions --- crates/macros/src/lib.rs | 13 +++------- crates/macros/src/struct_attrs.rs | 42 ++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 0db889f..34ce8ea 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -21,7 +21,7 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); match handle_derive(&input) { - Ok(ts_code) => ts_code, + Ok(ts) => ts, Err(e) => e.to_compile_error(), } } @@ -29,8 +29,7 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { fn handle_derive(input: &DeriveInput) -> anyhow::Result { let attrs = &input.attrs; - let mut struct_attrs = StructAttrs::new(); - struct_attrs.parse_attrs(attrs); + let struct_attrs = StructAttrs::from(input.ident.to_string(), attrs); let name = input.ident.to_string(); let struct_name = struct_attrs.rename.as_ref().unwrap_or(&name); @@ -39,13 +38,7 @@ fn handle_derive(input: &DeriveInput) -> anyhow::Result { let ts_bind = gen_ts_code(&struct_name, &fields, &struct_attrs)?; - let lib_path = struct_attrs.export.unwrap_or_else(|| { - PathBuf::new() - .join("bindings") - .join(format!("{}.ts", struct_name)) - }); - - write_to_file(&lib_path, &ts_bind); + write_to_file(&struct_attrs.export_path(), &ts_bind); Ok(quote! {}.into()) } diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 77ba820..3959bc6 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -3,19 +3,29 @@ use syn::Attribute; use crate::{parsers::struc::get_nested_value, rename_all::RenameAll}; use std::path::PathBuf; -#[derive(Default, Debug)] +#[derive(Debug)] pub struct StructAttrs { + name: String, pub rename_all: Option, pub rename: Option, - pub export: Option, + export: Option, } impl StructAttrs { - pub fn new() -> Self { - Self::default() + pub fn from(struct_name: String, attrs: &Vec) -> Self { + let mut struct_attrs = Self { + name: struct_name, + rename_all: None, + rename: None, + export: None, + }; + + Self::parse_attrs(&mut struct_attrs, attrs); + + struct_attrs } - pub fn parse_attrs(&mut self, attrs: &Vec) { + fn parse_attrs(struct_attrs: &mut Self, attrs: &Vec) { attrs.iter().for_each(|attr| { if attr.path().is_ident("ts_bind") { attr.parse_nested_meta(|meta| { @@ -24,7 +34,7 @@ impl StructAttrs { let value = get_nested_value(&meta).expect("Failed to parse rename attribute"); - self.rename = Some(value); + struct_attrs.rename = Some(value); } if path.is_ident("rename_all") { let value = @@ -32,19 +42,19 @@ impl StructAttrs { match value.as_str() { "camelCase" => { - self.rename_all = Some(RenameAll::CamelCase); + struct_attrs.rename_all = Some(RenameAll::CamelCase); } "snake_case" => { - self.rename_all = Some(RenameAll::SnakeCase); + struct_attrs.rename_all = Some(RenameAll::SnakeCase); } "UPPERCASE" => { - self.rename_all = Some(RenameAll::UpperCase); + struct_attrs.rename_all = Some(RenameAll::UpperCase); } "lowercase" => { - self.rename_all = Some(RenameAll::LowerCase); + struct_attrs.rename_all = Some(RenameAll::LowerCase); } "PascalCase" => { - self.rename_all = Some(RenameAll::PascalCase); + struct_attrs.rename_all = Some(RenameAll::PascalCase); } _ => { panic!("Invalid attribute name: {}", value); @@ -55,7 +65,7 @@ impl StructAttrs { let value = get_nested_value(&meta).expect("Failed to parse export attribute"); - self.export = Some(PathBuf::from(value)); + struct_attrs.export = Some(PathBuf::from(value)); } Ok(()) @@ -64,4 +74,12 @@ impl StructAttrs { } }); } + + pub fn export_path(&self) -> PathBuf { + self.export.clone().unwrap_or_else(|| { + PathBuf::new() + .join("bindings") + .join(format!("{}.ts", self.rename.as_ref().unwrap())) + }) + } } From 3e8ed2f017b2e3cb5acb215e7c33b32bb6a08bbf Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:01:24 +0300 Subject: [PATCH 10/20] more abstractions --- crates/macros/src/lib.rs | 11 +++-------- crates/macros/src/struct_attrs.rs | 6 +++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 34ce8ea..8d8f7d6 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -27,18 +27,13 @@ pub fn ts_bind_derive(input: TokenStream) -> TokenStream { } fn handle_derive(input: &DeriveInput) -> anyhow::Result { - let attrs = &input.attrs; - - let struct_attrs = StructAttrs::from(input.ident.to_string(), attrs); - - let name = input.ident.to_string(); - let struct_name = struct_attrs.rename.as_ref().unwrap_or(&name); + let struct_attrs = StructAttrs::from(input.ident.to_string(), &input.attrs); let fields = parse_struct_fields(&input)?; - let ts_bind = gen_ts_code(&struct_name, &fields, &struct_attrs)?; + let ts_bind = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; - write_to_file(&struct_attrs.export_path(), &ts_bind); + write_to_file(&struct_attrs.get_export_path(), &ts_bind); Ok(quote! {}.into()) } diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 3959bc6..f6764e7 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -75,11 +75,15 @@ impl StructAttrs { }); } - pub fn export_path(&self) -> PathBuf { + pub fn get_export_path(&self) -> PathBuf { self.export.clone().unwrap_or_else(|| { PathBuf::new() .join("bindings") .join(format!("{}.ts", self.rename.as_ref().unwrap())) }) } + + pub fn get_name(&self) -> &String { + self.rename.as_ref().unwrap_or(&self.name) + } } From 4c1de31d42d953463fc4de9aede697946b4b459a Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:08:29 +0300 Subject: [PATCH 11/20] bug fi --- crates/macros/src/lib.rs | 16 ++++++++++++---- crates/macros/src/struct_attrs.rs | 10 +++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 8d8f7d6..e1083b9 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -33,12 +33,20 @@ fn handle_derive(input: &DeriveInput) -> anyhow::Result { let ts_bind = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; - write_to_file(&struct_attrs.get_export_path(), &ts_bind); + write_to_file(&struct_attrs.get_export_path(), &ts_bind)?; Ok(quote! {}.into()) } -fn write_to_file(path: &PathBuf, content: &str) { - create_dir_all(path.parent().unwrap()).unwrap(); - write(path, content).unwrap(); +fn write_to_file(path: &PathBuf, content: &str) -> anyhow::Result<()> { + let parent = path.parent().ok_or(anyhow::anyhow!( + "Failed to get parent directory of path: {}", + path.display() + ))?; + + create_dir_all(parent)?; + + write(path, content)?; + + Ok(()) } diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index f6764e7..95e25cc 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -75,15 +75,15 @@ impl StructAttrs { }); } + pub fn get_name(&self) -> &String { + self.rename.as_ref().unwrap_or(&self.name) + } + pub fn get_export_path(&self) -> PathBuf { self.export.clone().unwrap_or_else(|| { PathBuf::new() .join("bindings") - .join(format!("{}.ts", self.rename.as_ref().unwrap())) + .join(format!("{}.ts", self.get_name())) }) } - - pub fn get_name(&self) -> &String { - self.rename.as_ref().unwrap_or(&self.name) - } } From c8f8d09f34aa3c8102f171ce35bb3668bf86da97 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:28:11 +0300 Subject: [PATCH 12/20] files --- crates/macros/src/files.rs | 17 +++++++++++++++++ crates/macros/src/lib.rs | 19 ++----------------- 2 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 crates/macros/src/files.rs diff --git a/crates/macros/src/files.rs b/crates/macros/src/files.rs new file mode 100644 index 0000000..4ac12e0 --- /dev/null +++ b/crates/macros/src/files.rs @@ -0,0 +1,17 @@ +use std::{ + fs::{create_dir_all, write}, + path::PathBuf, +}; + +pub fn write_to_file(path: &PathBuf, content: &str) -> anyhow::Result<()> { + let parent = path.parent().ok_or(anyhow::anyhow!( + "Failed to get parent directory of path: {}", + path.display() + ))?; + + create_dir_all(parent)?; + + write(path, content)?; + + Ok(()) +} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index e1083b9..f29f78d 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,16 +1,14 @@ use error::ToCompileError; +use files::write_to_file; use parsers::struc::parse_struct_fields; use proc_macro::TokenStream; use quote::quote; -use std::{ - fs::{create_dir_all, write}, - path::PathBuf, -}; use struct_attrs::StructAttrs; use syn::{parse_macro_input, DeriveInput}; use ts::gen_ts_code::gen_ts_code; mod error; +mod files; mod parsers; mod rename_all; mod struct_attrs; @@ -37,16 +35,3 @@ fn handle_derive(input: &DeriveInput) -> anyhow::Result { Ok(quote! {}.into()) } - -fn write_to_file(path: &PathBuf, content: &str) -> anyhow::Result<()> { - let parent = path.parent().ok_or(anyhow::anyhow!( - "Failed to get parent directory of path: {}", - path.display() - ))?; - - create_dir_all(parent)?; - - write(path, content)?; - - Ok(()) -} From e355906b8b658522539a6c0d21c351228b3fa28e Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:30:23 +0300 Subject: [PATCH 13/20] abstractions --- crates/macros/src/lib.rs | 21 +++------------------ crates/macros/src/ts_bind.rs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 crates/macros/src/ts_bind.rs diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index f29f78d..a6ec0e0 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,11 +1,7 @@ use error::ToCompileError; -use files::write_to_file; -use parsers::struc::parse_struct_fields; use proc_macro::TokenStream; -use quote::quote; -use struct_attrs::StructAttrs; use syn::{parse_macro_input, DeriveInput}; -use ts::gen_ts_code::gen_ts_code; +use ts_bind::handle_ts_bind; mod error; mod files; @@ -13,25 +9,14 @@ mod parsers; mod rename_all; mod struct_attrs; mod ts; +mod ts_bind; #[proc_macro_derive(TsBind, attributes(ts_bind))] pub fn ts_bind_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - match handle_derive(&input) { + match handle_ts_bind(&input) { Ok(ts) => ts, Err(e) => e.to_compile_error(), } } - -fn handle_derive(input: &DeriveInput) -> anyhow::Result { - let struct_attrs = StructAttrs::from(input.ident.to_string(), &input.attrs); - - let fields = parse_struct_fields(&input)?; - - let ts_bind = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; - - write_to_file(&struct_attrs.get_export_path(), &ts_bind)?; - - Ok(quote! {}.into()) -} diff --git a/crates/macros/src/ts_bind.rs b/crates/macros/src/ts_bind.rs new file mode 100644 index 0000000..2beb6f4 --- /dev/null +++ b/crates/macros/src/ts_bind.rs @@ -0,0 +1,20 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::DeriveInput; + +use crate::{ + files::write_to_file, parsers::struc::parse_struct_fields, struct_attrs::StructAttrs, + ts::gen_ts_code::gen_ts_code, +}; + +pub fn handle_ts_bind(input: &DeriveInput) -> anyhow::Result { + let struct_attrs = StructAttrs::from(input.ident.to_string(), &input.attrs); + + let fields = parse_struct_fields(&input)?; + + let ts_bind = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; + + write_to_file(&struct_attrs.get_export_path(), &ts_bind)?; + + Ok(quote! {}.into()) +} From 9983a0645d93d86bb7059c33aff69b12987ec4df Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:38:22 +0300 Subject: [PATCH 14/20] fix --- crates/macros/src/struct_attrs.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 95e25cc..68eeebc 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -80,10 +80,9 @@ impl StructAttrs { } pub fn get_export_path(&self) -> PathBuf { - self.export.clone().unwrap_or_else(|| { - PathBuf::new() - .join("bindings") - .join(format!("{}.ts", self.get_name())) - }) + self.export + .clone() + .unwrap_or_else(|| PathBuf::new().join("bindings")) + .join(format!("{}.ts", self.get_name())) } } From e1a25defcc24e754720ac423c285d82d444c1a1e Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:50:00 +0300 Subject: [PATCH 15/20] error handling --- crates/macros/src/parsers/struc.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/macros/src/parsers/struc.rs b/crates/macros/src/parsers/struc.rs index 603b19e..fc71b88 100644 --- a/crates/macros/src/parsers/struc.rs +++ b/crates/macros/src/parsers/struc.rs @@ -38,13 +38,25 @@ fn parse_field_attributes(attrs: &[Attribute]) -> anyhow::Result { + field_attrs.rename = Some( + get_nested_value(&meta).expect("Failed to parse rename attribute"), + ); + } + "skip" => { + field_attrs.skip = true; + } + _ => { + panic!("Invalid attribute name: {}", ident_str); + } + } } + Ok(()) })?; } From bec3212d090455afa25f131e2243184e27fa6b46 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:53:18 +0300 Subject: [PATCH 16/20] error handling --- crates/macros/src/struct_attrs.rs | 68 ++++++++++++++++++------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 68eeebc..b9d11ff 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -30,43 +30,55 @@ impl StructAttrs { if attr.path().is_ident("ts_bind") { attr.parse_nested_meta(|meta| { let path = &meta.path; - if path.is_ident("rename") { - let value = - get_nested_value(&meta).expect("Failed to parse rename attribute"); - struct_attrs.rename = Some(value); - } - if path.is_ident("rename_all") { - let value = - get_nested_value(&meta).expect("Failed to parse rename_all attribute"); + let ident = path.get_ident(); - match value.as_str() { - "camelCase" => { - struct_attrs.rename_all = Some(RenameAll::CamelCase); - } - "snake_case" => { - struct_attrs.rename_all = Some(RenameAll::SnakeCase); - } - "UPPERCASE" => { - struct_attrs.rename_all = Some(RenameAll::UpperCase); + if let Some(ident) = ident { + let ident_str = ident.to_string(); + + match ident_str.as_str() { + "rename" => { + let value = get_nested_value(&meta) + .expect("Failed to parse rename attribute"); + + struct_attrs.rename = Some(value); } - "lowercase" => { - struct_attrs.rename_all = Some(RenameAll::LowerCase); + "rename_all" => { + let value = get_nested_value(&meta) + .expect("Failed to parse rename_all attribute"); + + match value.as_str() { + "camelCase" => { + struct_attrs.rename_all = Some(RenameAll::CamelCase); + } + "snake_case" => { + struct_attrs.rename_all = Some(RenameAll::SnakeCase); + } + "UPPERCASE" => { + struct_attrs.rename_all = Some(RenameAll::UpperCase); + } + "lowercase" => { + struct_attrs.rename_all = Some(RenameAll::LowerCase); + } + "PascalCase" => { + struct_attrs.rename_all = Some(RenameAll::PascalCase); + } + _ => { + panic!("Invalid attribute name: {}", value); + } + } } - "PascalCase" => { - struct_attrs.rename_all = Some(RenameAll::PascalCase); + "export" => { + let value = get_nested_value(&meta) + .expect("Failed to parse export attribute"); + + struct_attrs.export = Some(PathBuf::from(value)); } _ => { - panic!("Invalid attribute name: {}", value); + panic!("Invalid attribute name: {}", ident_str); } } } - if path.is_ident("export") { - let value = - get_nested_value(&meta).expect("Failed to parse export attribute"); - - struct_attrs.export = Some(PathBuf::from(value)); - } Ok(()) }) From 60728c2d668ff63663a00a6b8362d84c8e5d2054 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:54:27 +0300 Subject: [PATCH 17/20] bump --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 342e0cf..b6ac70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,14 +48,14 @@ dependencies = [ [[package]] name = "ts-bind" -version = "0.1.5" +version = "0.1.6" dependencies = [ "ts-bind-macros", ] [[package]] name = "ts-bind-macros" -version = "0.1.5" +version = "0.1.6" dependencies = [ "anyhow", "convert_case", diff --git a/Cargo.toml b/Cargo.toml index 39d38b1..45ed2a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "crates/*" ] resolver = "2" [workspace.package] -version = "0.1.5" +version = "0.1.6" license = "MIT" edition = "2021" repository = "https://github.com/dcodesdev/ts-bind" From d0d49b3397712874c051b6d97f9bb50fcae471e5 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:57:32 +0300 Subject: [PATCH 18/20] fix --- crates/macros/src/struct_attrs.rs | 8 ++++++-- crates/macros/src/ts/gen_ts_code.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index b9d11ff..7bf6fa6 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -6,8 +6,8 @@ use std::path::PathBuf; #[derive(Debug)] pub struct StructAttrs { name: String, - pub rename_all: Option, - pub rename: Option, + rename_all: Option, + rename: Option, export: Option, } @@ -97,4 +97,8 @@ impl StructAttrs { .unwrap_or_else(|| PathBuf::new().join("bindings")) .join(format!("{}.ts", self.get_name())) } + + pub fn get_rename_all(&self) -> Option<&RenameAll> { + self.rename_all.as_ref() + } } diff --git a/crates/macros/src/ts/gen_ts_code.rs b/crates/macros/src/ts/gen_ts_code.rs index a9a590f..1fc9f18 100644 --- a/crates/macros/src/ts/gen_ts_code.rs +++ b/crates/macros/src/ts/gen_ts_code.rs @@ -13,7 +13,7 @@ pub fn gen_ts_code( continue; } - let field_name = if let Some(rename_all) = &struct_attrs.rename_all { + let field_name = if let Some(rename_all) = struct_attrs.get_rename_all() { rename_all.to_case(&ident.to_string()) } else { ident.to_string() From 4f2eb8a70beb46dd7ad0b4fdc71fac5b1493b46d Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:00:30 +0300 Subject: [PATCH 19/20] fix --- crates/macros/src/struct_attrs.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/macros/src/struct_attrs.rs b/crates/macros/src/struct_attrs.rs index 7bf6fa6..e3f2867 100644 --- a/crates/macros/src/struct_attrs.rs +++ b/crates/macros/src/struct_attrs.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; pub struct StructAttrs { name: String, rename_all: Option, - rename: Option, export: Option, } @@ -16,7 +15,6 @@ impl StructAttrs { let mut struct_attrs = Self { name: struct_name, rename_all: None, - rename: None, export: None, }; @@ -41,7 +39,7 @@ impl StructAttrs { let value = get_nested_value(&meta) .expect("Failed to parse rename attribute"); - struct_attrs.rename = Some(value); + struct_attrs.name = value; } "rename_all" => { let value = get_nested_value(&meta) @@ -88,7 +86,7 @@ impl StructAttrs { } pub fn get_name(&self) -> &String { - self.rename.as_ref().unwrap_or(&self.name) + &self.name } pub fn get_export_path(&self) -> PathBuf { From aa957e245cd2411f999fca4df248fd6bc723a9c0 Mon Sep 17 00:00:00 2001 From: dcodesdev <101001810+dcodesdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:06:13 +0300 Subject: [PATCH 20/20] rename --- crates/macros/src/ts_bind.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/macros/src/ts_bind.rs b/crates/macros/src/ts_bind.rs index 2beb6f4..585ce3f 100644 --- a/crates/macros/src/ts_bind.rs +++ b/crates/macros/src/ts_bind.rs @@ -12,9 +12,9 @@ pub fn handle_ts_bind(input: &DeriveInput) -> anyhow::Result { let fields = parse_struct_fields(&input)?; - let ts_bind = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; + let ts_code = gen_ts_code(struct_attrs.get_name(), &fields, &struct_attrs)?; - write_to_file(&struct_attrs.get_export_path(), &ts_bind)?; + write_to_file(&struct_attrs.get_export_path(), &ts_code)?; Ok(quote! {}.into()) }