thunder 0.2.0

Create simple commandline apps with *zero* boilerplate!
Documentation
//!

#![feature(proc_macro, proc_macro_lib)]
#![allow(unused_imports, unused_variables)]

extern crate proc_macro;

#[macro_use]
extern crate syn;

#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use quote::ToTokens;
use std::collections::HashSet as Set;
use syn::fold::{self, Fold};
use syn::punctuated::Punctuated;
use syn::synom::Synom;
use syn::LitStr;
use syn::{
    Expr, FnArg, Ident, ImplItem, ImplItemMethod, Item, ItemImpl, ItemStatic, Pat, Stmt, Type,
};

/// Main macro that implements automated clap generation.
///
/// Tag an `impl` block with this attribute of a type. Then
/// call `start()` on the type to handle match parsing.
#[proc_macro_attribute]
pub fn thunderclap(_args: TokenStream, input: TokenStream) -> TokenStream {
    let i: ItemImpl = match syn::parse(input.clone()) {
        Ok(input) => input,
        Err(e) => panic!("Error: '{}'", e),
    };

    let (name, app_token) = match *i.self_ty {
        Type::Path(ref p) => {
            let meh = p.path.segments[0].ident;
            (format!("{}", p.path.segments[0].ident), quote!( #meh ))
        }
        _ => (format!("Unknown App"), quote!()),
    };

    let about = match i.attrs.first() {
        Some(a) => String::from(
            format!("{}", a.tts)
                        /* Clean the tokens TODO: Make this not suck */
                        .replace("/", "")
                        .replace("\\", "")
                        .replace("\"", "")
                        .replace("=", "").trim(),
        ),
        _ => String::new(),
    };

    let mut matches: Vec<quote::Tokens> = Vec::new();
    let orignal = quote!(#i);
    let mut app = quote! {
        App::new(#name).about(#about).setting(AppSettings::SubcommandRequired)
    };

    for item in &i.items {
        match item {
            &ImplItem::Method(ref i) => {
                let name = LitStr::new(&i.sig.ident.to_string(), i.sig.ident.span);
                let func_id = &i.sig.ident;
                let about = match i.attrs.first() {
                    Some(a) => String::from(
                        format!("{}", a.tts)
                        /* Clean the tokens TODO: Make this not suck */
                        .replace("/", "")
                        .replace("\\", "")
                        .replace("\"", "")
                        .replace("=", "").trim(),
                    ),
                    _ => String::new(),
                };

                let mut arguments = quote!();

                let mut index: usize = 0;
                let args = i.sig
                    .decl
                    .inputs
                    .iter()
                    .fold(quote!{}, |acc, arg| match arg {
                        &FnArg::Captured(ref arg) => match &arg.pat {
                            &Pat::Ident(ref i) => {
                                let n = format!("{}", i.ident);
                                arguments = quote! {
                                    #arguments
                                    m.value_of(#n).unwrap(),
                                };

                                index += 1;
                                quote! { #acc.arg(Arg::with_name(#n)) }
                            }
                            _ => quote!{ #acc },
                        },
                        _ => quote!{ #acc },
                    });

                app = quote! {
                    #app.subcommand(
                        SubCommand::with_name(#name).about(#about)#args
                    )
                };

                matches.push(quote! { (#name, Some(m)) => #app_token :: #func_id ( #arguments ), });
            }
            _ => {}
        }
    }

    // let mut matchy = quote!{ match args.subcommand() { };
    let mut matchy = quote!{};

    for m in &matches {
        matchy = quote! {
            #matchy
            #m
        };
    }

    matchy = quote! {
        match args.subcommand() {
            #matchy
            _ => { /* We drop errors for now... */ },
        }
    };

    let tokens = quote! {
        #orignal

        /// This block was generated by thunder v0.0.0
        #[allow(unused)]
        impl #app_token {

            /// Starts the CLI parsing and calls whichever function handles the input
            fn start() {
                use clap::{App, SubCommand, Arg, AppSettings};

                let app = #app;
                let args = app.get_matches();
                #matchy
            }
        }
    };

    tokens.into()
}