extern crate darling;
extern crate proc_macro;
mod attrs;
mod error;
mod opt;
use crate::attrs::Attrs;
use crate::error::{Error, ErrorKind, Result};
use crate::opt::{Opt, OptKind};
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::collections::HashSet;
use syn::{Data, DataStruct, DeriveInput, Fields, FieldsNamed};
#[proc_macro_derive(StructConf, attributes(conf))]
pub fn derive_conf(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let span = name.span();
let result = match ast.data {
Data::Struct(DataStruct {
fields: Fields::Named(named_fields),
..
}) => impl_conf_macro(name, named_fields),
Data::Struct(_) => Err(Error {
kind: ErrorKind::DeriveType("unnamed struct".to_string()),
span,
}),
Data::Enum(_) => Err(Error {
kind: ErrorKind::DeriveType("enum".to_string()),
span,
}),
Data::Union(_) => Err(Error {
kind: ErrorKind::DeriveType("union".to_string()),
span,
}),
};
match result {
Ok(tokens) => tokens,
Err(e) => syn::Error::from(e).to_compile_error().into(),
}
}
fn impl_conf_macro(name: &Ident, fields: FieldsNamed) -> Result<TokenStream> {
let mut options = Vec::new();
let mut tok_fields = Vec::new();
for field in fields.named.into_iter() {
let attr = Attrs::init(field)?;
let (opt1, opt2) = attr.parse_opt()?;
let name = &opt1.base.id;
let default = &opt1.gen_default()?;
let first = &opt1.gen_field_init()?;
let second = if let Some(opt) = &opt2 {
let tok = opt.gen_field_init()?;
quote! { else #tok }
} else {
quote! {}
};
tok_fields.push(quote! {
#name: {
#first
#second
else { #default }
}
});
options.push(opt1);
if let Some(opt) = opt2 {
options.push(opt);
}
}
check_conflicts(&options)?;
let mut tok_args = Vec::new();
let mut tok_write_file = Vec::new();
for opt in options {
if let Some(tok) = opt.gen_arg_init() {
tok_args.push(tok);
}
if let Some(tok) = opt.gen_write_file() {
tok_write_file.push(tok);
}
}
let trait_impl = quote! {
impl StructConf for #name {
fn parse(
app: ::structconf::clap::App,
path: &str
) -> ::std::result::Result<#name, ::structconf::Error>
where
Self: ::std::marker::Sized
{
let args = #name::parse_args(app);
#name::parse_file(&args, path)
}
fn parse_args<'a>(
app: ::structconf::clap::App<'a, 'a>
) -> ::structconf::clap::ArgMatches<'a> {
#name::parse_args_from(
app,
&mut ::std::env::args()
)
}
fn parse_args_from<'a, I, T>(
app: ::structconf::clap::App<'a, 'a>,
iter: I,
) -> ::structconf::clap::ArgMatches<'a>
where
I: ::std::iter::IntoIterator<Item = T>,
T: ::std::convert::Into<::std::ffi::OsString>
+ ::std::clone::Clone {
app.args(&[
#(#tok_args,)*
]).get_matches_from(iter)
}
fn parse_file(
args: &::structconf::clap::ArgMatches,
path: &str
) -> ::std::result::Result<#name, ::structconf::Error>
where
Self: ::std::marker::Sized {
let path_wrap = ::std::path::Path::new(path);
if !path_wrap.exists() {
::std::fs::File::create(&path_wrap)?;
eprintln!("Created config file at {}", path);
}
let file = ::structconf::ini::Ini::load_from_file(path)?;
Ok(#name {
#(#tok_fields,)*
})
}
fn write_file(
&self,
path: &str
) -> ::std::result::Result<(), ::structconf::Error> {
let mut conf = ::structconf::ini::Ini::new();
#(#tok_write_file)*
conf.write_to_file(path)?;
Ok(())
}
}
};
Ok(trait_impl.into())
}
fn check_conflicts(opts: &[Opt]) -> Result<()> {
let mut files = HashSet::new();
let mut longs = HashSet::new();
let mut shorts = HashSet::new();
macro_rules! try_insert {
($iter:expr, $new:expr, $span:expr, $err_id:expr) => {
if !$iter.insert($new) {
return Err(Error {
kind: ErrorKind::ConflictIDs($err_id.to_string(), $new),
span: $span,
});
}
};
}
for opt in opts {
let span = opt.base.id.span();
match &opt.kind {
OptKind::Empty => {}
OptKind::Flag(arg) | OptKind::Arg(arg) => {
if let Some(short) = &arg.short {
try_insert!(shorts, short.clone(), span, "short");
}
if let Some(long) = &arg.long {
try_insert!(longs, long.clone(), span, "long");
}
}
OptKind::File(file) => {
try_insert!(files, file.name.clone(), span, "file");
}
}
}
Ok(())
}