Skip to content

Commit 96ccb81

Browse files
Merge pull request #5 from chuck-flowers/v0.2.0
Pulls the changes for v0.2.0 into master
2 parents 2045ab9 + 02697b4 commit 96ccb81

File tree

9 files changed

+552
-75
lines changed

9 files changed

+552
-75
lines changed

attribution-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "attribution-macros"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Timothy Flowers <[email protected]>"]
55
description = "The macros used by the attribution crate"
66
categories = ["development-tools::procedural-macro-helpers"]

attribution-macros/src/extraction.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ pub fn build_extractor(field: &FieldSpec) -> TokenStream {
88
let field_key = syn::LitStr::new(&field.ident().to_string(), Span::call_site());
99

1010
quote! {
11-
let #var_ident = attr_args.remove(#field_key)
12-
.unwrap()
13-
.try_into()
14-
.unwrap();
11+
let #var_ident = attribution::FromParameters::from_parameters(&mut attr_args, #field_key).unwrap();
1512
}
1613
}
1714

@@ -39,7 +36,7 @@ mod tests {
3936

4037
let extractor = build_extractor(&field);
4138
let expected = quote! {
42-
let foo = attr_args.remove("foo").unwrap().try_into().unwrap();
39+
let foo = attribution::FromParameters::from_parameters(&mut attr_args, "foo").unwrap();
4340
};
4441

4542
assert_eq!(extractor.to_string(), expected.to_string());

attribution-macros/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::all, clippy::cargo)]
2-
2+
#![allow(clippy::multiple_crate_versions)]
33
extern crate proc_macro;
44

55
mod extraction;
@@ -65,8 +65,8 @@ fn impl_parse(struct_name: &Ident, fields: &[FieldSpec]) -> TokenStream {
6565
};
6666

6767
quote! {
68-
impl parse::Parse for #struct_name {
69-
fn parse(buffer: &parse::ParseBuffer) -> parse::Result<Self> {
68+
impl syn::parse::Parse for #struct_name {
69+
fn parse(buffer: &syn::parse::ParseBuffer) -> syn::parse::Result<Self> {
7070
use std::convert::TryInto;
7171
let mut attr_args = attribution::Parameters::parse(buffer)?;
7272
#extraction

attribution/Cargo.toml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "attribution"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Timothy Flowers <[email protected]>"]
55
description = "A declarative custom attribute parsing framework"
66
categories = ["development-tools::procedural-macro-helpers"]
@@ -11,6 +11,18 @@ repository = "https://github.com/chuck-flowers/attribution"
1111
license = "MIT"
1212

1313
[dependencies]
14-
attribution-macros = { version = "0.1.0", path = "../attribution-macros" }
14+
attribution-macros = { version = "0.2.0", path = "../attribution-macros" }
1515
proc-macro2 = "0.4.30"
16-
syn = "0.15.41"
16+
shrinkwraprs = "0.2.1"
17+
syn = "0.15.41"
18+
19+
[dev-dependencies]
20+
quote = "0.6.13"
21+
22+
[[example]]
23+
name = "ez_trace"
24+
crate-type = ["proc-macro"]
25+
26+
[[example]]
27+
name = "gs"
28+
crate-type = ["proc-macro"]

attribution/examples/ez_trace.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
extern crate proc_macro;
2+
3+
use attribution::attr_args;
4+
use proc_macro::TokenStream;
5+
use quote::quote;
6+
use quote::ToTokens;
7+
8+
#[attr_args]
9+
struct EzTraceArgs {
10+
start: Option<String>,
11+
end: Option<String>,
12+
}
13+
14+
#[proc_macro_attribute]
15+
pub fn ez_trace(attr: TokenStream, tagged: TokenStream) -> TokenStream {
16+
ez_trace_impl(attr.into(), tagged.into()).into()
17+
}
18+
19+
fn ez_trace_impl(
20+
attr: proc_macro2::TokenStream,
21+
tagged: proc_macro2::TokenStream,
22+
) -> proc_macro2::TokenStream {
23+
let args = syn::parse2(attr).unwrap();
24+
if let syn::Item::Fn(tagged_fn) = syn::parse2(tagged).unwrap() {
25+
impl_trace(args, tagged_fn)
26+
} else {
27+
panic!("The ez_trace attribute can only be applied to functions");
28+
}
29+
}
30+
31+
fn impl_trace(attr: EzTraceArgs, mut tagged_fn: syn::ItemFn) -> proc_macro2::TokenStream {
32+
let start_statement = if let Some(start_text) = attr.start {
33+
let start_lit = syn::LitStr::new(&start_text, proc_macro2::Span::call_site());
34+
quote! {
35+
println!("{}", #start_lit);
36+
}
37+
} else {
38+
quote! {}
39+
};
40+
41+
let end_statement = if let Some(end_text) = attr.end {
42+
let end_lit = syn::LitStr::new(&end_text, proc_macro2::Span::call_site());
43+
quote! {
44+
println!("{}", #end_lit);
45+
}
46+
} else {
47+
quote! {}
48+
};
49+
50+
let old_block = tagged_fn.block;
51+
let new_block: syn::Block = syn::parse_quote! {
52+
{
53+
#start_statement
54+
let ret = #old_block;
55+
#end_statement
56+
ret
57+
}
58+
};
59+
60+
tagged_fn.block = Box::new(new_block);
61+
62+
tagged_fn.into_token_stream().into()
63+
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
use super::*;
68+
use proc_macro2::TokenTree;
69+
use std::iter::IntoIterator;
70+
use syn::parse_quote;
71+
use syn::Attribute;
72+
use syn::ItemFn;
73+
74+
fn build_expected() -> ItemFn {
75+
parse_quote! {
76+
fn start() {
77+
println!("{}", "Starting...");
78+
let ret = { println!("start"); };
79+
ret
80+
}
81+
}
82+
}
83+
84+
fn build_input_attr() -> Attribute {
85+
parse_quote! { #[ez_trace(start = "Starting...")] }
86+
}
87+
88+
fn build_input_fn() -> ItemFn {
89+
parse_quote! {
90+
fn start() {
91+
println!("start");
92+
}
93+
}
94+
}
95+
96+
#[test]
97+
fn example_trace_test() {
98+
let expected = build_expected();
99+
let expected_str = expected.into_token_stream().to_string();
100+
let raw_attr_start = build_input_attr();
101+
let raw_fn: ItemFn = build_input_fn();
102+
let attr_start_tts = raw_attr_start.tts;
103+
if let TokenTree::Group(group) = attr_start_tts.into_iter().next().unwrap() {
104+
let output = ez_trace_impl(group.stream(), raw_fn.into_token_stream());
105+
let output_str = output.into_token_stream().to_string();
106+
107+
assert_eq!(output_str, expected_str);
108+
}
109+
}
110+
}

attribution/examples/gs.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
extern crate proc_macro;
2+
3+
use attribution::attr_args;
4+
use attribution::DynamicParameters;
5+
use attribution::ParamVal;
6+
use proc_macro::TokenStream;
7+
use proc_macro2::Span;
8+
use proc_macro2::TokenStream as TokenStream2;
9+
use quote::quote;
10+
use syn::Field;
11+
use syn::Fields;
12+
use syn::Ident;
13+
use syn::Item;
14+
use syn::ItemStruct;
15+
16+
#[attr_args]
17+
struct GetterSetterArgs {
18+
field_names: DynamicParameters,
19+
}
20+
21+
#[proc_macro_attribute]
22+
pub fn gs(attr: TokenStream, tagged: TokenStream) -> TokenStream {
23+
impl_gs(attr, tagged).into()
24+
}
25+
26+
fn impl_gs(
27+
into_attr: impl Into<TokenStream2>,
28+
into_tagged: impl Into<TokenStream2>,
29+
) -> TokenStream2 {
30+
let attr = into_attr.into();
31+
let tagged = into_tagged.into();
32+
let args: GetterSetterArgs = syn::parse2(attr).expect("Unabled to parse attribute");
33+
34+
if let Ok(Item::Struct(tagged_struct)) = syn::parse2(tagged) {
35+
add_methods(tagged_struct, args.field_names)
36+
} else {
37+
panic!("The attribute can only be applied to structs")
38+
}
39+
}
40+
41+
fn add_methods(tagged_struct: ItemStruct, parameters: DynamicParameters) -> TokenStream2 {
42+
if let Fields::Named(fields) = &tagged_struct.fields {
43+
// Gets all the methods for the identifiers that were marked as true
44+
let methods = fields
45+
.named
46+
.iter()
47+
.filter(|field| {
48+
let ident_text: String = field.ident.as_ref().unwrap().to_string();
49+
50+
*parameters
51+
.get(&ident_text)
52+
.map(|param_val| {
53+
if let ParamVal::Bool(b) = param_val {
54+
b
55+
} else {
56+
&false
57+
}
58+
})
59+
.unwrap_or(&false)
60+
})
61+
.map(make_method);
62+
63+
let impl_block_name = tagged_struct.ident.clone();
64+
quote! {
65+
#tagged_struct
66+
67+
impl #impl_block_name {
68+
#(#methods)*
69+
}
70+
}
71+
} else {
72+
panic!("The tagged struct must have named fields")
73+
}
74+
}
75+
76+
fn make_method(field: &Field) -> TokenStream2 {
77+
let field_ident = field.ident.as_ref().unwrap();
78+
let field_type = &field.ty;
79+
let setter_field = field_ident.clone();
80+
let setter_type = field_type.clone();
81+
let setter_name = Ident::new(
82+
&format!("set_{}", field_ident.to_string()),
83+
Span::call_site(),
84+
);
85+
86+
let getter_field = field_ident.clone();
87+
let getter_type = syn::TypeReference {
88+
and_token: <syn::Token![&]>::default(),
89+
lifetime: None,
90+
mutability: None,
91+
elem: Box::new(field_type.clone()),
92+
};
93+
let getter_name = Ident::new(
94+
&format!("get_{}", field_ident.to_string()),
95+
Span::call_site(),
96+
);
97+
98+
quote! {
99+
fn #getter_name(&self) -> #getter_type {
100+
self.#getter_field
101+
}
102+
103+
fn #setter_name(&mut self, new_val: #setter_type) {
104+
self.#setter_field = new_val;
105+
}
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
use super::*;
112+
use quote::ToTokens;
113+
114+
#[test]
115+
fn usage_test() {
116+
let raw_attr: syn::Attribute = syn::parse_quote! {
117+
#[gs(foo = true)]
118+
};
119+
120+
let raw_struct: Item = syn::parse_quote! {
121+
struct MyStruct {
122+
foo: u32,
123+
bar: bool,
124+
baz: String
125+
}
126+
};
127+
128+
let expected = quote! {
129+
struct MyStruct {
130+
foo: u32,
131+
bar: bool,
132+
baz: String
133+
}
134+
135+
impl MyStruct {
136+
fn get_foo(&self) -> &u32 {
137+
self.foo
138+
}
139+
140+
fn set_foo(&mut self, new_val: u32) {
141+
self.foo = new_val;
142+
}
143+
}
144+
};
145+
146+
if let proc_macro2::TokenTree::Group(group) = raw_attr.tts.into_iter().next().unwrap() {
147+
let actual = impl_gs(group.stream(), raw_struct.into_token_stream());
148+
assert_eq!(actual.to_string(), expected.to_string());
149+
} else {
150+
panic!()
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)