argh
argh copied to clipboard
Allow subcommand flattening
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):
#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
#[argh(subcommand)]
nested: MySubCommandEnum,
}
#[derive(PartialEq, Debug, FlattenSubcommand)]
enum MySubCommandEnum {
Global(MySubCommandEnumGlobal),
Sys(MySubCommandEnumSys),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnumGlobal {
One(SubCommandOne),
Two(SubCommandTwo),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnumSys {
Three(SubCommandThree),
Four(SubCommandFour),
}
#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one")]
struct SubCommandOne {}
#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
struct SubCommandTwo {
#[argh(option)]
/// x
x: usize,
}
#[derive(FromArgs, PartialEq, Debug)]
/// Third subcommand.
#[argh(subcommand, name = "three")]
struct SubCommandThree {}
#[derive(FromArgs, PartialEq, Debug)]
/// Fourth subcommand.
#[argh(subcommand, name = "four")]
struct SubCommandFour {}
fn main() {
let up: TopLevel = argh::from_env();
println!("{:?}", up);
}
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 argh
is interested in this feature and if adding another derive macro is the way they would like to do that
Duplicate of https://github.com/google/argh/issues/15
Duplicate of #15
#15 specifically talks about structs and i'm talking about flattening command enums here