-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Riley Trautman
committed
Apr 6, 2018
1 parent
bbf4b84
commit 6606449
Showing
2 changed files
with
134 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,15 @@ | ||
[package] | ||
name = "tokio-zmq-derive" | ||
description = "Provides derivation for Tokio ZMQ Socket wrapper types" | ||
version = "0.4.1" | ||
version = "0.4.2" | ||
license = "GPL-3.0" | ||
authors = ["Riley Trautman <[email protected]>"] | ||
repository = "https://github.com/asonix/tokio-zmq" | ||
|
||
[dependencies] | ||
quote = "0.3" | ||
syn = "0.11" | ||
synstructure = "0.6" | ||
quote = "0.5" | ||
syn = "0.13" | ||
proc-macro2 = "0.3" | ||
|
||
[lib] | ||
proc-macro = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,161 @@ | ||
extern crate proc_macro; | ||
extern crate proc_macro2; | ||
extern crate syn; | ||
|
||
#[macro_use] | ||
extern crate quote; | ||
#[macro_use] | ||
extern crate synstructure; | ||
|
||
decl_derive!([SocketWrapper, attributes(stream, sink, try_from)] => socket_derive); | ||
use proc_macro::TokenStream; | ||
use proc_macro2::TokenTree; | ||
use syn::{Attribute, Data, DeriveInput, Fields, Ident, Type}; | ||
|
||
#[proc_macro_derive(SocketWrapper, attributes(sink, stream, try_from))] | ||
pub fn socket_derive(input: TokenStream) -> TokenStream { | ||
let input: DeriveInput = syn::parse(input).unwrap(); | ||
|
||
if !only_has_inner_socket(&input.data) { | ||
panic!("Expected to derive for struct with inner: Socket"); | ||
} | ||
|
||
let name = input.ident; | ||
|
||
fn socket_derive(s: synstructure::Structure) -> quote::Tokens { | ||
let socket_binding = s.variants().iter().find(|v| { | ||
v.bindings().iter().any(|b| { | ||
if let Some(ref ident) = b.ast().ident { | ||
if ident == "inner" { | ||
return true; | ||
let from_parts = quote! { | ||
impl From<(zmq::Socket, PollEvented<File<ZmqFile>>)> for #name { | ||
fn from(tup: (zmq::Socket, PollEvented<File<ZmqFile>>)) -> Self { | ||
#name { | ||
inner: tup.into() | ||
} | ||
} | ||
} | ||
}; | ||
|
||
false | ||
}) | ||
}); | ||
|
||
if let Some(sb) = socket_binding { | ||
let name = sb.ast().ident; | ||
let from_parts = s.bound_impl( | ||
"From<(zmq::Socket, PollEvented<File<ZmqFile>>)>", | ||
quote! { | ||
fn from(tup: (zmq::Socket, PollEvented<File<ZmqFile>>)) -> Self { | ||
#name { | ||
inner: tup.into(), | ||
let as_socket = quote! { | ||
impl ::prelude::AsSocket for #name { | ||
fn socket(self) -> Socket { | ||
self.inner | ||
} | ||
} | ||
}; | ||
|
||
let try_from = { | ||
let socket_type = Ident::from(format!("{}", name).to_uppercase().as_ref()); | ||
|
||
let try_from_attr = input.attrs.iter().find(|attr| { | ||
attr.path | ||
.segments | ||
.last() | ||
.map(|seg| seg.into_value()) | ||
.map(|seg| seg.ident == Ident::new("try_from", seg.ident.span())) | ||
.unwrap_or(false) | ||
}); | ||
|
||
let try_from_config = if let Some(attr) = try_from_attr { | ||
attr.tts | ||
.clone() | ||
.into_iter() | ||
.filter_map(|token_tree| match token_tree { | ||
TokenTree::Literal(literal) => { | ||
Some(Ident::from(format!("{}", literal).trim_matches('"'))) | ||
} | ||
} | ||
}, | ||
); | ||
let as_socket = s.bound_impl( | ||
"::prelude::AsSocket", | ||
quote! { | ||
fn socket(self) -> Socket { | ||
self.inner | ||
} | ||
}, | ||
); | ||
|
||
let try_from = build_try_from(&s, name); | ||
|
||
let stream = if has_attr(&s, "stream") { | ||
s.bound_impl("::prelude::StreamSocket", quote!{}) | ||
_ => None, | ||
}) | ||
.next() | ||
} else { | ||
quote!{} | ||
Some(Ident::from("SockConfig")) | ||
}; | ||
|
||
let sink = if has_attr(&s, "sink") { | ||
s.bound_impl("::prelude::SinkSocket", quote!{}) | ||
} else { | ||
quote!{} | ||
let conf = match try_from_config { | ||
Some(conf) => conf, | ||
None => panic!("try_from must take the name of the config struct"), | ||
}; | ||
|
||
quote! { | ||
#from_parts | ||
#as_socket | ||
#stream | ||
#sink | ||
#try_from | ||
quote!{ | ||
impl<'a> TryFrom<#conf<'a>> for #name { | ||
type Error = Error; | ||
|
||
fn try_from(conf: #conf<'a>) -> Result<Self, Self::Error> { | ||
Ok(#name { | ||
inner: conf.build(zmq::#socket_type)?, | ||
}) | ||
} | ||
} | ||
} | ||
}; | ||
|
||
let stream = if has_attr(&input.attrs, "stream") { | ||
quote!{ | ||
impl ::prelude::StreamSocket for #name {} | ||
} | ||
} else { | ||
panic!("Could not find socket"); | ||
} | ||
} | ||
quote!{} | ||
}; | ||
|
||
fn has_attr(s: &synstructure::Structure, attr: &str) -> bool { | ||
s.ast().attrs.iter().any(|a| is_attr(a, attr)) | ||
let sink = if has_attr(&input.attrs, "sink") { | ||
quote!{ | ||
impl ::prelude::SinkSocket for #name {} | ||
} | ||
} else { | ||
quote!{} | ||
}; | ||
|
||
let full = quote! { | ||
#from_parts | ||
#as_socket | ||
#stream | ||
#sink | ||
#try_from | ||
}; | ||
|
||
full.into() | ||
} | ||
|
||
fn is_attr(a: &syn::Attribute, attr: &str) -> bool { | ||
a.name() == attr | ||
fn has_attr(attrs: &[Attribute], name: &str) -> bool { | ||
attrs.iter().any(|attr| { | ||
attr.path | ||
.segments | ||
.last() | ||
.map(|seg| seg.into_value()) | ||
.map(|seg| seg.ident == Ident::new(name, seg.ident.span())) | ||
.unwrap_or(false) | ||
}) | ||
} | ||
|
||
fn build_try_from(s: &synstructure::Structure, name: &syn::Ident) -> quote::Tokens { | ||
let socket_type = syn::Ident::new(format!("{}", name).to_uppercase()); | ||
fn only_has_inner_socket(input: &Data) -> bool { | ||
let data_struct = match *input { | ||
Data::Struct(ref data_struct) => data_struct, | ||
_ => return false, // TODO: Make this work for enums with sockets in each varient? | ||
}; | ||
|
||
let try_from_attr = s.ast().attrs.iter().find(|a| is_attr(a, "try_from")); | ||
let fields_named = match data_struct.fields { | ||
Fields::Named(ref fields_named) => fields_named, | ||
_ => return false, // TODO: Allow other kinds of structs? | ||
}; | ||
|
||
let mut try_from_conf = None; | ||
|
||
if let Some(ref try_from) = try_from_attr { | ||
if let syn::MetaItem::NameValue(_, syn::Lit::Str(ref name, _)) = try_from.value { | ||
let name: String = name.to_owned(); | ||
try_from_conf = Some(syn::Ident::new(name)); | ||
} else { | ||
panic!("try_from must take the name of the config struct"); | ||
} | ||
if fields_named.named.len() != 1 { | ||
return false; | ||
} | ||
|
||
let conf = try_from_conf.unwrap_or(syn::Ident::new("SockConfig")); | ||
let field = fields_named.named.first().unwrap().into_value(); | ||
|
||
quote! { | ||
impl<'a> TryFrom<#conf<'a>> for #name { | ||
type Error = Error; | ||
let found = field | ||
.ident | ||
.map(|id| id == Ident::new("inner", id.span())) | ||
.unwrap_or(false); | ||
|
||
fn try_from(conf: #conf<'a>) -> Result<Self, Self::Error> { | ||
Ok(#name { | ||
inner: conf.build(zmq::#socket_type)?, | ||
}) | ||
} | ||
} | ||
if !found { | ||
return false; | ||
} | ||
|
||
let type_path = match field.ty { | ||
Type::Path(ref type_path) => type_path, | ||
_ => return false, | ||
}; | ||
|
||
type_path | ||
.path | ||
.segments | ||
.last() | ||
.map(|ps| ps.into_value()) | ||
.map(|ps| ps.ident == Ident::new("Socket", ps.ident.span())) | ||
.unwrap_or(false) | ||
} |