diff options
| author | Jonas Maier <> | 2026-03-08 08:28:57 +0100 |
|---|---|---|
| committer | Jonas Maier <> | 2026-03-08 08:28:57 +0100 |
| commit | deafab9c930ab092c4ee8abd4cbe09c7eb34aa22 (patch) | |
| tree | 8cd3b19f41eb37d7fef0ad45e707020b0e9a0fc6 /pish_derive/src | |
| parent | 46a1a298af63c82e48fd0aa805f32c691eb7ff97 (diff) | |
| download | pish-deafab9c930ab092c4ee8abd4cbe09c7eb34aa22.tar.gz | |
argument parsing
Diffstat (limited to 'pish_derive/src')
| -rw-r--r-- | pish_derive/src/lib.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/pish_derive/src/lib.rs b/pish_derive/src/lib.rs new file mode 100644 index 0000000..2ac98f7 --- /dev/null +++ b/pish_derive/src/lib.rs @@ -0,0 +1,96 @@ +use proc_macro::{Literal, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{Data, DeriveInput, Fields, parse_macro_input}; + +#[proc_macro_derive(FromArgs)] +pub fn derive_cli(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + + let fields = match input.data { + Data::Struct(data) => data.fields, + _ => panic!("Cli can only be derived for structs"), + }; + + let mut field_parsers_init = Vec::new(); + let mut field_parsers = Vec::new(); + let mut field_parsers_post = Vec::new(); + let mut field_names = Vec::new(); + + if let Fields::Named(fields_named) = fields { + for field in fields_named.named { + let ident = field.ident.unwrap(); + let name_str = ident.to_string(); + + let mut long_name = b"--".to_vec(); + long_name.extend_from_slice(name_str.as_bytes()); + let long_name = proc_macro2::Literal::byte_string(&long_name); + + field_names.push(ident.clone()); + + // initialization + field_parsers_init.push(quote! { + let mut #ident = None; + }); + + let is_bool = field.ty.to_token_stream().to_string() == String::from("bool"); + let is_option = field.ty.to_token_stream().to_string().starts_with("Option"); // bad bad detection + + // in the loop + if is_bool { + field_parsers.push(quote! { + if arg == #long_name { + #ident = Some(true); + continue; + } + }); + } else { + field_parsers.push(quote! { + if arg == #long_name { + let Some(val) = iter.next() else { + return Err(ArgParseError::MissingArgValue(#name_str)); + }; + match String::from_utf8_lossy(val).parse() { + Ok(parsed) => { + #ident = Some(parsed); + continue; + } + Err(err) => { + return Err(ArgParseError::ArgValueParseError(#name_str, format!("{err:?}"))); + } + } + } + }); + } + + // after loop + if is_bool { + field_parsers_post.push(quote!{ let #ident = #ident.unwrap_or(false); }); + } else if !is_option { + field_parsers_post.push(quote!{ let Some(#ident) = #ident else { return Err(ArgParseError::MissingArg(#name_str)) }; }); + } + } + } + + let expanded = quote! { + impl ArgParse for #name { + fn parse<'a>(args: &'a [BString]) -> std::result::Result<Self, ArgParseError<'a>> { + let mut iter = args.iter().skip(1); + + #(#field_parsers_init)* + + while let Some(arg) = iter.next() { + #(#field_parsers)*; + return Err(ArgParseError::LeftoverArg(arg)); + } + + #(#field_parsers_post)* + + Ok(Self { #( #field_names ),* }) + } + } + }; + + TokenStream::from(expanded) +} |
