-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow subcommand flattening #126
Comments
For my usecase I ended up writing following derive proc macro: #[proc_macro_derive(FlattenSubcommand)]
pub fn flatten_subcommand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let de = match ast.data {
syn::Data::Enum(v) => v,
_ => unreachable!(),
};
let name = &ast.ident;
// An enum variant like `<name>(<ty>)`
struct SubCommandVariant<'a> {
name: &'a syn::Ident,
ty: &'a syn::Type,
}
let variants: Vec<SubCommandVariant<'_>> = de
.variants
.iter()
.map(|variant| {
let name = &variant.ident;
let ty = match &variant.fields {
syn::Fields::Unnamed(field) => {
if field.unnamed.len() != 1 {
unreachable!()
}
&field.unnamed.first().unwrap().ty
}
_ => unreachable!(),
};
SubCommandVariant { name, ty }
})
.collect();
let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
(quote! {
impl argh::FromArgs for #name {
fn from_args(command_name: &[&str], args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
{
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
*subcommand_name
} else {
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
};
#(
if <#variant_ty as argh::SubCommands>::COMMANDS
.iter()
.find(|ci| ci.name.eq(subcommand_name))
.is_some()
{
return <#variant_ty as argh::FromArgs>::from_args(command_name, args)
.map(|v| Self::#variant_names(v));
}
)*
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
}
fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
*subcommand_name
} else {
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
};
#(
if <#variant_ty as argh::SubCommands>::COMMANDS
.iter()
.find(|ci| ci.name.eq(subcommand_name))
.is_some()
{
return <#variant_ty as argh::FromArgs>::redact_arg_values(
command_name,
args,
);
}
)*
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
}
}
impl argh::SubCommands for #name {
const COMMANDS: &'static [&'static argh::CommandInfo] = {
const TOTAL_LEN: usize = #(<#variant_ty as argh::SubCommands>::COMMANDS.len())+*;
const COMMANDS: [&'static argh::CommandInfo; TOTAL_LEN] = {
let slices = &[#(<#variant_ty as argh::SubCommands>::COMMANDS,)*];
// Its not possible for slices[0][0] to be invalid
let mut output = [slices[0][0]; TOTAL_LEN];
let mut output_index = 0;
let mut which_slice = 0;
while which_slice < slices.len() {
let slice = &slices[which_slice];
let mut index_in_slice = 0;
while index_in_slice < slice.len() {
output[output_index] = slice[index_in_slice];
output_index += 1;
index_in_slice += 1;
}
which_slice += 1;
}
output
};
&COMMANDS
};
}
})
.into()
} However I'm not sure if |
Duplicate of #15 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is already an issue open for struct flattening. This one is specifically for subcommand flattenin.
Here is use-case example (using proc-macro from second comment):
The text was updated successfully, but these errors were encountered: