Skip to content

Commit c60911f

Browse files
authored
feat: add a attribute proc macro for convenient sake (#10)
1 parent ef105e9 commit c60911f

File tree

6 files changed

+155
-55
lines changed

6 files changed

+155
-55
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
authors = ["ImJeremyHe<[email protected]>"]
33
edition = "2018"
44
name = "gents"
5-
version = "0.6.0"
5+
version = "0.7.0"
66
license = "MIT"
77
description = "generate Typescript interfaces from Rust code"
88
repository = "https://github.com/ImJeremyHe/gents"
99
keywords = ["Typescript", "interface", "ts-rs", "Rust", "wasm"]
1010

1111
[dev-dependencies]
12-
gents_derives = {version = "0.6.0", path = "./derives"}
12+
gents_derives = {version = "0.7.0", path = "./derives"}
13+
serde = {version = "1.0", features = ["derive"]}

derives/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gents_derives"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
description = "provides some macros for gents"
55
authors = ["ImJeremyHe<[email protected]>"]
66
license = "MIT"
@@ -10,7 +10,7 @@ edition = "2018"
1010
proc-macro = true
1111

1212
[dependencies]
13-
syn = {version = "1.0.86", features = ["full"]}
13+
syn = {version = "2.0.28", features = ["full"]}
1414
quote = "1.0.15"
1515
paste = "1.0.5"
1616
proc-macro2 = "1.0.36"

derives/src/container.rs

Lines changed: 83 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use proc_macro2::Ident;
2+
use syn::parse::Parse;
3+
use syn::punctuated::Punctuated;
4+
use syn::token::Comma;
25
use syn::Attribute;
3-
use syn::Meta::List;
4-
use syn::Meta::NameValue;
5-
use syn::NestedMeta::Meta;
6+
use syn::MetaNameValue;
67
use syn::Type;
78

89
use crate::symbol::FILE_NAME;
@@ -30,24 +31,22 @@ impl<'a> Container<'a> {
3031
.flat_map(|attr| get_ts_meta_items(attr))
3132
.flatten()
3233
{
33-
match meta_item {
34-
Meta(NameValue(m)) if m.path == RENAME_ALL => {
35-
let s = get_lit_str(&m.lit).expect("rename_all requires lit str");
36-
let t = match s.value().as_str() {
37-
"camelCase" => RenameAll::CamelCase,
38-
_ => panic!("unexpected literal for case converting"),
39-
};
40-
rename_all = Some(t);
41-
}
42-
Meta(NameValue(m)) if m.path == FILE_NAME => {
43-
let s = get_lit_str(&m.lit).expect("file_name requires lit str");
44-
file_name = Some(s.value());
45-
}
46-
Meta(NameValue(m)) if m.path == RENAME => {
47-
let s = get_lit_str(&m.lit).expect("rename requires lit str");
48-
rename = Some(s.value());
49-
}
50-
_ => panic!("unexpected attr"),
34+
let m = meta_item;
35+
if m.path == RENAME_ALL {
36+
let s = get_lit_str(&m.value).expect("rename_all requires lit str");
37+
let t = match s.value().as_str() {
38+
"camelCase" => RenameAll::CamelCase,
39+
_ => panic!("unexpected literal for case converting"),
40+
};
41+
rename_all = Some(t);
42+
} else if m.path == FILE_NAME {
43+
let s = get_lit_str(&m.value).expect("file_name requires lit str");
44+
file_name = Some(s.value());
45+
} else if m.path == RENAME {
46+
let s = get_lit_str(&m.value).expect("rename requires lit str");
47+
rename = Some(s.value());
48+
} else {
49+
panic!("unexpected attr")
5150
}
5251
}
5352
match &item.data {
@@ -138,20 +137,17 @@ fn parse_attrs<'a>(attrs: &'a Vec<Attribute>) -> FieldAttrs {
138137
.flat_map(|attr| get_ts_meta_items(attr))
139138
.flatten()
140139
{
141-
match meta_item {
142-
Meta(NameValue(m)) if m.path == RENAME => {
143-
if let Ok(s) = get_lit_str(&m.lit) {
144-
rename = Some(s.value());
145-
}
140+
let m = meta_item;
141+
if m.path == RENAME {
142+
if let Ok(s) = get_lit_str(&m.value) {
143+
rename = Some(s.value());
146144
}
147-
Meta(NameValue(m)) if m.path == SKIP => {
148-
if let Ok(s) = get_lit_bool(&m.lit) {
149-
skip = s;
150-
} else {
151-
panic!("expected bool value in skip attr")
152-
}
145+
} else if m.path == SKIP {
146+
if let Ok(s) = get_lit_bool(&m.value) {
147+
skip = s;
148+
} else {
149+
panic!("expected bool value in skip attr")
153150
}
154-
_ => {}
155151
}
156152
}
157153
FieldAttrs { skip, rename }
@@ -166,41 +162,42 @@ pub enum RenameAll {
166162
CamelCase,
167163
}
168164

169-
fn get_ts_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
170-
if attr.path != TS {
165+
fn get_ts_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::MetaNameValue>, ()> {
166+
if attr.path() != TS {
171167
return Ok(Vec::new());
172168
}
173169

174-
match attr.parse_meta() {
175-
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
176-
Ok(_) => Err(()),
170+
match attr.parse_args_with(Punctuated::<MetaNameValue, Comma>::parse_terminated) {
171+
Ok(name_values) => Ok(name_values.into_iter().collect()),
177172
Err(_) => Err(()),
178173
}
179174
}
180175

181-
fn get_lit_str<'a>(lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
182-
if let syn::Lit::Str(lit) = lit {
183-
Ok(lit)
184-
} else {
185-
Err(())
176+
fn get_lit_str<'a>(lit: &'a syn::Expr) -> Result<&'a syn::LitStr, ()> {
177+
if let syn::Expr::Lit(lit) = lit {
178+
if let syn::Lit::Str(l) = &lit.lit {
179+
return Ok(&l);
180+
}
186181
}
182+
Err(())
187183
}
188184

189-
fn get_lit_bool<'a>(lit: &'a syn::Lit) -> Result<bool, ()> {
190-
if let syn::Lit::Bool(b) = lit {
191-
Ok(b.value)
192-
} else {
193-
Err(())
185+
fn get_lit_bool<'a>(lit: &'a syn::Expr) -> Result<bool, ()> {
186+
if let syn::Expr::Lit(lit) = lit {
187+
if let syn::Lit::Bool(b) = &lit.lit {
188+
return Ok(b.value);
189+
}
194190
}
191+
Err(())
195192
}
196193

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

200197
attrs.iter().for_each(|attr| {
201-
if attr.path.is_ident("doc") {
202-
if let Ok(NameValue(nv)) = &attr.parse_meta() {
203-
if let Ok(s) = get_lit_str(&nv.lit) {
198+
if attr.path().is_ident("doc") {
199+
if let Ok(nv) = attr.meta.require_name_value() {
200+
if let Ok(s) = get_lit_str(&nv.value) {
204201
let comment = s.value();
205202
result.push(comment.trim().to_string());
206203
}
@@ -209,3 +206,38 @@ fn parse_comments(attrs: &[Attribute]) -> Vec<String> {
209206
});
210207
result
211208
}
209+
210+
pub(crate) struct GentsWasmAttrs {
211+
file_name: String,
212+
}
213+
214+
impl GentsWasmAttrs {
215+
pub fn get_file_name(&self) -> &str {
216+
&self.file_name
217+
}
218+
}
219+
220+
impl Parse for GentsWasmAttrs {
221+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
222+
let name_values = Punctuated::<MetaNameValue, Comma>::parse_terminated(input)?;
223+
let mut file_name = String::new();
224+
name_values.into_iter().for_each(|name_value| {
225+
let path = name_value.path;
226+
let attr = path
227+
.get_ident()
228+
.expect("unvalid attr, should be an ident")
229+
.to_string();
230+
let value = get_lit_str(&name_value.value)
231+
.expect("should be a str")
232+
.value();
233+
match attr.as_str() {
234+
"file_name" => file_name = value,
235+
_ => panic!("invalid attr: {}", attr),
236+
}
237+
});
238+
if file_name.is_empty() {
239+
panic!("file_name unset")
240+
}
241+
Ok(GentsWasmAttrs { file_name })
242+
}
243+
}

derives/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ use container::{Container, RenameAll};
88
use proc_macro::TokenStream;
99
use quote::quote;
1010

11+
use crate::container::GentsWasmAttrs;
12+
13+
#[proc_macro_attribute]
14+
pub fn gents_header(attr: TokenStream, item: TokenStream) -> TokenStream {
15+
let item: proc_macro2::TokenStream = item.into();
16+
let attrs = syn::parse2::<GentsWasmAttrs>(attr.into()).expect("parse error, please check");
17+
let file_name = attrs.get_file_name();
18+
quote! {
19+
#[derive(::serde::Serialize, ::serde::Deserialize)]
20+
#[cfg_attr(any(test, feature = "gents"), derive(::gents_derives::TS))]
21+
#[cfg_attr(
22+
any(test, feature = "gents"),
23+
ts(file_name = #file_name, rename_all = "camelCase")
24+
)]
25+
#[serde(rename_all = "camelCase")]
26+
#item
27+
}
28+
.into()
29+
}
30+
1131
#[proc_macro_derive(TS, attributes(ts))]
1232
pub fn derive_ts(input: TokenStream) -> TokenStream {
1333
let input = parse_macro_input!(input as DeriveInput);

src/descriptor.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,24 @@ impl<T: TS + 'static> TS for Option<T> {
286286
}
287287
}
288288

289+
impl<T: TS + 'static, E: TS + 'static> TS for Result<T, E> {
290+
fn _register(manager: &mut DescriptorManager) -> usize {
291+
let t_idx = T::_register(manager);
292+
let e_idx = E::_register(manager);
293+
let type_id = TypeId::of::<Self>();
294+
let descriptor = GenericDescriptor {
295+
dependencies: vec![t_idx, e_idx],
296+
ts_name: Self::_ts_name(),
297+
optional: false,
298+
};
299+
manager.registry(type_id, Descriptor::Generics(descriptor))
300+
}
301+
302+
fn _ts_name() -> String {
303+
format!("{} | {}", T::_ts_name(), E::_ts_name())
304+
}
305+
}
306+
289307
impl<K, V> TS for (K, V)
290308
where
291309
K: TS + 'static,

tests/test.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub struct TestSkip {
4747
mod tests {
4848
use super::*;
4949
use gents::*;
50+
use gents_derives::gents_header;
5051

5152
#[test]
5253
fn gen_skip_test() {
@@ -178,4 +179,32 @@ export interface StructWithComments {
178179
}"#
179180
);
180181
}
182+
183+
#[test]
184+
fn test_result() {
185+
#[gents_header(file_name = "test_struct.ts")]
186+
pub struct TestStruct {
187+
pub f1: u8,
188+
pub f2: Result<String, u16>,
189+
}
190+
let mut manager = DescriptorManager::default();
191+
TestStruct::_register(&mut manager);
192+
let (_, content) = manager.gen_data().into_iter().next().unwrap();
193+
assert_eq!(
194+
content.trim(),
195+
r#"export interface TestStruct {
196+
f1: number
197+
f2: string | number
198+
}"#
199+
);
200+
}
201+
202+
#[test]
203+
fn test_gents_for_wasm() {
204+
#[gents_header(file_name = "test_struct.ts")]
205+
pub struct TestStruct {
206+
pub f1: u8,
207+
pub f2: String,
208+
}
209+
}
181210
}

0 commit comments

Comments
 (0)