fpr-cli-derives 0.4.8

A library that allows one to write cli tools quickly. (derive macros)
Documentation
use super::prelude::*;

#[derive(Debug, FromField)]
#[darling(attributes(arg))]
struct Arg {
    desc: Option<LitStr>,
    s: Option<Expr>,
    d: Option<Expr>,
    ident: Option<Ident>,
    ty: Type,
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(args), supports(struct_named))]
struct Args {
    desc: LitStr,
    f: Ident,
    pos: Option<LitStr>,
    ident: Ident,
    data: ast::Data<(), Arg>,
}

fn is_arg(a: &Arg) -> bool {
    !(a.desc.is_none())
}

fn a(p: Args) -> Result<TokenStream, String> {
    let i = &p.ident;
    let k = LitStr::new(i.to_string().to_case(Case::Kebab).as_str(), i.span());

    enum F {
        Args(Ident, Type),
        Arg(Ident, Type, LitStr, proc_macro2::TokenStream, LitStr),
    }

    let f: Vec<_> = p
        .data
        .take_struct()
        .ok_or(format!("Expected a struct."))?
        .fields
        .into_iter()
        .map(|f| -> Result<F, String> {
            let b = is_arg(&f);
            let i = f.ident.unwrap();
            let ty = f.ty;
            Ok(if !b {
                F::Args(i, ty)
            } else {
                let desc = f.desc.unwrap();
                let k = LitStr::new(
                    format!("--{}", i.to_string().to_case(Case::Kebab)).as_str(),
                    i.span(),
                );

                if f.s.is_some() && f.d.is_some() {
                    return Err(format!(
                        "Only one of 's' (static) and 'd' (dynamic) should be set"
                    ));
                }

                let init = if let Some(s) = f.s {
                    quote! { Init::Const((#s).into()) }
                } else if let Some(d) = f.d {
                    quote! { Init::Dyn(|c| (#d)(c).into()) }
                } else {
                    quote! { Init::None }
                };
                F::Arg(i, ty, desc, init, k)
            })
        })
        .collect::<Result<Vec<_>, _>>()?;
    let desc = &p.desc;
    let func = &p.f;
    let pos = &p.pos;

    let parse = f
        .iter()
        .map(|a| {
            match a {
                F::Args(ident, _) => 
                    quote! { #ident: Args::new(c, args)? },
                F::Arg(ident, ty, _, init, k) => quote! { #ident: <#ty>::parse2(#init, #k, c, args).map_err(|e| ErrArgs::Arg { arg: #k.into(), e })? },
            }
        })
        .collect::<Vec<_>>();

    let descs = match pos{
        Some(e) => vec![quote! { r.push([format!("positional"), format!("Vec<String>"), format!(#e), format!("")]) } ],
        None => vec![],
    };
    let descs = descs.into_iter().chain( f
        .iter()
        .map(|a| match a {
            F::Args(_, ty) => quote! {
                #ty::add_usage(c, r)
            },
            F::Arg(_, ty, desc, init, k) => quote! { r.push(<#ty>::desc2(#init, #desc, #k, c)) },
        }))
        .collect::<Vec<_>>();
    let defaults: Vec<_> = f
        .iter()
        .map(|a| match a {
            F::Args(i, ty) => quote! { #i: <#ty>::default(c) },
            F::Arg(i, ty, _, init, _) => quote! { #i: <#ty>::default2(c, #init) },
        })
        .collect();

    let run = match pos {
        Some(_) => quote! {
            fn run(self, c:&C, a: Vec<&str>) -> Result<(), String> {
                let _ = #func(c, self, a)?;
                Ok(())
            }
        },
        None => quote! {
            fn run(self, c:&C, _: Vec<&str>) -> Result<(), String> {
                let _ = #func(c, self)?;
                Ok(())
            }
        },
    };

    Ok(quote! {
        impl Args<C> for #i {
            fn new<'a, 'b>(c: &C, args: &mut ParsedArgs<'b>) -> Result<Self, ErrArgs>{
                Ok(Self{
                   #( #parse, )*
                })
            }
            fn desc_act() -> &'static str { #desc }
            fn add_usage(c: &C, r: &mut Vec<[String; 4]>) {
                #( #descs; )*
            }
            fn add_paths<'a>(pfx: &Vec<Arg<'a>>, p: &mut Vec<Vec<Arg<'a>>>) {
                let mut pfx = pfx.clone();
                pfx.push(#k);
                p.push(pfx);
            }
            fn default(c: &C) -> Self {
                Self { #( #defaults, )* }
            }
            #run
        }
    }
    .into())
}

pub fn f(i: TokenStream) -> TokenStream {
    let p = match Args::from_derive_input(&parse_macro_input!(i as DeriveInput)) {
        Ok(p) => p,
        Err(e) => return e.write_errors().into(),
    };

    match a(p) {
        Ok(p) => p,
        Err(e) => Error::custom(e).write_errors().into(),
    }
}