Skip to content
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

Complete subcommand to list all subcommands for a given argument. #134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];
/// `parse_positionals`: Helper to parse positional arguments.
/// `parse_subcommand`: Helper to parse a subcommand.
/// `help_func`: Generate a help message.
/// `complete_func`: Generates all possible subcommands for tab completion.
#[doc(hidden)]
pub fn parse_struct_args(
cmd_name: &[&str],
Expand All @@ -804,8 +805,10 @@ pub fn parse_struct_args(
mut parse_positionals: ParseStructPositionals<'_>,
mut parse_subcommand: Option<ParseStructSubCommand<'_>>,
help_func: &dyn Fn() -> String,
complete_func: &dyn Fn() -> String,
) -> Result<(), EarlyExit> {
let mut help = false;
let mut complete = false;
let mut remaining_args = args;
let mut positional_index = 0;
let mut options_ended = false;
Expand All @@ -817,6 +820,11 @@ pub fn parse_struct_args(
continue;
}

if (next_arg == "--complete" || next_arg == "complete") && !options_ended {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claiming --complete or complete is a backwards incompatible change, since it's possible for users to be actively using these as options or subcommands. Can we avoid this? For example, we could land support for complete_func, but still leave it up to the user to implement it, with something like:

#[derive(FromArgs)]
struct MyCmd {
    #[argh(switch)]
    complete: bool,

    ...
}

fn main() {
    let cmd: MyCmd = argh::from_env();
    if cmd.complete {
        return MyCmd::complete();
    }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And good point about the backwards incompatibility. I think this is going to need iterating.

As mentioned in the other comment, this would require going back and adding this switch to every command/subcommand in the ffx tool (which is what I'm targeting to test this stuff) ?

complete = true;
continue;
}

if next_arg.starts_with('-') && !options_ended {
if next_arg == "--" {
options_ended = true;
Expand All @@ -827,6 +835,10 @@ pub fn parse_struct_args(
return Err("Trailing arguments are not allowed after `help`.".to_string().into());
}

if complete {
return Err("Trailing arguments are not allowed after `complete`.".to_string().into());
}

parse_options.parse(next_arg, &mut remaining_args)?;
continue;
}
Expand All @@ -842,8 +854,11 @@ pub fn parse_struct_args(
parse_positionals.parse(&mut positional_index, next_arg)?;
}

// Prioritize a `help` request over a `complete` request.
if help {
Err(EarlyExit { output: help_func(), status: Ok(()) })
} else if complete {
Err(EarlyExit { output: complete_func(), status: Ok(()) })
} else {
Ok(())
}
Expand Down Expand Up @@ -1025,6 +1040,16 @@ pub fn print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) ->
out
}

#[doc(hidden)]
pub fn print_subcommand_list<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String {
let mut out = String::new();
for cmd in commands {
out.push_str(cmd.name);
out.push('\n');
}
out.trim().to_string()
}

fn unrecognized_arg(arg: &str) -> String {
["Unrecognized argument: ", arg, "\n"].concat()
}
Expand Down
33 changes: 33 additions & 0 deletions argh_derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,39 @@ pub(crate) fn help(
} }
}

/// Returns a `TokenStream` generating a `String` list of subcommands.
pub(crate) fn complete(
subcommand: Option<&StructField<'_>>,
) -> TokenStream {
let mut format_lit = "".to_string();
let subcommand_calculation;
let subcommand_format_arg;
if let Some(subcommand) = subcommand {
format_lit.push_str("{subcommands}");
let subcommand_ty = subcommand.ty_without_wrapper;
subcommand_format_arg = quote! { subcommands = subcommands };
subcommand_calculation = quote! {
let subcommands = argh::print_subcommand_list(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also include options in the output?

<#subcommand_ty as argh::SubCommands>::COMMANDS
.iter()
.copied()
.chain(
<#subcommand_ty as argh::SubCommands>::dynamic_commands()
.iter()
.copied())
);
};
} else {
subcommand_calculation = TokenStream::new();
subcommand_format_arg = TokenStream::new()
}

quote! { {
#subcommand_calculation
format!(#format_lit, #subcommand_format_arg)
} }
}

/// A section composed of exactly just the literals provided to the program.
fn lits_section(out: &mut String, heading: &str, lits: &[syn::LitStr]) {
if !lits.is_empty() {
Expand Down
8 changes: 8 additions & 0 deletions argh_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ fn impl_from_args_struct_from_args<'a>(
let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);

// Generate all subcommands of the current command to assist with auto-completion in the shell.
let complete = help::complete(subcommand);

let method_impl = quote_spanned! { impl_span =>
fn from_args(__cmd_name: &[&str], __args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
Expand Down Expand Up @@ -349,6 +352,7 @@ fn impl_from_args_struct_from_args<'a>(
},
#parse_subcommands,
&|| #help,
&|| #complete
)?;

let mut #missing_requirements_ident = argh::MissingRequirements::default();
Expand Down Expand Up @@ -436,6 +440,9 @@ fn impl_from_args_struct_redact_arg_values<'a>(
let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);

// Generate all subcommands of the current command to assist with auto-completion in the shell.
let complete = help::complete(subcommand);

let method_impl = quote_spanned! { impl_span =>
fn redact_arg_values(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
#( #init_fields )*
Expand All @@ -460,6 +467,7 @@ fn impl_from_args_struct_redact_arg_values<'a>(
},
#redact_subcommands,
&|| #help,
&|| #complete,
)?;

let mut #missing_requirements_ident = argh::MissingRequirements::default();
Expand Down