Added helper methods to generator

This commit is contained in:
Pavan Kumar Sunkara 2020-02-06 11:19:03 +01:00
parent 33f47acc67
commit e6f77a8713
9 changed files with 293 additions and 173 deletions

View file

@ -0,0 +1,11 @@
use clap::App;
use clap_generate::{generate, generators::Bash};
use std::io;
fn main() {
let mut app = App::new("myapp")
.subcommand(App::new("test").subcommand(App::new("config")))
.subcommand(App::new("hello"));
generate::<Bash, _>(&mut app, "myapp", &mut io::stdout());
}

View file

@ -5,7 +5,6 @@ use std::io::Write;
// Internal
use clap::*;
pub use shells::*;
/// Generator trait which can be used to write generators
@ -55,39 +54,12 @@ pub trait Generator {
/// ```
fn generate(app: &App, buf: &mut dyn Write);
/// Gets all subcommands including child subcommands in the form of 'name' where the name
/// is a single word (i.e. "install") of the path to said subcommand (i.e.
/// "rustup toolchain install")
/// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`.
///
/// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
/// aliasing.
fn all_subcommand_names(app: &App) -> Vec<String> {
debugln!("all_subcommand_names;");
let mut subcmds: Vec<_> = Self::subcommands_of(app)
.iter()
.map(|&(ref n, _)| n.clone())
.collect();
for sc_v in subcommands!(app).map(|s| Self::all_subcommand_names(&s)) {
subcmds.extend(sc_v);
}
subcmds.sort();
subcmds.dedup();
subcmds
}
/// Gets all subcommands including child subcommands in the form of ('name', 'bin_name') where the name
/// is a single word (i.e. "install") of the path and full bin_name of said subcommand (i.e.
/// "rustup toolchain install")
///
/// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
/// aliasing.
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
fn all_subcommands(app: &App) -> Vec<(String, String)> {
debugln!("all_subcommands;");
let mut subcmds: Vec<_> = Self::subcommands_of(app);
let mut subcmds: Vec<_> = Self::subcommands(app);
for sc_v in subcommands!(app).map(|s| Self::all_subcommands(&s)) {
subcmds.extend(sc_v);
@ -96,120 +68,256 @@ pub trait Generator {
subcmds
}
/// Gets all subcommands exlcuding child subcommands in the form of (name, bin_name) where the name
/// is a single word (i.e. "install") and the bin_name is a space deliniated list of the path to said
/// subcommand (i.e. "rustup toolchain install")
/// Finds the subcommand [`clap::App`][clap] from the given [`clap::App`][clap] with the given path.
///
/// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
/// aliasing.
fn subcommands_of(p: &App) -> Vec<(String, String)> {
debugln!(
"subcommands_of: name={}, bin_name={}",
p.name,
p.bin_name.as_ref().unwrap()
);
debugln!(
"subcommands_of: Has subcommands...{:?}",
p.has_subcommands()
);
/// **NOTE:** `path` should not contain the root `bin_name`.
///
/// [clap]: ../clap/struct.App.html
fn find_subcommand_with_path<'b>(p: &'b App<'b>, path: Vec<&str>) -> &'b App<'b> {
let mut app = p;
for sc in path {
app = find_subcmd!(app, sc).unwrap();
}
app
}
/// Gets subcommands of [`clap::App`](../clap/struct.App.html) in the form of `("name", "bin_name")`.
///
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
fn subcommands(p: &App) -> Vec<(String, String)> {
debugln!("subcommands: name={}", p.name);
debugln!("subcommands: Has subcommands...{:?}", p.has_subcommands());
let mut subcmds = vec![];
if !p.has_subcommands() {
let mut ret = vec![];
debugln!("subcommands_of: Looking for aliases...");
if let Some(ref aliases) = p.aliases {
for &(n, _) in aliases {
debugln!("subcommands_of:iter:iter: Found alias...{}", n);
let mut als_bin_name: Vec<_> =
p.bin_name.as_ref().unwrap().split(' ').collect();
als_bin_name.push(n);
let old = als_bin_name.len() - 2;
als_bin_name.swap_remove(old);
ret.push((n.to_owned(), als_bin_name.join(" ")));
}
}
return ret;
return subcmds;
}
for sc in subcommands!(p) {
for sc in &p.subcommands {
let sc_bin_name = sc.get_bin_name().unwrap();
debugln!(
"subcommands_of:iter: name={}, bin_name={}",
"subcommands:iter: name={}, bin_name={}",
sc.name,
sc.bin_name.as_ref().unwrap()
sc_bin_name
);
debugln!("subcommands_of:iter: Looking for aliases...");
if let Some(ref aliases) = sc.aliases {
for &(n, _) in aliases {
debugln!("subcommands_of:iter:iter: Found alias...{}", n);
let mut als_bin_name: Vec<_> =
p.bin_name.as_ref().unwrap().split(' ').collect();
als_bin_name.push(n);
let old = als_bin_name.len() - 2;
als_bin_name.swap_remove(old);
subcmds.push((n.to_owned(), als_bin_name.join(" ")));
}
}
subcmds.push((sc.name.clone(), sc.get_bin_name().unwrap().to_string()));
subcmds.push((sc.name.clone(), sc_bin_name.to_string()));
}
subcmds
}
/// TODO
fn get_all_subcommand_paths(p: &App, first: bool) -> Vec<String> {
debugln!("get_all_subcommand_paths;");
/// Gets all the short options and flags of a [`clap::App`](../clap/struct.App.html).
/// Includes `h` and `V` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
fn shorts<'b>(p: &'b App<'b>) -> Vec<char> {
debugln!("shorts: name={}", p.name);
let mut subcmds = vec![];
if !p.has_subcommands() {
if !first {
let name = &*p.name;
let path = p.get_bin_name().unwrap().to_string().replace(" ", "__");
let mut ret = vec![path.clone()];
if let Some(ref aliases) = p.aliases {
for &(n, _) in aliases {
ret.push(path.replace(name, n));
}
let mut shorts: Vec<char> = p
.args
.args
.iter()
.filter_map(|a| {
if a.index.is_none() && a.short.is_some() {
Some(a.short.unwrap())
} else {
None
}
})
.collect();
return ret;
}
return vec![];
if shorts.iter().find(|x| **x == 'h').is_none() {
shorts.push('h');
}
for sc in subcommands!(p) {
let name = &*sc.name;
let path = sc.get_bin_name().unwrap().to_string().replace(" ", "__");
if !p.is_set(AppSettings::DisableVersion) && shorts.iter().find(|x| **x == 'V').is_none() {
shorts.push('V');
}
subcmds.push(path.clone());
shorts
}
if let Some(ref aliases) = sc.aliases {
for &(n, _) in aliases {
subcmds.push(path.replace(name, n));
/// Gets all the long options and flags of a [`clap::App`](../clap/struct.App.html).
/// Includes `help` and `version` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
fn longs<'b>(p: &'b App<'b>) -> Vec<String> {
debugln!("longs: name={}", p.name);
let mut longs: Vec<String> = p
.args
.args
.iter()
.filter_map(|a| {
if a.index.is_none() && a.long.is_some() {
Some(a.long.unwrap().to_string())
} else {
None
}
}
})
.collect();
if longs.iter().find(|x| **x == "help").is_none() {
longs.push(String::from("help"));
}
for sc_v in subcommands!(p).map(|s| Self::get_all_subcommand_paths(&s, false)) {
subcmds.extend(sc_v);
if !p.is_set(AppSettings::DisableVersion)
&& longs.iter().find(|x| **x == "version").is_none()
{
longs.push(String::from("version"));
}
subcmds
longs
}
/// Gets all the flags of a [`clap::App`](../clap/struct.App.html).
/// Includes `help` and `version` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
fn flags<'b>(p: &'b App<'b>) -> Vec<Arg> {
debugln!("flags: name={}", p.name);
let mut flags: Vec<_> = flags!(p).cloned().collect();
if flags.iter().find(|x| x.name == "help").is_none() {
flags.push(
Arg::with_name("help")
.short('h')
.long("help")
.help("Prints help information"),
);
}
if !p.is_set(AppSettings::DisableVersion)
&& flags.iter().find(|x| x.name == "version").is_none()
{
flags.push(
Arg::with_name("version")
.short('V')
.long("version")
.help("Prints version information"),
);
}
flags
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
struct Foo;
impl Generator for Foo {
fn generate(_: &App, _: &mut dyn Write) {}
fn file_name(name: &str) -> String {
name.to_string()
}
}
fn common() -> App<'static> {
let mut app = App::new("myapp")
.subcommand(
App::new("test")
.subcommand(App::new("config"))
.arg(Arg::with_name("file").short('f').long("file")),
)
.subcommand(App::new("hello"))
.bin_name("my-app");
app._build();
app._build_bin_names();
app
}
#[test]
fn test_subcommands() {
let app = common();
assert_eq!(
Foo::subcommands(&app),
vec![
("test".to_string(), "my-app test".to_string()),
("hello".to_string(), "my-app hello".to_string()),
("help".to_string(), "my-app help".to_string()),
]
);
}
#[test]
fn test_all_subcommands() {
let app = common();
assert_eq!(
Foo::all_subcommands(&app),
vec![
("test".to_string(), "my-app test".to_string()),
("hello".to_string(), "my-app hello".to_string()),
("help".to_string(), "my-app help".to_string()),
("config".to_string(), "my-app test config".to_string()),
]
);
}
#[test]
fn test_find_subcommand_with_path() {
let app = common();
let sc_app = Foo::find_subcommand_with_path(&app, "test config".split(" ").collect());
assert_eq!(sc_app.name, "config");
}
#[test]
fn test_flags() {
let app = common();
let flags = Foo::flags(&app);
assert_eq!(flags.len(), 2);
assert_eq!(flags[0].long, Some("help"));
assert_eq!(flags[1].long, Some("version"));
let sc_flags = Foo::flags(Foo::find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_flags.len(), 3);
assert_eq!(sc_flags[0].long, Some("file"));
assert_eq!(sc_flags[1].long, Some("help"));
assert_eq!(sc_flags[2].long, Some("version"));
}
#[test]
fn test_shorts() {
let app = common();
let shorts = Foo::shorts(&app);
assert_eq!(shorts.len(), 2);
assert_eq!(shorts[0], 'h');
assert_eq!(shorts[1], 'V');
let sc_shorts = Foo::shorts(Foo::find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_shorts.len(), 3);
assert_eq!(sc_shorts[0], 'f');
assert_eq!(sc_shorts[1], 'h');
assert_eq!(sc_shorts[2], 'V');
}
#[test]
fn test_longs() {
let app = common();
let longs = Foo::longs(&app);
assert_eq!(longs.len(), 2);
assert_eq!(longs[0], "help");
assert_eq!(longs[1], "version");
let sc_longs = Foo::longs(Foo::find_subcommand_with_path(&app, vec!["test"]));
assert_eq!(sc_longs.len(), 3);
assert_eq!(sc_longs[0], "file");
assert_eq!(sc_longs[1], "help");
assert_eq!(sc_longs[2], "version");
}
}

View file

@ -76,7 +76,13 @@ fn all_subcommands(app: &App) -> String {
debugln!("Bash::all_subcommands;");
let mut subcmds = String::new();
let scs = Bash::all_subcommand_names(app);
let mut scs = Bash::all_subcommands(app)
.iter()
.map(|x| x.0.clone())
.collect::<Vec<_>>();
scs.sort();
scs.dedup();
for sc in &scs {
subcmds = format!(
@ -97,10 +103,12 @@ fn subcommand_details(app: &App) -> String {
debugln!("Bash::subcommand_details;");
let mut subcmd_dets = String::new();
let mut scs = Bash::get_all_subcommand_paths(app, true);
let mut scs = Bash::all_subcommands(app)
.iter()
.map(|x| x.1.replace(" ", "__"))
.collect::<Vec<_>>();
scs.sort();
scs.dedup();
for sc in &scs {
subcmd_dets = format!(
@ -123,7 +131,7 @@ fn subcommand_details(app: &App) -> String {
subcmd_dets,
subcmd = sc.replace("-", "__"),
sc_opts = all_options_for_path(app, &*sc),
level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n),
level = sc.split("__").map(|_| 1).sum::<u64>(),
opts_details = option_details_for_path(app, &*sc)
);
}
@ -134,13 +142,7 @@ fn subcommand_details(app: &App) -> String {
fn option_details_for_path(app: &App, path: &str) -> String {
debugln!("Bash::option_details_for_path: path={}", path);
let mut p = app;
for sc in path.split("__").skip(1) {
debugln!("Bash::option_details_for_path:iter: sc={}", sc);
p = &find_subcmd!(p, sc).unwrap();
}
let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
let mut opts = String::new();
for o in opts!(p) {
@ -187,25 +189,19 @@ fn vals_for(o: &Arg) -> String {
fn all_options_for_path(app: &App, path: &str) -> String {
debugln!("Bash::all_options_for_path: path={}", path);
let mut p = app;
for sc in path.split("__").skip(1) {
debugln!("Bash::all_options_for_path:iter: sc={}", sc);
p = &find_subcmd!(p, sc).unwrap();
}
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 = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)),
// Handles aliases too
longs = longs!(p).fold(String::new(), |acc, l| format!(
"{} --{}",
acc,
l.to_str().unwrap()
)),
shorts = Bash::shorts(p)
.iter()
.fold(String::new(), |acc, s| format!("{} -{}", acc, s)),
longs = Bash::longs(p)
.iter()
.fold(String::new(), |acc, l| format!("{} --{}", acc, l)),
pos = positionals!(p).fold(String::new(), |acc, p| format!("{} {}", acc, p)),
// Handles aliases too
subcmds = sc_names!(p).fold(String::new(), |acc, s| format!("{} {}", acc, s))
subcmds = scs.join(" "),
);
opts

View file

@ -93,7 +93,7 @@ fn generate_inner<'b>(
}
}
for flag in flags!(p) {
for flag in Elvish::flags(p) {
if let Some(data) = flag.short {
let tooltip = get_tooltip(flag.help, data);

View file

@ -17,7 +17,7 @@ impl Generator for Fish {
let command = app.get_bin_name().unwrap();
let mut buffer = String::new();
gen_fish_inner(command, app, command, &mut buffer);
gen_fish_inner(command, app, &mut buffer);
w!(buf, buffer.as_bytes());
}
}
@ -27,7 +27,7 @@ fn escape_string(string: &str) -> String {
string.replace("\\", "\\\\").replace("'", "\\'")
}
fn gen_fish_inner(root_command: &str, app: &App, subcommand: &str, buffer: &mut String) {
fn gen_fish_inner(root_command: &str, app: &App, buffer: &mut String) {
debugln!("Fish::gen_fish_inner;");
// example :
//
@ -43,13 +43,17 @@ fn gen_fish_inner(root_command: &str, app: &App, subcommand: &str, buffer: &mut
// -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1"
let mut basic_template = format!("complete -c {} -n ", root_command);
let mut bin_name = app.get_bin_name().unwrap();
if root_command == subcommand {
if root_command == bin_name {
basic_template.push_str("\"__fish_use_subcommand\"");
} else {
basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", subcommand).as_str());
bin_name = &app.name;
basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", bin_name).as_str());
}
debugln!("Fish::gen_fish_inner; bin_name={}", bin_name);
for option in opts!(app) {
let mut template = basic_template.clone();
@ -73,7 +77,7 @@ fn gen_fish_inner(root_command: &str, app: &App, subcommand: &str, buffer: &mut
buffer.push_str("\n");
}
for flag in flags!(app) {
for flag in Fish::flags(app) {
let mut template = basic_template.clone();
if let Some(data) = flag.short {
@ -92,7 +96,7 @@ fn gen_fish_inner(root_command: &str, app: &App, subcommand: &str, buffer: &mut
buffer.push_str("\n");
}
for subcommand in subcommands!(app) {
for subcommand in &app.subcommands {
let mut template = basic_template.clone();
template.push_str(" -f");
@ -107,7 +111,7 @@ fn gen_fish_inner(root_command: &str, app: &App, subcommand: &str, buffer: &mut
}
// generate options of subcommands
for subapp in &app.subcommands {
gen_fish_inner(root_command, subapp, &subapp.name, buffer);
for subcommand in &app.subcommands {
gen_fish_inner(root_command, subcommand, buffer);
}
}

View file

@ -112,7 +112,7 @@ fn generate_inner<'b>(
}
}
for flag in flags!(p) {
for flag in PowerShell::flags(p) {
if let Some(data) = flag.short {
let tooltip = get_tooltip(flag.help, data);

View file

@ -216,7 +216,7 @@ fn get_subcommands_of(p: &App) -> String {
return String::new();
}
let sc_names = Zsh::subcommands_of(p);
let sc_names = Zsh::subcommands(p);
let mut subcmds = vec![];
for &(ref name, ref bin_name) in &sc_names {
@ -262,7 +262,7 @@ fn parser_of<'b>(p: &'b App<'b>, mut sc: &str) -> &'b App<'b> {
return p;
}
sc = sc.split(" ").last().unwrap();
sc = sc.split(' ').last().unwrap();
find_subcmd!(p, sc).expect(INTERNAL_ERROR_MSG)
}
@ -429,7 +429,7 @@ fn write_flags_of(p: &App) -> String {
let mut ret = vec![];
for f in flags!(p) {
for f in Zsh::flags(p) {
debugln!("Zsh::write_flags_of:iter: f={}", f.name);
let help = f.help.map_or(String::new(), escape_help);

View file

@ -15,9 +15,10 @@
unused_allocation,
trivial_numeric_casts
)]
#![allow(clippy::needless_doctest_main)]
const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/clap-rs/clap/issues";
const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/clap-rs/clap/issues";
#[macro_use]
#[allow(missing_docs)]
@ -106,9 +107,9 @@ pub use generators::Generator;
/// };
///
/// let mut app = build_cli();
/// 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
/// 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
/// }
/// ```
///
@ -153,13 +154,13 @@ where
///
/// mod cli;
/// use std::io;
/// use clap_generate::{generate_to, generators::Bash};
/// use clap_generate::{generate, generators::Bash};
///
/// fn main() {
/// 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...

View file

@ -1,20 +1,20 @@
use std::fmt;
use clap::{App, Arg};
use clap_generate::{generate, generators::*};
use std::fmt;
#[derive(PartialEq, Eq)]
pub struct PrettyString<'a>(pub &'a str);
impl<'a> fmt::Debug for PrettyString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0)
}
}
macro_rules! assert_eq {
($left:expr, $right:expr) => {
pretty_assertions::assert_eq!(PrettyString($left), PrettyString($right));
}
};
}
static BASH: &'static str = r#"_myapp() {
@ -45,7 +45,7 @@ static BASH: &'static str = r#"_myapp() {
case "${cmd}" in
myapp)
opts=" -h -V --help --version <file> test help"
opts=" -h -V --help --version <file> test help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
@ -572,7 +572,7 @@ static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
case "${cmd}" in
my_app)
opts=" -h -V --help --version <file> test some_cmd some-cmd-with-hypens help"
opts=" -h -V --help --version <file> test some_cmd some-cmd-with-hypens help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
@ -740,7 +740,7 @@ fn build_app_special_commands() -> App<'static> {
.help("the other case to test"),
),
)
.subcommand(App::new("some-cmd-with-hypens"))
.subcommand(App::new("some-cmd-with-hypens").alias("hyphen"))
}
fn build_app_special_help() -> App<'static> {