extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
use crate::field_def::{Container, Context, FieldDef};
use proc_macro::TokenStream;
use std::result;
use syn::DeriveInput;
mod field_def;
#[proc_macro_derive(FixedWidth, attributes(fixed_width))]
pub fn fixed_width(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
impl_fixed_width(&input)
}
fn impl_fixed_width(ast: &DeriveInput) -> TokenStream {
let fields: Vec<syn::Field> = match ast.data {
syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
if fields.iter().any(|field| field.ident.is_none()) {
panic!("struct has unnamed fields");
}
fields.iter().cloned().collect()
}
_ => panic!("#[derive(FixedWidth)] can only be used with structs"),
};
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let container = Container::from_ast(ast);
if container.fixed_width_fn.is_some() {
let field_def = container.fixed_width_fn.unwrap();
for field in &fields {
for attr in &field.attrs {
if attr.path().is_ident("fixed_width") {
panic!("specify whether container attribue `field_def` or field attribue respectively");
}
}
}
let quote = quote! {
impl #impl_generics fixed_width::FixedWidth for #ident #ty_generics #where_clause {
fn fields() -> fixed_width::FieldSet {
#field_def()
}
}
};
quote.into()
} else {
let tokens: Vec<proc_macro2::TokenStream> = fields
.iter()
.filter(should_skip)
.map(build_field_def)
.map(build_fixed_width_field)
.collect();
let quote = quote! {
impl #impl_generics fixed_width::FixedWidth for #ident #ty_generics #where_clause {
fn fields() -> fixed_width::FieldSet {
fixed_width::field_seq![#(#tokens),*]
}
}
};
quote.into()
}
}
fn should_skip(field: &&syn::Field) -> bool {
!Context::from_field(field).skip
}
fn build_field_def(field: &syn::Field) -> FieldDef {
let ctx = Context::from_field(field);
let name = match ctx.metadata.get("name") {
Some(name) => name.value.clone(),
None => ctx.field_name(),
};
let range = if let Some(r) = ctx.metadata.get("range") {
let range_parts = r
.value
.split("..")
.map(str::parse)
.filter_map(result::Result::ok)
.collect::<Vec<usize>>();
if range_parts.len() != 2 {
panic!("Invalid range {} for field: {}", r.value, ctx.field_name());
}
range_parts[0]..range_parts[1]
} else {
panic!("Must supply a byte range for field: {}", ctx.field_name());
};
let pad_with = ctx.metadata.get("pad_with").map_or(' ', |c| {
if c.value.len() != 1 {
panic!("pad_with must be a char for field: {}", ctx.field_name());
}
c.value.chars().next().unwrap()
});
let justify = match ctx.metadata.get("justify") {
Some(j) => match j.value.to_lowercase().trim() {
"left" | "right" => j.value.clone(),
_ => panic!(
"justify must be 'left' or 'right' for field: {}",
ctx.field_name()
),
},
None => "left".to_string(),
};
FieldDef {
ident: ctx.field.ident.unwrap(),
field_type: field.ty.clone(),
name,
pad_with,
range,
justify,
}
}
fn build_fixed_width_field(field_def: FieldDef) -> proc_macro2::TokenStream {
let name = field_def.name;
let start = field_def.range.start;
let end = field_def.range.end;
let pad_with = field_def.pad_with;
let justify = field_def.justify;
quote! {
fixed_width::FieldSet::new_field(#start..#end)
.name(#name)
.pad_with(#pad_with)
.justify(#justify.to_string())
}
}