#[macro_use]
extern crate derive_builder;
extern crate proc_macro;
use quote::quote;
use syn::{parse_macro_input, Item, ItemStruct, LitStr};
#[derive(Builder)]
#[builder(pattern = "mutable")]
struct Cli {
name: String,
}
#[derive(Builder)]
#[builder(pattern = "mutable")]
struct Flag {
long: String,
#[builder(default)]
short: Option<char>,
}
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("Could not applied on unnamed field")]
UnnamedField,
}
#[proc_macro_derive(Completion, attributes(flag))]
pub fn completion(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed_input = parse_macro_input!(input as Item);
if let Item::Struct(item) = parsed_input {
impl_struct(item).unwrap().into()
} else {
quote! {
compile_error!("not used on struct or enum")
}
.into()
}
}
fn impl_struct(item: ItemStruct) -> Result<proc_macro2::TokenStream, Error> {
let mut cli = CliBuilder::default();
let mut flags: Vec<Flag> = vec![];
let struct_name = &item.ident;
cli.name(struct_name.to_string().to_ascii_lowercase());
for field in item.fields.iter() {
let field_name = field.ident.clone().ok_or(Error::UnnamedField)?;
for attr in field.attrs.iter() {
if attr.path().is_ident("flag") {
let mut flag = FlagBuilder::default();
flag.long(field_name.to_string());
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("long") {
let value = meta.value()?;
let s = value.parse::<LitStr>()?.value();
flag.long(s);
} else if meta.path.is_ident("short") {
let c = if let Ok(value) = meta.value() {
value.parse::<LitStr>()?.value()
} else {
field_name.to_string()
};
let c = c
.chars()
.next()
.ok_or(meta.error("expected short flag with single character"))?;
flag.short(Some(c));
} else {
return Err(meta.error("unsupported attribute"));
}
Ok(())
})
.unwrap();
flags.push(flag.build().unwrap());
}
}
}
let cli = cli.build().unwrap();
let _flag_rules = flag_rules(cli, flags).unwrap();
let output = quote! {
use shrs::prelude::*;
impl #struct_name {
pub fn rules(comp: &mut DefaultCompleter) {
comp.register(#_flag_rules);
}
}
};
Ok(output)
}
fn flag_rules(cli: Cli, flags: Vec<Flag>) -> Result<proc_macro2::TokenStream, Error> {
let cli_name = &cli.name;
let long_flags = flags
.iter()
.map(|f| {
let f = format!("--{}", f.long);
quote! { #f.into() }
})
.collect::<Vec<_>>();
let short_flags = flags
.iter()
.filter_map(|f| {
f.short.map(|f| {
let f = format!("-{f}");
quote! { #f.into() }
})
})
.collect::<Vec<_>>();
let output = quote! {
{
let flags_action = |ctx: &CompletionCtx| -> Vec<Completion> {
default_format(
vec![
#(#short_flags),*
,
#(#long_flags),*
]
)
};
Rule::new(
Pred::new(cmdname_eq_pred(#cli_name.into())).and(flag_pred),
Box::new(flags_action)
)
}
};
Ok(output)
}