clappos 0.3.3

A simple, flagless version of Clap that uses argument position for assignment
Documentation
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_derive(Clappos)]
pub fn derive_clappos(item: TokenStream) -> TokenStream {
    //println!("{item}");
    let mut lines = item.to_string().split("\n").map(|s| s.trim().to_string()).collect::<Vec<String>>();
    //println!("{lines:?}");
    let mut struct_comments = vec![];
    while lines[0].starts_with("///") {
        struct_comments.push(lines.remove(0));
    }
    let struct_name =
        lines.remove(0)
            .split(' ')
            .map(|s| s.to_string())
            .collect::<Vec<String>>()
            .into_iter()
            .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?").to_string();
    let mut idents = vec![];
    let mut types = vec![];
    let mut comments = vec![];
    let mut type_expected = false;
    lines.into_iter().for_each(|line| {
        if line.starts_with("///") {
            comments.push(line);
        } 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("///") {
                    comments.push(tokens.join(" "));
                    break;
                } else if token.ends_with(':') {
                    token.remove(token.len() - 1);
                    idents.push(token);
                    type_expected = true;
                } else if type_expected {
                    if token.ends_with(',') {
                        token.remove(token.len() - 1);
                    }
                    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\
                fn parse() -> Self {{\n\
                    let mut args = std::env::args().into_iter().filter(|a| !a.starts_with(\"--\")).map(|a| a).collect::<Vec<String>>();\n\
                    args.remove(0);
                    Self {{\n");


    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}) {{ Some(n_str) => Some(n_str.parse::<{inner_type}>().expect(\"failed to parse {{n_str}} to {inner_type}\")), None => None }},\n").as_str());
            }
        } else {
            if type_ == "String" || type_ == "char" {
                stream.push_str(format!("{ident}: args[{arg_idx}].clone(),\n").as_str());
            } else if type_ == "bool" {
                stream.push_str(format!("{ident}: match args[{arg_idx}].as_str() {{ \"true\" => true, \"false\" => false, x => panic!(\"expected bool, got {{x}}\") }},\n").as_str())
            } else {
                stream.push_str(format!("{ident}: args[{arg_idx}].parse::<{type_}>().expect(\"failed to parse arg args[{arg_idx}] to {type_}\").clone(),\n").as_str())
            }
        }
        arg_idx += 1;
    }
    for arg in std::env::args() {
        if !(arg_idx == 0 || arg.starts_with("--") || idents.len() == 0) {


        }
        arg_idx += 1;
    }
    //println!("{item}\n{stream}");
    stream.push_str("}}}");
    stream.parse().unwrap()
}