1extern crate proc_macro;
2use proc_macro::TokenStream;
3#[proc_macro_derive(Clappos)]
7pub fn derive_clappos(item: TokenStream) -> TokenStream {
8 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 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 })
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 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 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 stream.push_str("}}}");
151 let stream = stream.parse().expect("failed to parse struct");
152 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}