#![cfg(feature = "unstable-dynamic")] use std::path::Path; use clap::{builder::PossibleValue, Command}; use snapbox::assert_data_eq; macro_rules! complete { ($cmd:expr, $input:expr$(, current_dir = $current_dir:expr)? $(,)?) => { { #[allow(unused)] let current_dir = None; $(let current_dir = $current_dir;)? complete(&mut $cmd, $input, current_dir) } } } #[test] fn suggest_subcommand_subset() { let mut cmd = Command::new("exhaustive") .subcommand(Command::new("hello-world")) .subcommand(Command::new("hello-moon")) .subcommand(Command::new("goodbye-world")); assert_data_eq!( complete!(cmd, "he"), snapbox::str![[r#" hello-moon hello-world help Print this message or the help of the given subcommand(s) "#]], ); } #[test] fn suggest_hidden_long_flags() { let mut cmd = Command::new("exhaustive") .arg(clap::Arg::new("hello-world-visible").long("hello-world-visible")) .arg( clap::Arg::new("hello-world-hidden") .long("hello-world-hidden") .hide(true), ); assert_data_eq!( complete!(cmd, "--hello-world"), snapbox::str!["--hello-world-visible"] ); assert_data_eq!( complete!(cmd, "--hello-world-h"), snapbox::str!["--hello-world-hidden"] ) } #[test] fn suggest_subcommand_aliases() { let mut cmd = Command::new("exhaustive") .subcommand( Command::new("hello-world") .visible_alias("hello-world-foo") .alias("hidden-world"), ) .subcommand( Command::new("hello-moon") .visible_alias("hello-moon-foo") .alias("hidden-moon"), ) .subcommand( Command::new("goodbye-world") .visible_alias("goodbye-world-foo") .alias("hidden-goodbye"), ); assert_data_eq!( complete!(cmd, "hello"), snapbox::str![ "hello-moon hello-moon-foo hello-world hello-world-foo" ], ); } #[test] fn suggest_hidden_long_flag_aliases() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("test_visible") .long("test_visible") .visible_alias("test_visible-alias_visible") .alias("test_visible-alias_hidden"), ) .arg( clap::Arg::new("test_hidden") .long("test_hidden") .visible_alias("test_hidden-alias_visible") .alias("test_hidden-alias_hidden") .hide(true), ); assert_data_eq!( complete!(cmd, "--test"), snapbox::str![ "--test_visible --test_visible-alias_visible" ] ); assert_data_eq!( complete!(cmd, "--test_h"), snapbox::str![ "--test_hidden --test_hidden-alias_visible --test_hidden-alias_hidden" ] ); assert_data_eq!( complete!(cmd, "--test_visible-alias_h"), snapbox::str!["--test_visible-alias_hidden"] ); assert_data_eq!( complete!(cmd, "--test_hidden-alias_h"), snapbox::str!["--test_hidden-alias_hidden"] ); } #[test] fn suggest_long_flag_subset() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("hello-world") .long("hello-world") .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("hello-moon") .long("hello-moon") .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("goodbye-world") .long("goodbye-world") .action(clap::ArgAction::Count), ); assert_data_eq!( complete!(cmd, "--he"), snapbox::str![[r#" --hello-world --hello-moon --help Print help "#]], ); } #[test] fn suggest_possible_value_subset() { let name = "exhaustive"; let mut cmd = Command::new(name).arg(clap::Arg::new("hello-world").value_parser([ PossibleValue::new("hello-world").help("Say hello to the world"), "hello-moon".into(), "goodbye-world".into(), ])); assert_data_eq!( complete!(cmd, "hello"), snapbox::str![[r#" hello-world Say hello to the world hello-moon "#]], ); } #[test] fn suggest_additional_short_flags() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("a") .short('a') .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("b") .short('b') .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("c") .short('c') .action(clap::ArgAction::Count), ); assert_data_eq!( complete!(cmd, "-a"), snapbox::str![[r#" -aa -ab -ac -ah Print help "#]], ); } #[test] fn suggest_subcommand_positional() { let mut cmd = Command::new("exhaustive").subcommand(Command::new("hello-world").arg( clap::Arg::new("hello-world").value_parser([ PossibleValue::new("hello-world").help("Say hello to the world"), "hello-moon".into(), "goodbye-world".into(), ]), )); assert_data_eq!( complete!(cmd, "hello-world [TAB]"), snapbox::str![[r#" --help Print help (see more with '--help') -h Print help (see more with '--help') hello-world Say hello to the world hello-moon goodbye-world "#]], ); } fn complete(cmd: &mut Command, args: impl AsRef, current_dir: Option<&Path>) -> String { let input = args.as_ref(); let mut args = vec![std::ffi::OsString::from(cmd.get_name())]; let arg_index; if let Some((prior, after)) = input.split_once("[TAB]") { args.extend(prior.split_whitespace().map(From::from)); if prior.ends_with(char::is_whitespace) { args.push(std::ffi::OsString::default()); } arg_index = args.len() - 1; // HACK: this cannot handle in-word '[TAB]' args.extend(after.split_whitespace().map(From::from)); } else { args.extend(input.split_whitespace().map(From::from)); if input.ends_with(char::is_whitespace) { args.push(std::ffi::OsString::default()); } arg_index = args.len() - 1; } clap_complete::dynamic::complete(cmd, args, arg_index, current_dir) .unwrap() .into_iter() .map(|candidate| { let compl = candidate.get_content().to_str().unwrap(); if let Some(help) = candidate.get_help() { format!("{compl}\t{help}") } else { compl.to_owned() } }) .collect::>() .join("\n") }