Skip to content

Commit

Permalink
feat: add a attribute proc macro for convenient sake (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
ImJeremyHe authored Nov 20, 2023
1 parent ef105e9 commit c60911f
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 55 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
authors = ["ImJeremyHe<[email protected]>"]
edition = "2018"
name = "gents"
version = "0.6.0"
version = "0.7.0"
license = "MIT"
description = "generate Typescript interfaces from Rust code"
repository = "https://github.com/ImJeremyHe/gents"
keywords = ["Typescript", "interface", "ts-rs", "Rust", "wasm"]

[dev-dependencies]
gents_derives = {version = "0.6.0", path = "./derives"}
gents_derives = {version = "0.7.0", path = "./derives"}
serde = {version = "1.0", features = ["derive"]}
4 changes: 2 additions & 2 deletions derives/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gents_derives"
version = "0.6.0"
version = "0.7.0"
description = "provides some macros for gents"
authors = ["ImJeremyHe<[email protected]>"]
license = "MIT"
Expand All @@ -10,7 +10,7 @@ edition = "2018"
proc-macro = true

[dependencies]
syn = {version = "1.0.86", features = ["full"]}
syn = {version = "2.0.28", features = ["full"]}
quote = "1.0.15"
paste = "1.0.5"
proc-macro2 = "1.0.36"
134 changes: 83 additions & 51 deletions derives/src/container.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use proc_macro2::Ident;
use syn::parse::Parse;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::Attribute;
use syn::Meta::List;
use syn::Meta::NameValue;
use syn::NestedMeta::Meta;
use syn::MetaNameValue;
use syn::Type;

use crate::symbol::FILE_NAME;
Expand Down Expand Up @@ -30,24 +31,22 @@ impl<'a> Container<'a> {
.flat_map(|attr| get_ts_meta_items(attr))
.flatten()
{
match meta_item {
Meta(NameValue(m)) if m.path == RENAME_ALL => {
let s = get_lit_str(&m.lit).expect("rename_all requires lit str");
let t = match s.value().as_str() {
"camelCase" => RenameAll::CamelCase,
_ => panic!("unexpected literal for case converting"),
};
rename_all = Some(t);
}
Meta(NameValue(m)) if m.path == FILE_NAME => {
let s = get_lit_str(&m.lit).expect("file_name requires lit str");
file_name = Some(s.value());
}
Meta(NameValue(m)) if m.path == RENAME => {
let s = get_lit_str(&m.lit).expect("rename requires lit str");
rename = Some(s.value());
}
_ => panic!("unexpected attr"),
let m = meta_item;
if m.path == RENAME_ALL {
let s = get_lit_str(&m.value).expect("rename_all requires lit str");
let t = match s.value().as_str() {
"camelCase" => RenameAll::CamelCase,
_ => panic!("unexpected literal for case converting"),
};
rename_all = Some(t);
} else if m.path == FILE_NAME {
let s = get_lit_str(&m.value).expect("file_name requires lit str");
file_name = Some(s.value());
} else if m.path == RENAME {
let s = get_lit_str(&m.value).expect("rename requires lit str");
rename = Some(s.value());
} else {
panic!("unexpected attr")
}
}
match &item.data {
Expand Down Expand Up @@ -138,20 +137,17 @@ fn parse_attrs<'a>(attrs: &'a Vec<Attribute>) -> FieldAttrs {
.flat_map(|attr| get_ts_meta_items(attr))
.flatten()
{
match meta_item {
Meta(NameValue(m)) if m.path == RENAME => {
if let Ok(s) = get_lit_str(&m.lit) {
rename = Some(s.value());
}
let m = meta_item;
if m.path == RENAME {
if let Ok(s) = get_lit_str(&m.value) {
rename = Some(s.value());
}
Meta(NameValue(m)) if m.path == SKIP => {
if let Ok(s) = get_lit_bool(&m.lit) {
skip = s;
} else {
panic!("expected bool value in skip attr")
}
} else if m.path == SKIP {
if let Ok(s) = get_lit_bool(&m.value) {
skip = s;
} else {
panic!("expected bool value in skip attr")
}
_ => {}
}
}
FieldAttrs { skip, rename }
Expand All @@ -166,41 +162,42 @@ pub enum RenameAll {
CamelCase,
}

fn get_ts_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
if attr.path != TS {
fn get_ts_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::MetaNameValue>, ()> {
if attr.path() != TS {
return Ok(Vec::new());
}

match attr.parse_meta() {
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
Ok(_) => Err(()),
match attr.parse_args_with(Punctuated::<MetaNameValue, Comma>::parse_terminated) {
Ok(name_values) => Ok(name_values.into_iter().collect()),
Err(_) => Err(()),
}
}

fn get_lit_str<'a>(lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
if let syn::Lit::Str(lit) = lit {
Ok(lit)
} else {
Err(())
fn get_lit_str<'a>(lit: &'a syn::Expr) -> Result<&'a syn::LitStr, ()> {
if let syn::Expr::Lit(lit) = lit {
if let syn::Lit::Str(l) = &lit.lit {
return Ok(&l);
}
}
Err(())
}

fn get_lit_bool<'a>(lit: &'a syn::Lit) -> Result<bool, ()> {
if let syn::Lit::Bool(b) = lit {
Ok(b.value)
} else {
Err(())
fn get_lit_bool<'a>(lit: &'a syn::Expr) -> Result<bool, ()> {
if let syn::Expr::Lit(lit) = lit {
if let syn::Lit::Bool(b) = &lit.lit {
return Ok(b.value);
}
}
Err(())
}

fn parse_comments(attrs: &[Attribute]) -> Vec<String> {
let mut result = Vec::new();

attrs.iter().for_each(|attr| {
if attr.path.is_ident("doc") {
if let Ok(NameValue(nv)) = &attr.parse_meta() {
if let Ok(s) = get_lit_str(&nv.lit) {
if attr.path().is_ident("doc") {
if let Ok(nv) = attr.meta.require_name_value() {
if let Ok(s) = get_lit_str(&nv.value) {
let comment = s.value();
result.push(comment.trim().to_string());
}
Expand All @@ -209,3 +206,38 @@ fn parse_comments(attrs: &[Attribute]) -> Vec<String> {
});
result
}

pub(crate) struct GentsWasmAttrs {
file_name: String,
}

impl GentsWasmAttrs {
pub fn get_file_name(&self) -> &str {
&self.file_name
}
}

impl Parse for GentsWasmAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let name_values = Punctuated::<MetaNameValue, Comma>::parse_terminated(input)?;
let mut file_name = String::new();
name_values.into_iter().for_each(|name_value| {
let path = name_value.path;
let attr = path
.get_ident()
.expect("unvalid attr, should be an ident")
.to_string();
let value = get_lit_str(&name_value.value)
.expect("should be a str")
.value();
match attr.as_str() {
"file_name" => file_name = value,
_ => panic!("invalid attr: {}", attr),
}
});
if file_name.is_empty() {
panic!("file_name unset")
}
Ok(GentsWasmAttrs { file_name })
}
}
20 changes: 20 additions & 0 deletions derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ use container::{Container, RenameAll};
use proc_macro::TokenStream;
use quote::quote;

use crate::container::GentsWasmAttrs;

#[proc_macro_attribute]
pub fn gents_header(attr: TokenStream, item: TokenStream) -> TokenStream {
let item: proc_macro2::TokenStream = item.into();
let attrs = syn::parse2::<GentsWasmAttrs>(attr.into()).expect("parse error, please check");
let file_name = attrs.get_file_name();
quote! {
#[derive(::serde::Serialize, ::serde::Deserialize)]
#[cfg_attr(any(test, feature = "gents"), derive(::gents_derives::TS))]
#[cfg_attr(
any(test, feature = "gents"),
ts(file_name = #file_name, rename_all = "camelCase")
)]
#[serde(rename_all = "camelCase")]
#item
}
.into()
}

#[proc_macro_derive(TS, attributes(ts))]
pub fn derive_ts(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand Down
18 changes: 18 additions & 0 deletions src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,24 @@ impl<T: TS + 'static> TS for Option<T> {
}
}

impl<T: TS + 'static, E: TS + 'static> TS for Result<T, E> {
fn _register(manager: &mut DescriptorManager) -> usize {
let t_idx = T::_register(manager);
let e_idx = E::_register(manager);
let type_id = TypeId::of::<Self>();
let descriptor = GenericDescriptor {
dependencies: vec![t_idx, e_idx],
ts_name: Self::_ts_name(),
optional: false,
};
manager.registry(type_id, Descriptor::Generics(descriptor))
}

fn _ts_name() -> String {
format!("{} | {}", T::_ts_name(), E::_ts_name())
}
}

impl<K, V> TS for (K, V)
where
K: TS + 'static,
Expand Down
29 changes: 29 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub struct TestSkip {
mod tests {
use super::*;
use gents::*;
use gents_derives::gents_header;

#[test]
fn gen_skip_test() {
Expand Down Expand Up @@ -178,4 +179,32 @@ export interface StructWithComments {
}"#
);
}

#[test]
fn test_result() {
#[gents_header(file_name = "test_struct.ts")]
pub struct TestStruct {
pub f1: u8,
pub f2: Result<String, u16>,
}
let mut manager = DescriptorManager::default();
TestStruct::_register(&mut manager);
let (_, content) = manager.gen_data().into_iter().next().unwrap();
assert_eq!(
content.trim(),
r#"export interface TestStruct {
f1: number
f2: string | number
}"#
);
}

#[test]
fn test_gents_for_wasm() {
#[gents_header(file_name = "test_struct.ts")]
pub struct TestStruct {
pub f1: u8,
pub f2: String,
}
}
}

0 comments on commit c60911f

Please sign in to comment.