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