clappos/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3//use std::time::SystemTime;
4
5
6#[proc_macro_derive(Clappos)]
7pub fn derive_clappos(item: TokenStream) -> TokenStream {
8    //let start = SystemTime::now();
9    //println!("{item}");
10    let mut lines = item.to_string().split("\n").map(|s| s.trim().to_string()).collect::<Vec<String>>();
11
12    for i in 0..lines.len() {
13        if lines[i].contains("{") {
14            let line = lines.remove(i);
15            line.split('{').rev().for_each(|s| lines.insert(i, s.trim().to_string() ));
16        }
17    }
18
19
20    //println!("lines: {lines:?}");
21    let mut help_menu = String::new();
22    while lines[0].starts_with("///") {
23        let comment = lines.remove(0);
24        help_menu.push_str(format!("{comment}\n").as_str())
25    }
26    help_menu.push_str("\n");
27    let struct_name =
28        lines.remove(0)
29            .split(' ')
30            .map(|s| s.to_string())
31            .find(|s| {
32                let first_char = s.as_bytes()[0];
33                first_char >= 0x41 && first_char <= 0x5a    // the utf8 range of capital letters
34            })
35            .expect("didn't find struct name?");
36    let mut idents = vec![];
37    let mut types = vec![];
38    let mut type_expected = false;
39    lines.into_iter().for_each(|line| {
40        if line.starts_with("///") {
41            help_menu.push_str(format!("\n{line}").as_str())
42        } else if line.len() > 1 {
43            let tokens = line.split(' ').map(|s| s.to_string()).collect::<Vec<String>>();
44            //println!("tokens {tokens:?}");
45            for token in tokens.iter() {
46                let mut token = token.trim().to_string();
47                if token.starts_with("///") {
48                    let comment = join_comment(tokens);
49                    help_menu.push_str(format!("\n{}", comment).as_str());
50                    break;
51                } else if token.ends_with(':') {
52                    token.remove(token.len() - 1);
53                    help_menu.push_str(format!("\n{token}: ").as_str());
54                    idents.push(token);
55                    type_expected = true;
56                } else if type_expected {
57                    if token.ends_with(',') {
58                        token.remove(token.len() - 1);
59                    }
60                    help_menu.push_str(format!("{token}\n").as_str());
61                    types.push(token);
62                    type_expected = false;
63                }
64            }
65
66        }
67
68    });
69    //println!("STRUCT NAME: {struct_name}");
70    //println!("IDENTS: {idents:?}");
71    //println!("TYPES: {types:?}");
72    let mut stream =
73        format!(
74            "impl {struct_name} {{\n\
75                #[allow(unreachable_code)] \n\
76                fn parse() -> Self {{\n\
77                    let mut args = std::env::args().into_iter().filter(|a| !a.starts_with(\"--\")).collect::<Vec<String>>();\n\
78                    args.remove(0); \n\
79                    Self {{\n");
80
81    let ident_help = idents.iter().map(|s| format!("<{s}>")).collect::<Vec<String>>().join(" ");
82    let usage_string = format!("\n\nusage: cargo run {ident_help}\nor\n./<executable> {ident_help}\n\n");
83    help_menu.push_str(usage_string.as_str());
84    let mut arg_idx = 0;
85    for i in 0..idents.len() {
86        let ident = &idents[i];
87        let type_ = &types[i];
88        if type_.starts_with("Option<") {
89            let inner_type = &type_[7..(type_.len() - 1)];
90            if inner_type == "String" || inner_type == "bool" || inner_type == "char" {
91                stream.push_str(format!("{ident}: args.get({arg_idx}).cloned(),\n").as_str())
92            } else {
93                stream.push_str(format!(
94                    "{ident}: match args.get({arg_idx}) {{ \n\
95                         Some(n_str) => match n_str.parse::<{inner_type}>() {{\n\
96                             Ok(n) => Some(n), \n\
97                             Err(e) => {{\n\
98                                 println!(\"{help_menu}\"); \n\
99                                 exit(0); \n\
100                                 unreachable!(\"already exited\") \n\
101                             }}, \n\
102                         }}, \n\
103                         None => None, \n\
104                    }},\n").as_str());
105            }
106        } else {
107            if type_ == "String" || type_ == "char" {
108                stream.push_str(format!(
109                    "{ident}: match args.get({arg_idx}) {{\n\
110                        Some(s) => s.clone(), \n\
111                        None => {{\n\
112                            println!(\"{help_menu}\"); \n\
113                            exit(0); \n\
114                            unreachable!(\"process already exited\") \n\
115                        }}, \n\
116                    }},\n").as_str());
117            } else if type_ == "bool" {
118                stream.push_str(format!(
119                    "{ident}: match args.get({arg_idx}){{\n\
120                         Some(arg) => match arg.as_str() {{ \n\
121                            \"True\" | \"true\" | \"T\" | \"t\" => true, \n\
122                            \"False\" | \"false\" | \"F\" | \"f\" => false, \n\
123                            x => {{\
124                                println!(\"{help_menu}\"); \n\
125                                exit(0); \n\
126                                unreachable!(\"already exited\") \n\
127                            }} \n\
128                         }}, \n\
129                         None => {{\n\
130                             println!(\"{help_menu}\"); \n\
131                             exit(0); \n\
132                             unreachable!(\"already exited\") \n\
133                         }}, \n\
134                    }},\n").as_str())
135            } else {
136                stream.push_str(format!(
137                    "{ident}: match args[{arg_idx}].parse::<{type_}>() {{\n\
138                        Ok(n) => n, \n\
139                        Err(e) => {{\n\
140                            println!(\"{help_menu}\"); \n\
141                            exit(0); \n\
142                            unreachable!(\"already exited\") \n\
143                        }}, \n\
144                    }},\n").as_str())
145            }
146        }
147        arg_idx += 1;
148    }
149    //println!("{item}\n{stream}");
150    stream.push_str("}}}");
151    let stream =  stream.parse().expect("failed to parse struct");
152    //let elapsed = start.elapsed().unwrap();
153    //println!("parse took: {elapsed:?}");
154    stream
155}
156
157fn join_comment(tokens: Vec<String>) -> String {
158    let mut slash_idx = 0;
159    for i in 0..tokens.len() {
160        if tokens[i].starts_with("///") {
161            slash_idx = i;
162            break;
163        }
164    }
165    let v = &tokens[slash_idx..];
166    v.join(" ")
167}