2721: feat(generate): Give `Shell` superpowers to simplify getting started r=pksunkara a=epage



Co-authored-by: Ed Page <eopage@gmail.com>
This commit is contained in:
bors[bot] 2021-10-11 23:48:17 +00:00 committed by GitHub
commit 74cbe0bf09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 255 additions and 174 deletions

View file

@ -12,23 +12,12 @@
//! . ./value_hints_derive.fish
//! ./target/debug/examples/value_hints_derive --<TAB>
//! ```
use clap::{App, AppSettings, ArgEnum, IntoApp, Parser, ValueHint};
use clap_generate::generators::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_generate::{generate, Generator};
use clap::{App, AppSettings, IntoApp, Parser, ValueHint};
use clap_generate::{generate, Generator, Shell};
use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
#[derive(ArgEnum, Debug, PartialEq, Clone)]
enum GeneratorChoice {
Bash,
Elvish,
Fish,
#[clap(name = "powershell")]
PowerShell,
Zsh,
}
#[derive(Parser, Debug, PartialEq)]
#[clap(
name = "value_hints_derive",
@ -38,7 +27,7 @@ enum GeneratorChoice {
struct Opt {
/// If provided, outputs the completion file for given shell
#[clap(long = "generate", arg_enum)]
generator: Option<GeneratorChoice>,
generator: Option<Shell>,
// Showcasing all possible ValueHints:
#[clap(long, value_hint = ValueHint::Unknown)]
unknown: Option<String>,
@ -68,8 +57,8 @@ struct Opt {
email: Option<String>,
}
fn print_completions<G: Generator>(app: &mut App) {
generate::<G, _>(app, app.get_name().to_string(), &mut io::stdout());
fn print_completions<G: Generator>(gen: G, app: &mut App) {
generate(gen, app, app.get_name().to_string(), &mut io::stdout());
}
fn main() {
@ -78,13 +67,7 @@ fn main() {
if let Some(generator) = opt.generator {
let mut app = Opt::into_app();
eprintln!("Generating completion file for {:?}...", generator);
match generator {
GeneratorChoice::Bash => print_completions::<Bash>(&mut app),
GeneratorChoice::Elvish => print_completions::<Elvish>(&mut app),
GeneratorChoice::Fish => print_completions::<Fish>(&mut app),
GeneratorChoice::PowerShell => print_completions::<PowerShell>(&mut app),
GeneratorChoice::Zsh => print_completions::<Zsh>(&mut app),
}
print_completions(generator, &mut app);
} else {
println!("{:#?}", opt);
}

View file

@ -35,6 +35,7 @@ clap = { path = "../", version = "=3.0.0-beta.4" }
[dev-dependencies]
pretty_assertions = "0.7"
version-sync = "0.9"
itertools = "0.10"
[features]
default = []

View file

@ -7,5 +7,5 @@ fn main() {
.subcommand(App::new("test").subcommand(App::new("config")))
.subcommand(App::new("hello"));
generate::<Bash, _>(&mut app, "myapp", &mut io::stdout());
generate(Bash, &mut app, "myapp", &mut io::stdout());
}

View file

@ -13,21 +13,18 @@
//! ./target/debug/examples/value_hints --<TAB>
//! ```
use clap::{App, AppSettings, Arg, ValueHint};
use clap_generate::generators::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_generate::{generate, Generator};
use clap_generate::{generate, Generator, Shell};
use std::io;
fn build_cli() -> App<'static> {
App::new("value_hints")
// AppSettings::TrailingVarArg is required to use ValueHint::CommandWithArguments
.setting(AppSettings::TrailingVarArg)
.arg(Arg::new("generator").long("generate").possible_values([
"bash",
"elvish",
"fish",
"powershell",
"zsh",
]))
.arg(
Arg::new("generator")
.long("generate")
.possible_values(Shell::arg_values()),
)
.arg(
Arg::new("unknown")
.long("unknown")
@ -95,23 +92,16 @@ fn build_cli() -> App<'static> {
)
}
fn print_completions<G: Generator>(app: &mut App) {
generate::<G, _>(app, app.get_name().to_string(), &mut io::stdout());
fn print_completions<G: Generator>(gen: G, app: &mut App) {
generate(gen, app, app.get_name().to_string(), &mut io::stdout());
}
fn main() {
let matches = build_cli().get_matches();
if let Some(generator) = matches.value_of("generator") {
if let Ok(generator) = matches.value_of_t::<Shell>("generator") {
let mut app = build_cli();
eprintln!("Generating completion file for {}...", generator);
match generator {
"bash" => print_completions::<Bash>(&mut app),
"elvish" => print_completions::<Elvish>(&mut app),
"fish" => print_completions::<Fish>(&mut app),
"powershell" => print_completions::<PowerShell>(&mut app),
"zsh" => print_completions::<Zsh>(&mut app),
_ => panic!("Unknown generator"),
}
print_completions(generator, &mut app);
}
}

View file

@ -21,13 +21,13 @@ pub trait Generator {
/// pub struct Fish;
///
/// impl Generator for Fish {
/// # fn generate(app: &App, buf: &mut dyn Write) {}
/// fn file_name(name: &str) -> String {
/// # fn generate(&self, app: &App, buf: &mut dyn Write) {}
/// fn file_name(&self, name: &str) -> String {
/// format!("{}.fish", name)
/// }
/// }
/// ```
fn file_name(name: &str) -> String;
fn file_name(&self, name: &str) -> String;
/// Generates output out of [`clap::App`](App).
///
@ -44,24 +44,24 @@ pub trait Generator {
/// pub struct ClapDebug;
///
/// impl Generator for ClapDebug {
/// fn generate(app: &App, buf: &mut dyn Write) {
/// fn generate(&self, app: &App, buf: &mut dyn Write) {
/// write!(buf, "{}", app).unwrap();
/// }
/// # fn file_name(name: &str) -> String {
/// # fn file_name(&self, name: &str) -> String {
/// # name.into()
/// # }
/// }
/// ```
fn generate(app: &App, buf: &mut dyn Write);
fn generate(&self, app: &App, buf: &mut dyn Write);
/// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`.
///
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
fn all_subcommands(app: &App) -> Vec<(String, String)> {
let mut subcmds: Vec<_> = Self::subcommands(app);
fn all_subcommands(&self, app: &App) -> Vec<(String, String)> {
let mut subcmds: Vec<_> = self.subcommands(app);
for sc_v in app.get_subcommands().map(|s| Self::all_subcommands(s)) {
for sc_v in app.get_subcommands().map(|s| self.all_subcommands(s)) {
subcmds.extend(sc_v);
}
@ -72,6 +72,7 @@ pub trait Generator {
///
/// **NOTE:** `path` should not contain the root `bin_name`.
fn find_subcommand_with_path<'help, 'app>(
&self,
p: &'app App<'help>,
path: Vec<&str>,
) -> &'app App<'help> {
@ -88,7 +89,7 @@ pub trait Generator {
///
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
fn subcommands(p: &App) -> Vec<(String, String)> {
fn subcommands(&self, p: &App) -> Vec<(String, String)> {
debug!("subcommands: name={}", p.get_name());
debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
@ -115,7 +116,7 @@ pub trait Generator {
/// Gets all the short options, their visible aliases and flags of a [`clap::App`].
/// Includes `h` and `V` depending on the [`clap::AppSettings`].
fn shorts_and_visible_aliases(p: &App) -> Vec<char> {
fn shorts_and_visible_aliases(&self, p: &App) -> Vec<char> {
debug!("shorts: name={}", p.get_name());
p.get_arguments()
@ -140,7 +141,7 @@ pub trait Generator {
/// Gets all the long options, their visible aliases and flags of a [`clap::App`].
/// Includes `help` and `version` depending on the [`clap::AppSettings`].
fn longs_and_visible_aliases(p: &App) -> Vec<String> {
fn longs_and_visible_aliases(&self, p: &App) -> Vec<String> {
debug!("longs: name={}", p.get_name());
p.get_arguments()
@ -170,7 +171,7 @@ pub trait Generator {
/// Gets all the flags of a [`clap::App`](App).
/// Includes `help` and `version` depending on the [`clap::AppSettings`].
fn flags<'help>(p: &App<'help>) -> Vec<Arg<'help>> {
fn flags<'help>(&self, p: &App<'help>) -> Vec<Arg<'help>> {
debug!("flags: name={}", p.get_name());
p.get_flags().cloned().collect()
}
@ -184,9 +185,9 @@ mod tests {
struct Foo;
impl Generator for Foo {
fn generate(_: &App, _: &mut dyn Write) {}
fn generate(&self, _: &App, _: &mut dyn Write) {}
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
name.to_string()
}
}
@ -217,7 +218,7 @@ mod tests {
let app = common();
assert_eq!(
Foo::subcommands(&app),
Foo.subcommands(&app),
vec![
("test".to_string(), "my-app test".to_string()),
("hello".to_string(), "my-app hello".to_string()),
@ -231,7 +232,7 @@ mod tests {
let app = common();
assert_eq!(
Foo::all_subcommands(&app),
Foo.all_subcommands(&app),
vec![
("test".to_string(), "my-app test".to_string()),
("hello".to_string(), "my-app hello".to_string()),
@ -244,7 +245,7 @@ mod tests {
#[test]
fn test_find_subcommand_with_path() {
let app = common();
let sc_app = Foo::find_subcommand_with_path(&app, "test config".split(' ').collect());
let sc_app = Foo.find_subcommand_with_path(&app, "test config".split(' ').collect());
assert_eq!(sc_app.get_name(), "config");
}
@ -252,13 +253,13 @@ mod tests {
#[test]
fn test_flags() {
let app = common();
let flags = Foo::flags(&app);
let flags = Foo.flags(&app);
assert_eq!(flags.len(), 2);
assert_eq!(flags[0].get_long(), Some("help"));
assert_eq!(flags[1].get_long(), Some("version"));
let sc_flags = Foo::flags(Foo::find_subcommand_with_path(&app, vec!["test"]));
let sc_flags = Foo.flags(Foo.find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_flags.len(), 3);
assert_eq!(sc_flags[0].get_long(), Some("file"));
@ -269,14 +270,14 @@ mod tests {
#[test]
fn test_shorts() {
let app = common();
let shorts = Foo::shorts_and_visible_aliases(&app);
let shorts = Foo.shorts_and_visible_aliases(&app);
assert_eq!(shorts.len(), 2);
assert_eq!(shorts[0], 'h');
assert_eq!(shorts[1], 'V');
let sc_shorts =
Foo::shorts_and_visible_aliases(Foo::find_subcommand_with_path(&app, vec!["test"]));
Foo.shorts_and_visible_aliases(Foo.find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_shorts.len(), 4);
assert_eq!(sc_shorts[0], 'p');
@ -288,14 +289,14 @@ mod tests {
#[test]
fn test_longs() {
let app = common();
let longs = Foo::longs_and_visible_aliases(&app);
let longs = Foo.longs_and_visible_aliases(&app);
assert_eq!(longs.len(), 2);
assert_eq!(longs[0], "help");
assert_eq!(longs[1], "version");
let sc_longs =
Foo::longs_and_visible_aliases(Foo::find_subcommand_with_path(&app, vec!["test"]));
Foo.longs_and_visible_aliases(Foo.find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_longs.len(), 4);
assert_eq!(sc_longs[0], "path");

View file

@ -6,14 +6,15 @@ use crate::Generator;
use clap::*;
/// Generate bash completion file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Bash;
impl Generator for Bash {
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
format!("{}.bash", name)
}
fn generate(app: &App, buf: &mut dyn Write) {
fn generate(&self, app: &App, buf: &mut dyn Write) {
let bin_name = app.get_bin_name().unwrap();
w!(
@ -77,7 +78,8 @@ fn all_subcommands(app: &App) -> String {
debug!("all_subcommands");
let mut subcmds = String::new();
let mut scs = Bash::all_subcommands(app)
let mut scs = Bash
.all_subcommands(app)
.iter()
.map(|x| x.0.clone())
.collect::<Vec<_>>();
@ -104,7 +106,8 @@ fn subcommand_details(app: &App) -> String {
debug!("subcommand_details");
let mut subcmd_dets = String::new();
let mut scs = Bash::all_subcommands(app)
let mut scs = Bash
.all_subcommands(app)
.iter()
.map(|x| x.1.replace(" ", "__"))
.collect::<Vec<_>>();
@ -143,7 +146,7 @@ fn subcommand_details(app: &App) -> String {
fn option_details_for_path(app: &App, path: &str) -> String {
debug!("option_details_for_path: path={}", path);
let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
let p = Bash.find_subcommand_with_path(app, path.split("__").skip(1).collect());
let mut opts = String::new();
for o in p.get_opts() {
@ -200,15 +203,17 @@ fn vals_for(o: &Arg) -> String {
fn all_options_for_path(app: &App, path: &str) -> String {
debug!("all_options_for_path: path={}", path);
let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
let scs: Vec<_> = Bash::subcommands(p).iter().map(|x| x.0.clone()).collect();
let p = Bash.find_subcommand_with_path(app, path.split("__").skip(1).collect());
let scs: Vec<_> = Bash.subcommands(p).iter().map(|x| x.0.clone()).collect();
let opts = format!(
"{shorts} {longs} {pos} {subcmds}",
shorts = Bash::shorts_and_visible_aliases(p)
shorts = Bash
.shorts_and_visible_aliases(p)
.iter()
.fold(String::new(), |acc, s| format!("{} -{}", acc, s)),
longs = Bash::longs_and_visible_aliases(p)
longs = Bash
.longs_and_visible_aliases(p)
.iter()
.fold(String::new(), |acc, l| format!("{} --{}", acc, l)),
pos = p

View file

@ -7,14 +7,15 @@ use crate::INTERNAL_ERROR_MSG;
use clap::*;
/// Generate elvish completion file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Elvish;
impl Generator for Elvish {
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
format!("{}.elv", name)
}
fn generate(app: &App, buf: &mut dyn Write) {
fn generate(&self, app: &App, buf: &mut dyn Write) {
let bin_name = app.get_bin_name().unwrap();
let mut names = vec![];
@ -98,7 +99,7 @@ fn generate_inner<'help>(
}
}
for flag in Elvish::flags(p) {
for flag in Elvish.flags(p) {
if let Some(shorts) = flag.get_short_and_visible_aliases() {
let tooltip = get_tooltip(flag.get_about(), shorts[0]);
for short in shorts {

View file

@ -8,14 +8,15 @@ use clap::*;
/// Generate fish completion file
///
/// Note: The fish generator currently only supports named options (-o/--option), not positional arguments.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Fish;
impl Generator for Fish {
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
format!("{}.fish", name)
}
fn generate(app: &App, buf: &mut dyn Write) {
fn generate(&self, app: &App, buf: &mut dyn Write) {
let command = app.get_bin_name().unwrap();
let mut buffer = String::new();
@ -95,7 +96,7 @@ fn gen_fish_inner(root_command: &str, parent_commands: &[&str], app: &App, buffe
buffer.push('\n');
}
for flag in Fish::flags(app) {
for flag in Fish.flags(app) {
let mut template = basic_template.clone();
if let Some(shorts) = flag.get_short_and_visible_aliases() {

View file

@ -7,14 +7,15 @@ use crate::INTERNAL_ERROR_MSG;
use clap::*;
/// Generate powershell completion file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct PowerShell;
impl Generator for PowerShell {
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
format!("_{}.ps1", name)
}
fn generate(app: &App, buf: &mut dyn Write) {
fn generate(&self, app: &App, buf: &mut dyn Write) {
let bin_name = app.get_bin_name().unwrap();
let mut names = vec![];
@ -114,7 +115,7 @@ fn generate_inner<'help>(
}
}
for flag in PowerShell::flags(p) {
for flag in PowerShell.flags(p) {
if let Some(shorts) = flag.get_short_and_visible_aliases() {
let tooltip = get_tooltip(flag.get_about(), shorts[0]);
for short in shorts {

View file

@ -7,14 +7,15 @@ use crate::INTERNAL_ERROR_MSG;
use clap::*;
/// Generate zsh completion file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Zsh;
impl Generator for Zsh {
fn file_name(name: &str) -> String {
fn file_name(&self, name: &str) -> String {
format!("_{}", name)
}
fn generate(app: &App, buf: &mut dyn Write) {
fn generate(&self, app: &App, buf: &mut dyn Write) {
w!(
buf,
format!(
@ -101,7 +102,7 @@ _{bin_name_underscore}_commands() {{
ret.push(parent_text);
// Next we start looping through all the children, grandchildren, etc.
let mut all_subcommands = Zsh::all_subcommands(p);
let mut all_subcommands = Zsh.all_subcommands(p);
all_subcommands.sort();
all_subcommands.dedup();
@ -217,7 +218,7 @@ fn get_subcommands_of(parent: &App) -> String {
return String::new();
}
let subcommand_names = Zsh::subcommands(parent);
let subcommand_names = Zsh.subcommands(parent);
let mut all_subcommands = vec![];
for &(ref name, ref bin_name) in &subcommand_names {
@ -524,7 +525,7 @@ fn write_flags_of(p: &App, p_global: Option<&App>) -> String {
let mut ret = vec![];
for f in Zsh::flags(p) {
for f in Zsh.flags(p) {
debug!("write_flags_of:iter: f={}", f.get_name());
let help = f.get_about().map_or(String::new(), escape_help);

View file

@ -9,6 +9,44 @@
//!
//! - For generating at compile-time, see [`generate_to`]
//! - For generating at runtime, see [`generate`]
//!
//! [`Shell`] is a convenience `enum` for an argument value type that implements `Generator`
//! for each natively-supported shell type.
//!
//! # Example
//!
//! ```rust,no_run
//! use clap::{App, AppSettings, Arg, ValueHint};
//! use clap_generate::{generate, Generator, Shell};
//! use std::io;
//!
//! fn build_cli() -> App<'static> {
//! App::new("example")
//! .arg(Arg::new("file")
//! .about("some input file")
//! .value_hint(ValueHint::AnyPath),
//! )
//! .arg(
//! Arg::new("generator")
//! .long("generate")
//! .possible_values(Shell::arg_values()),
//! )
//! }
//!
//! fn print_completions<G: Generator>(gen: G, app: &mut App) {
//! generate(gen, app, app.get_name().to_string(), &mut io::stdout());
//! }
//!
//! fn main() {
//! let matches = build_cli().get_matches();
//!
//! if let Ok(generator) = matches.value_of_t::<Shell>("generator") {
//! let mut app = build_cli();
//! eprintln!("Generating completion file for {}...", generator);
//! print_completions(generator, &mut app);
//! }
//! }
//! ```
#![doc(html_logo_url = "https://clap.rs/images/media/clap.png")]
#![doc(html_root_url = "https://docs.rs/clap_generate/3.0.0-beta.4")]
@ -118,7 +156,8 @@ pub use shell::Shell;
/// };
///
/// let mut app = build_cli();
/// let path = generate_to::<Bash, _, _>(
/// let path = generate_to(
/// Bash,
/// &mut app, // We need to specify what generator to use
/// "myapp", // We need to specify the bin name manually
/// outdir, // We need to specify where to write to
@ -136,7 +175,12 @@ pub use shell::Shell;
///
/// **NOTE:** Please look at the individual [generators]
/// to see the name of the files generated.
pub fn generate_to<G, S, T>(app: &mut clap::App, bin_name: S, out_dir: T) -> Result<PathBuf, Error>
pub fn generate_to<G, S, T>(
gen: G,
app: &mut clap::App,
bin_name: S,
out_dir: T,
) -> Result<PathBuf, Error>
where
G: Generator,
S: Into<String>,
@ -145,12 +189,12 @@ where
app.set_bin_name(bin_name);
let out_dir = PathBuf::from(out_dir.into());
let file_name = G::file_name(app.get_bin_name().unwrap());
let file_name = gen.file_name(app.get_bin_name().unwrap());
let path = out_dir.join(file_name);
let mut file = File::create(&path)?;
_generate::<G, S>(app, &mut file);
_generate::<G, S>(gen, app, &mut file);
Ok(path)
}
@ -176,7 +220,7 @@ where
/// let matches = cli::build_cli().get_matches();
///
/// if matches.is_present("generate-bash-completions") {
/// generate::<Bash, _>(&mut cli::build_cli(), "myapp", &mut io::stdout());
/// generate(Bash, &mut cli::build_cli(), "myapp", &mut io::stdout());
/// }
///
/// // normal logic continues...
@ -189,16 +233,16 @@ where
/// ```shell
/// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash
/// ```
pub fn generate<G, S>(app: &mut clap::App, bin_name: S, buf: &mut dyn Write)
pub fn generate<G, S>(gen: G, app: &mut clap::App, bin_name: S, buf: &mut dyn Write)
where
G: Generator,
S: Into<String>,
{
app.set_bin_name(bin_name);
_generate::<G, S>(app, buf)
_generate::<G, S>(gen, app, buf)
}
fn _generate<G, S>(app: &mut clap::App, buf: &mut dyn Write)
fn _generate<G, S>(gen: G, app: &mut clap::App, buf: &mut dyn Write)
where
G: Generator,
S: Into<String>,
@ -207,5 +251,5 @@ where
app._build();
app._build_bin_names();
G::generate(app, buf)
gen.generate(app, buf)
}

View file

@ -1,6 +1,8 @@
use std::fmt::Display;
use std::str::FromStr;
use clap::{ArgEnum, ArgValue};
/// Shell with auto-generated completion script available.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
@ -18,21 +20,20 @@ pub enum Shell {
}
impl Shell {
/// A list of supported shells in `[&'static str]` form.
pub fn variants() -> [&'static str; 5] {
["bash", "elvish", "fish", "powershell", "zsh"]
/// Report all `possible_values`
pub fn arg_values() -> impl Iterator<Item = ArgValue<'static>> {
Shell::value_variants()
.iter()
.filter_map(ArgEnum::to_arg_value)
}
}
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Shell::Bash => write!(f, "bash"),
Shell::Elvish => write!(f, "elvish"),
Shell::Fish => write!(f, "fish"),
Shell::PowerShell => write!(f, "powershell"),
Shell::Zsh => write!(f, "zsh"),
}
self.to_arg_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
@ -40,15 +41,57 @@ impl FromStr for Shell {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"bash" => Ok(Shell::Bash),
"elvish" => Ok(Shell::Elvish),
"fish" => Ok(Shell::Fish),
"powershell" => Ok(Shell::PowerShell),
"zsh" => Ok(Shell::Zsh),
_ => Err(String::from(
"[valid values: bash, elvish, fish, powershell, zsh]",
)),
for variant in Self::value_variants() {
if variant.to_arg_value().unwrap().matches(s, false) {
return Ok(*variant);
}
}
Err(format!("Invalid variant: {}", s))
}
}
// Hand-rolled so it can work even when `derive` feature is disabled
impl ArgEnum for Shell {
fn value_variants<'a>() -> &'a [Self] {
&[
Shell::Bash,
Shell::Elvish,
Shell::Fish,
Shell::PowerShell,
Shell::Zsh,
]
}
fn to_arg_value<'a>(&self) -> Option<ArgValue<'a>> {
let value = match self {
Shell::Bash => ArgValue::new("bash"),
Shell::Elvish => ArgValue::new("elvish"),
Shell::Fish => ArgValue::new("fish"),
Shell::PowerShell => ArgValue::new("powershell"),
Shell::Zsh => ArgValue::new("zsh"),
};
Some(value)
}
}
impl crate::Generator for Shell {
fn file_name(&self, name: &str) -> String {
match self {
Shell::Bash => crate::generators::Bash.file_name(name),
Shell::Elvish => crate::generators::Elvish.file_name(name),
Shell::Fish => crate::generators::Fish.file_name(name),
Shell::PowerShell => crate::generators::PowerShell.file_name(name),
Shell::Zsh => crate::generators::Zsh.file_name(name),
}
}
fn generate(&self, app: &clap::App, buf: &mut dyn std::io::Write) {
match self {
Shell::Bash => crate::generators::Bash.generate(app, buf),
Shell::Elvish => crate::generators::Elvish.generate(app, buf),
Shell::Fish => crate::generators::Fish.generate(app, buf),
Shell::PowerShell => crate::generators::PowerShell.generate(app, buf),
Shell::Zsh => crate::generators::Zsh.generate(app, buf),
}
}
}

View file

@ -26,7 +26,7 @@ fn build_app_with_name(s: &'static str) -> App<'static> {
#[test]
fn bash() {
let mut app = build_app();
common::<Bash>(&mut app, "myapp", BASH);
common(Bash, &mut app, "myapp", BASH);
}
static BASH: &str = r#"_myapp() {
@ -43,7 +43,7 @@ static BASH: &str = r#"_myapp() {
myapp)
cmd="myapp"
;;
help)
cmd+="__help"
;;
@ -63,7 +63,7 @@ static BASH: &str = r#"_myapp() {
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
@ -71,7 +71,7 @@ static BASH: &str = r#"_myapp() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
myapp__help)
opts=" -h -V --help --version "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@ -79,7 +79,7 @@ static BASH: &str = r#"_myapp() {
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
@ -94,7 +94,7 @@ static BASH: &str = r#"_myapp() {
return 0
fi
case "${prev}" in
--case)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -115,7 +115,7 @@ complete -F _myapp -o bashdefault -o default myapp
#[test]
fn bash_with_special_commands() {
let mut app = build_app_special_commands();
common::<Bash>(&mut app, "my_app", BASH_SPECIAL_CMDS);
common(Bash, &mut app, "my_app", BASH_SPECIAL_CMDS);
}
fn build_app_special_commands() -> App<'static> {
@ -145,7 +145,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
my_app)
cmd="my_app"
;;
help)
cmd+="__help"
;;
@ -171,7 +171,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
@ -179,7 +179,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
my_app__help)
opts=" -h -V --help --version "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@ -187,7 +187,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
@ -202,7 +202,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
@ -217,7 +217,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
return 0
fi
case "${prev}" in
--config)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -236,7 +236,7 @@ static BASH_SPECIAL_CMDS: &str = r#"_my_app() {
return 0
fi
case "${prev}" in
--case)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -257,7 +257,7 @@ complete -F _my_app -o bashdefault -o default my_app
#[test]
fn bash_with_aliases() {
let mut app = build_app_with_aliases();
common::<Bash>(&mut app, "cmd", BASH_ALIASES);
common(Bash, &mut app, "cmd", BASH_ALIASES);
}
fn build_app_with_aliases() -> App<'static> {
@ -298,7 +298,7 @@ static BASH_ALIASES: &str = r#"_cmd() {
cmd)
cmd="cmd"
;;
*)
;;
esac
@ -312,7 +312,7 @@ static BASH_ALIASES: &str = r#"_cmd() {
return 0
fi
case "${prev}" in
--option)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -336,7 +336,7 @@ static BASH_ALIASES: &str = r#"_cmd() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}

View file

@ -26,7 +26,7 @@ fn build_app_with_name(s: &'static str) -> App<'static> {
#[test]
fn elvish() {
let mut app = build_app();
common::<Elvish>(&mut app, "my_app", ELVISH);
common(Elvish, &mut app, "my_app", ELVISH);
}
static ELVISH: &str = r#"
@ -77,7 +77,7 @@ set edit:completion:arg-completer[my_app] = [@words]{
#[test]
fn elvish_with_special_commands() {
let mut app = build_app_special_commands();
common::<Elvish>(&mut app, "my_app", ELVISH_SPECIAL_CMDS);
common(Elvish, &mut app, "my_app", ELVISH_SPECIAL_CMDS);
}
fn build_app_special_commands() -> App<'static> {
@ -156,7 +156,7 @@ set edit:completion:arg-completer[my_app] = [@words]{
#[test]
fn elvish_with_aliases() {
let mut app = build_app_with_aliases();
common::<Elvish>(&mut app, "cmd", ELVISH_ALIASES);
common(Elvish, &mut app, "cmd", ELVISH_ALIASES);
}
fn build_app_with_aliases() -> App<'static> {

View file

@ -26,7 +26,7 @@ fn build_app_with_name(s: &'static str) -> App<'static> {
#[test]
fn fish() {
let mut app = build_app();
common::<Fish>(&mut app, "myapp", FISH);
common(Fish, &mut app, "myapp", FISH);
}
static FISH: &str = r#"complete -c myapp -n "__fish_use_subcommand" -s h -l help -d 'Print help information'
@ -43,7 +43,7 @@ complete -c myapp -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prin
#[test]
fn fish_with_special_commands() {
let mut app = build_app_special_commands();
common::<Fish>(&mut app, "my_app", FISH_SPECIAL_CMDS);
common(Fish, &mut app, "my_app", FISH_SPECIAL_CMDS);
}
fn build_app_special_commands() -> App<'static> {
@ -80,7 +80,7 @@ complete -c my_app -n "__fish_seen_subcommand_from help" -s V -l version -d 'Pri
#[test]
fn fish_with_special_help() {
let mut app = build_app_special_help();
common::<Fish>(&mut app, "my_app", FISH_SPECIAL_HELP);
common(Fish, &mut app, "my_app", FISH_SPECIAL_HELP);
}
fn build_app_special_help() -> App<'static> {
@ -127,7 +127,7 @@ complete -c my_app -l expansions -d 'Execute the shell command with $SHELL'
#[test]
fn fish_with_aliases() {
let mut app = build_app_with_aliases();
common::<Fish>(&mut app, "cmd", FISH_ALIASES);
common(Fish, &mut app, "cmd", FISH_ALIASES);
}
fn build_app_with_aliases() -> App<'static> {
@ -163,7 +163,7 @@ complete -c cmd -s f -s F -l flag -l flg -d 'cmd flag'
#[test]
fn fish_with_sub_subcommands() {
let mut app = build_app_sub_subcommands();
common::<Fish>(&mut app, "my_app", FISH_SUB_SUBCMDS);
common(Fish, &mut app, "my_app", FISH_SUB_SUBCMDS);
}
fn build_app_sub_subcommands() -> App<'static> {

View file

@ -23,10 +23,16 @@ macro_rules! assert_eq {
};
}
pub fn common<G: Generator>(app: &mut App, name: &str, fixture: &str) {
pub fn common<G: Generator>(gen: G, app: &mut App, name: &str, fixture: &str) {
let mut buf = vec![];
generate::<G, _>(app, name, &mut buf);
generate(gen, app, name, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert_eq!(&string, fixture);
assert_eq!(&normalize(&string), &normalize(fixture));
}
fn normalize(string: &str) -> String {
use itertools::Itertools;
string.lines().map(|s| s.trim_end()).join("\n")
}

View file

@ -34,7 +34,7 @@ fn build_app_with_name(s: &'static str) -> App<'static> {
#[test]
fn powershell() {
let mut app = build_app();
common::<PowerShell>(&mut app, "my_app", POWERSHELL);
common(PowerShell, &mut app, "my_app", POWERSHELL);
}
static POWERSHELL: &str = r#"
@ -96,7 +96,7 @@ Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
#[test]
fn powershell_with_special_commands() {
let mut app = build_app_special_commands();
common::<PowerShell>(&mut app, "my_app", POWERSHELL_SPECIAL_CMDS);
common(PowerShell, &mut app, "my_app", POWERSHELL_SPECIAL_CMDS);
}
fn build_app_special_commands() -> App<'static> {
@ -188,7 +188,7 @@ Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
#[test]
fn powershell_with_aliases() {
let mut app = build_app_with_aliases();
common::<PowerShell>(&mut app, "cmd", POWERSHELL_ALIASES);
common(PowerShell, &mut app, "cmd", POWERSHELL_ALIASES);
}
fn build_app_with_aliases() -> App<'static> {

View file

@ -26,7 +26,7 @@ fn build_app_with_name(s: &'static str) -> App<'static> {
#[test]
fn zsh() {
let mut app = build_app();
common::<Zsh>(&mut app, "myapp", ZSH);
common(Zsh, &mut app, "myapp", ZSH);
}
static ZSH: &str = r#"#compdef myapp
@ -106,7 +106,7 @@ _myapp "$@""#;
#[test]
fn zsh_with_special_commands() {
let mut app = build_app_special_commands();
common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_CMDS);
common(Zsh, &mut app, "my_app", ZSH_SPECIAL_CMDS);
}
fn build_app_special_commands() -> App<'static> {
@ -246,7 +246,7 @@ _my_app "$@""#;
#[test]
fn zsh_with_special_help() {
let mut app = build_app_special_help();
common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_HELP);
common(Zsh, &mut app, "my_app", ZSH_SPECIAL_HELP);
}
fn build_app_special_help() -> App<'static> {
@ -308,7 +308,7 @@ _my_app() {
'--brackets[List packages \[filter\]]' \
'--expansions[Execute the shell command with $SHELL]' \
&& ret=0
}
(( $+functions[_my_app_commands] )) ||
@ -322,7 +322,7 @@ _my_app "$@""#;
#[test]
fn zsh_with_nested_subcommands() {
let mut app = build_app_nested_subcommands();
common::<Zsh>(&mut app, "my_app", ZSH_NESTED_SUBCOMMANDS);
common(Zsh, &mut app, "my_app", ZSH_NESTED_SUBCOMMANDS);
}
fn build_app_nested_subcommands() -> App<'static> {
@ -430,7 +430,7 @@ _my_app "$@""#;
#[test]
fn zsh_with_aliases() {
let mut app = build_app_with_aliases();
common::<Zsh>(&mut app, "cmd", ZSH_ALIASES);
common(Zsh, &mut app, "cmd", ZSH_ALIASES);
}
fn build_app_with_aliases() -> App<'static> {
@ -488,7 +488,7 @@ _cmd() {
'--flg[cmd flag]' \
'::positional:' \
&& ret=0
}
(( $+functions[_cmd_commands] )) ||

View file

@ -18,9 +18,9 @@ fn generate_completions() {
.arg(Arg::new("debug").short('d')),
);
generate::<Bash, _>(&mut app, "test_app", &mut io::sink());
generate::<Fish, _>(&mut app, "test_app", &mut io::sink());
generate::<PowerShell, _>(&mut app, "test_app", &mut io::sink());
generate::<Elvish, _>(&mut app, "test_app", &mut io::sink());
generate::<Zsh, _>(&mut app, "test_app", &mut io::sink());
generate(Bash, &mut app, "test_app", &mut io::sink());
generate(Fish, &mut app, "test_app", &mut io::sink());
generate(PowerShell, &mut app, "test_app", &mut io::sink());
generate(Elvish, &mut app, "test_app", &mut io::sink());
generate(Zsh, &mut app, "test_app", &mut io::sink());
}

View file

@ -119,7 +119,7 @@ _my_app() {
'--help[Print help information]' \
'*::command_with_args:_cmdambivalent' \
&& ret=0
}
(( $+functions[_my_app_commands] )) ||
@ -149,11 +149,11 @@ complete -c my_app -l help -d 'Print help information'
#[test]
fn zsh_with_value_hints() {
let mut app = build_app_with_value_hints();
common::<Zsh>(&mut app, "my_app", ZSH_VALUE_HINTS);
common(Zsh, &mut app, "my_app", ZSH_VALUE_HINTS);
}
#[test]
fn fish_with_value_hints() {
let mut app = build_app_with_value_hints();
common::<Fish>(&mut app, "my_app", FISH_VALUE_HINTS);
common(Fish, &mut app, "my_app", FISH_VALUE_HINTS);
}

View file

@ -25,7 +25,8 @@ use std::ffi::OsString;
/// throughout the application representing the normalized values coming from
/// the CLI.
///
/// ```rust
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```")]
/// /// My super CLI
/// #[derive(clap::Parser)]
/// #[clap(name = "demo")]
@ -150,7 +151,8 @@ pub trait FromArgMatches: Sized {
/// Motivation: If our application had two CLI options, `--name
/// <STRING>` and the flag `--debug`, we may create a struct as follows:
///
/// ```no_run
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```no_run")]
/// struct Context {
/// name: String,
/// debug: bool
@ -160,7 +162,8 @@ pub trait FromArgMatches: Sized {
/// We then need to convert the `ArgMatches` that `clap` generated into our struct.
/// `from_arg_matches` serves as the equivalent of:
///
/// ```no_run
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```no_run")]
/// # use clap::ArgMatches;
/// # struct Context {
/// # name: String,
@ -192,7 +195,8 @@ pub trait FromArgMatches: Sized {
///
/// # Example
///
/// ```rust
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```")]
/// #[derive(clap::Parser)]
/// struct Args {
/// #[clap(flatten)]
@ -229,7 +233,8 @@ pub trait Args: FromArgMatches + Sized {
///
/// # Example
///
/// ```rust
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```")]
/// #[derive(clap::Parser)]
/// struct Args {
/// #[clap(subcommand)]
@ -265,7 +270,8 @@ pub trait Subcommand: FromArgMatches + Sized {
///
/// # Example
///
/// ```rust
#[cfg_attr(not(feature = "derive"), doc = " ```ignore")]
#[cfg_attr(feature = "derive", doc = " ```")]
/// #[derive(clap::Parser)]
/// struct Args {
/// #[clap(arg_enum)]

View file

@ -31,7 +31,6 @@ pub use crate::{
parse::{ArgMatches, Indices, OsValues, Values},
};
#[cfg(feature = "derive")]
pub use crate::derive::{ArgEnum, Args, FromArgMatches, IntoApp, Parser, Subcommand};
#[cfg(feature = "yaml")]
@ -50,7 +49,6 @@ pub use lazy_static;
#[allow(missing_docs)]
mod macros;
#[cfg(feature = "derive")]
mod derive;
#[cfg(feature = "regex")]