clappos 0.3.10

A simple, flagless version of Clap that uses argument position for assignment with zero dependencies
Documentation
extern crate proc_macro;
use proc_macro::TokenStream;
//use std::time::SystemTime;


#[proc_macro_derive(Clappos)]
pub fn derive_clappos(item: TokenStream) -> TokenStream {
    //let start = SystemTime::now();
    //println!("{item}");
    let mut lines = item.to_string().split("\n").map(|s| s.trim().to_string()).collect::<Vec<String>>();

    for i in 0..lines.len() {
        if lines[i].contains("{") {
            let line = lines.remove(i);
            line.split('{').rev().for_each(|s| lines.insert(i, s.trim().to_string() ));
        }
    }


    //println!("lines: {lines:?}");
    let mut help_menu = String::new();
    while lines[0].starts_with("///") {
        let comment = lines.remove(0);
        help_menu.push_str(format!("{comment}\n").as_str())
    }
    help_menu.push_str("\n");
    let struct_name =
        lines.remove(0)
            .split(' ')
            .map(|s| s.to_string())
            .find(|s| {
                let first_char = s.as_bytes()[0];
                first_char >= 0x41 && first_char <= 0x5a    // the utf8 range of capital letters
            })
            .expect("didn't find struct name?");
    let mut idents = vec![];
    let mut types = vec![];
    let mut type_expected = false;
    lines.into_iter().for_each(|line| {
        if line.starts_with("///") {
            help_menu.push_str(format!("\n{line}").as_str())
        } else if line.len() > 1 {
            let tokens = line.split(' ').map(|s| s.to_string()).collect::<Vec<String>>();
            //println!("tokens {tokens:?}");
            for token in tokens.iter() {
                let mut token = token.trim().to_string();
                if token.starts_with("///") {
                    let comment = join_comment(tokens);
                    help_menu.push_str(format!("\n{}", comment).as_str());
                    break;
                } else if token.ends_with(':') {
                    token.remove(token.len() - 1);
                    help_menu.push_str(format!("\n{token}: ").as_str());
                    idents.push(token);
                    type_expected = true;
                } else if type_expected {
                    if token.ends_with(',') {
                        token.remove(token.len() - 1);
                    }
                    help_menu.push_str(format!("{token}\n").as_str());
                    types.push(token);
                    type_expected = false;
                }
            }

        }

    });
    //println!("STRUCT NAME: {struct_name}");
    //println!("IDENTS: {idents:?}");
    //println!("TYPES: {types:?}");
    let mut stream =
        format!(
            "impl {struct_name} {{\n\
                #[allow(unreachable_code)] \n\
                fn parse() -> Self {{\n\
                    let mut args = std::env::args().into_iter().filter(|a| !a.starts_with(\"--\")).collect::<Vec<String>>();\n\
                    args.remove(0); \n\
                    Self {{\n");

    let ident_help = idents.iter().map(|s| format!("<{s}>")).collect::<Vec<String>>().join(" ");
    let usage_string = format!("\n\nusage: cargo run {ident_help}\nor\n./<executable> {ident_help}\n\n");
    help_menu.push_str(usage_string.as_str());
    let mut arg_idx = 0;
    for i in 0..idents.len() {
        let ident = &idents[i];
        let type_ = &types[i];
        if type_.starts_with("Option<") {
            let inner_type = &type_[7..(type_.len() - 1)];
            if inner_type == "String" || inner_type == "bool" || inner_type == "char" {
                stream.push_str(format!("{ident}: args.get({arg_idx}).cloned(),\n").as_str())
            } else {
                stream.push_str(format!(
                    "{ident}: match args.get({arg_idx}) {{ \n\
                         Some(n_str) => match n_str.parse::<{inner_type}>() {{\n\
                             Ok(n) => Some(n), \n\
                             Err(e) => {{\n\
                                 println!(\"{help_menu}\"); \n\
                                 exit(0); \n\
                                 unreachable!(\"already exited\") \n\
                             }}, \n\
                         }}, \n\
                         None => None, \n\
                    }},\n").as_str());
            }
        } else {
            if type_ == "String" || type_ == "char" {
                stream.push_str(format!(
                    "{ident}: match args.get({arg_idx}) {{\n\
                        Some(s) => s.clone(), \n\
                        None => {{\n\
                            println!(\"{help_menu}\"); \n\
                            exit(0); \n\
                            unreachable!(\"process already exited\") \n\
                        }}, \n\
                    }},\n").as_str());
            } else if type_ == "bool" {
                stream.push_str(format!(
                    "{ident}: match args.get({arg_idx}){{\n\
                         Some(arg) => match arg.as_str() {{ \n\
                            \"True\" | \"true\" | \"T\" | \"t\" => true, \n\
                            \"False\" | \"false\" | \"F\" | \"f\" => false, \n\
                            x => {{\
                                println!(\"{help_menu}\"); \n\
                                exit(0); \n\
                                unreachable!(\"already exited\") \n\
                            }} \n\
                         }}, \n\
                         None => {{\n\
                             println!(\"{help_menu}\"); \n\
                             exit(0); \n\
                             unreachable!(\"already exited\") \n\
                         }}, \n\
                    }},\n").as_str())
            } else {
                stream.push_str(format!(
                    "{ident}: match args[{arg_idx}].parse::<{type_}>() {{\n\
                        Ok(n) => n, \n\
                        Err(e) => {{\n\
                            println!(\"{help_menu}\"); \n\
                            exit(0); \n\
                            unreachable!(\"already exited\") \n\
                        }}, \n\
                    }},\n").as_str())
            }
        }
        arg_idx += 1;
    }
    //println!("{item}\n{stream}");
    stream.push_str("}}}");
    let stream =  stream.parse().expect("failed to parse struct");
    //let elapsed = start.elapsed().unwrap();
    //println!("parse took: {elapsed:?}");
    stream
}

fn join_comment(tokens: Vec<String>) -> String {
    let mut slash_idx = 0;
    for i in 0..tokens.len() {
        if tokens[i].starts_with("///") {
            slash_idx = i;
            break;
        }
    }
    let v = &tokens[slash_idx..];
    v.join(" ")
}