From 1321630ef56955f152c73376d4d85cceb0bb4a12 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 31 Mar 2016 21:11:34 -0300 Subject: [PATCH 01/10] imp(src/args): Exposes argument display order by introducing a new Trait This commit introduces a new trait (`DispOrder`) with a single function (`fn disp_order(&self) -> usize`). It is use to expose the display order of an argument in a read-only manner. --- src/args/any_arg.rs | 4 ++++ src/args/arg_builder/flag.rs | 6 +++++- src/args/arg_builder/option.rs | 6 +++++- src/args/arg_builder/positional.rs | 6 +++++- src/args/mod.rs | 2 +- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 18f8827b..a4fc5ec7 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -26,3 +26,7 @@ pub trait AnyArg<'n, 'e> { fn help(&self) -> Option<&'e str>; fn default_val(&self) -> Option<&'n str>; } + +pub trait DispOrder { + fn disp_ord(&self) -> usize; +} diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index dfbebbbc..4f1d201e 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -8,7 +8,7 @@ use std::result::Result as StdResult; use vec_map::VecMap; use Arg; -use args::{AnyArg, HelpWriter}; +use args::{AnyArg, HelpWriter, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[derive(Debug)] @@ -131,6 +131,10 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn default_val(&self) -> Option<&'n str> { None } } +impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.disp_ord } +} + #[cfg(test)] mod test { use super::FlagBuilder; diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 0e2da78d..f150802a 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -5,7 +5,7 @@ use std::io; use vec_map::VecMap; -use args::{AnyArg, Arg, HelpWriter}; +use args::{AnyArg, Arg, HelpWriter, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[allow(missing_debug_implementations)] @@ -195,6 +195,10 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn default_val(&self) -> Option<&'n str> { self.default_val } } +impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.disp_ord } +} + #[cfg(test)] mod test { use super::OptBuilder; diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 590b9340..81fe46ca 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -6,7 +6,7 @@ use std::io; use vec_map::VecMap; use Arg; -use args::{AnyArg, HelpWriter}; +use args::{AnyArg, HelpWriter, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[allow(missing_debug_implementations)] @@ -182,6 +182,10 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn default_val(&self) -> Option<&'n str> { self.default_val } } +impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.disp_ord } +} + #[cfg(test)] mod test { use super::PosBuilder; diff --git a/src/args/mod.rs b/src/args/mod.rs index 365eaf91..38cffd8d 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -5,7 +5,7 @@ pub use self::subcommand::SubCommand; pub use self::arg_builder::{FlagBuilder, OptBuilder, PosBuilder}; pub use self::matched_arg::MatchedArg; pub use self::group::ArgGroup; -pub use self::any_arg::AnyArg; +pub use self::any_arg::{AnyArg, DispOrder}; pub use self::settings::ArgSettings; pub use self::help_writer::HelpWriter; From 9b23e7ee40e51f7a823644c4496be955dc6c9d3a Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 31 Mar 2016 21:22:59 -0300 Subject: [PATCH 02/10] imp(parser.rs): Expose Parser's flags, opts and positionals argument as iterators Writing the help requires read only access to Parser's flags, opts and positionals. This commit provides 3 functions returning iterators over those collections (`iter_*`) allowing to have function outside the parser to generate the help. --- src/app/parser.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 3812c203..f025db30 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -6,7 +6,7 @@ use std::fmt::Display; #[cfg(feature = "debug")] use std::os::unix::ffi::OsStrExt; -use vec_map::VecMap; +use vec_map::{self, VecMap}; use app::App; use args::{Arg, FlagBuilder, OptBuilder, ArgGroup, PosBuilder}; @@ -1570,6 +1570,18 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { } Ok(()) } + + pub fn iter_flags(&self) -> Iter { + self.flags.iter() + } + + pub fn iter_opts(&self) -> Iter { + self.opts.iter() + } + + pub fn iter_positionals(&self) -> vec_map::Values { + self.positionals.values() + } } impl<'a, 'b> Clone for Parser<'a, 'b> where 'a: 'b { From d51945f8b82ebb0963f4f40b384a9e8335783091 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 31 Mar 2016 21:33:28 -0300 Subject: [PATCH 03/10] imp(parser.rs): Make Parser's create_usage public allowing to have function outside the parser to generate the help --- src/app/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index f025db30..d41b198a 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1309,7 +1309,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { // Creates a usage string if one was not provided by the user manually. This happens just // after all arguments were parsed, but before any subcommands have been parsed // (so as to give subcommands their own usage recursively) - fn create_usage(&self, used: &[&str]) -> String { + pub fn create_usage(&self, used: &[&str]) -> String { debugln!("fn=create_usage;"); let mut usage = String::with_capacity(75); usage.push_str("USAGE:\n "); From 65b3f667532685f854c699ddd264d326599cf7e5 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Fri, 1 Apr 2016 02:07:44 -0300 Subject: [PATCH 04/10] imp(srs/args): Added longest_filter to AnyArg trait This function allows providing an extra filter to remove elements when finding the longest element. --- src/app/mod.rs | 1 + src/args/any_arg.rs | 1 + src/args/arg_builder/flag.rs | 1 + src/args/arg_builder/option.rs | 1 + src/args/arg_builder/positional.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index 17ff3d5f..04170c8c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -936,6 +936,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.p.meta.about } fn default_val(&self) -> Option<&'n str> { None } + fn longest_filter(&self) -> bool { true } } impl<'n, 'e> fmt::Display for App<'n, 'e> { diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index a4fc5ec7..16f3d001 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -25,6 +25,7 @@ pub trait AnyArg<'n, 'e> { fn val_names(&self) -> Option<&VecMap<&'e str>>; fn help(&self) -> Option<&'e str>; fn default_val(&self) -> Option<&'n str>; + fn longest_filter(&self) -> bool; } pub trait DispOrder { diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 4f1d201e..0471246a 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -129,6 +129,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn val_delim(&self) -> Option { None } fn help(&self) -> Option<&'e str> { self.help } fn default_val(&self) -> Option<&'n str> { None } + fn longest_filter(&self) -> bool { self.long.is_some() } } impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> { diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index f150802a..01c05f06 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -193,6 +193,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.help } fn default_val(&self) -> Option<&'n str> { self.default_val } + fn longest_filter(&self) -> bool { true } } impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> { diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 81fe46ca..c08980f2 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -180,6 +180,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.help } fn default_val(&self) -> Option<&'n str> { self.default_val } + fn longest_filter(&self) -> bool { true } } impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> { From 9d757e8678e334e5a740ac750c76a9ed4e785cba Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 3 Apr 2016 01:06:37 -0300 Subject: [PATCH 05/10] imp(macros.rs): Added write_nspaces macro (a new version of write_spaces) `write_nspaces` has three differences with `write_spaces` 1. Accepts arguments with attribute access (such as self.writer) 2. The order of the arguments is swapped to make the writer the first argument as in other write related macros. 3. Does not use the `write!` macro under the hood but rather calls directly `write` I have chosen to put the function under a new name to avoid backwards compatibility problem but it might be better to migrate everything to `write_nspaces` (and maybe rename it `write_spaces`) --- src/macros.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index f7d50505..9abde7bd 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -586,6 +586,19 @@ macro_rules! write_spaces { }) } +// Helper/deduplication macro for printing the correct number of spaces in help messages +// used in: +// src/args/arg_builder/*.rs +// src/app/mod.rs +macro_rules! write_nspaces { + ($dst:expr, $num:expr) => ({ + debugln!("macro=write_spaces!;"); + for _ in 0..$num { + try!($dst.write(b" ")); + } + }) +} + // convenience macro for remove an item from a vec macro_rules! vec_remove { ($vec:expr, $to_rem:expr) => { From a91d378ba0c91b5796457f8c6e881b13226ab735 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 3 Apr 2016 01:17:22 -0300 Subject: [PATCH 06/10] imp(parser.rs): Provide a way to create a usage string without the USAGE: title --- src/app/parser.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/app/parser.rs b/src/app/parser.rs index d41b198a..23f06017 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1313,6 +1313,16 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { debugln!("fn=create_usage;"); let mut usage = String::with_capacity(75); usage.push_str("USAGE:\n "); + usage.push_str(&self.create_usage_no_title(&used)); + usage + } + + // Creates a usage string (*without title*) if one was not provided by the user + // manually. This happens just + // after all arguments were parsed, but before any subcommands have been parsed + // (so as to give subcommands their own usage recursively) + pub fn create_usage_no_title(&self, used: &[&str]) -> String { + let mut usage = String::with_capacity(75); if let Some(u) = self.meta.usage_str { usage.push_str(&*u); } else if used.is_empty() { From 04b5b074d1f16a8e62a5ca59deaf0506b47ba88b Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Fri, 1 Apr 2016 03:00:11 -0300 Subject: [PATCH 07/10] refactor(HELP): A new Help Engine The largest organizational change is that methods used to generate the help are implemented by the Help object and not the App, FlagBuilder, Parser, etc. The new code is based heavily on the old one with a few minor modifications aimed to reduce code duplication and coupling between the Help and the rest of the code. The new code turn things around: instead of having a HelpWriter that holds an AnyArg object and a method that is called with a writer as argument, there is a Help Object that holds a writer and a method that is called with a writer as an argument. There are still things to do such as moving `create_usage` outside the Parser. The peformance has been affected, probably by the use of Trait Objects. This was done as a way to reduce code duplication (i.e. in the unified help code). This performance hit should not affect the usability as generating and printing the help is dominated by user interaction and IO. The old code to generate the help is still functional and is the active one. The new code has been tested against the old one by generating help strings for most of the examples in the repo. --- benches/04_new_help.rs | 343 ++++++++++++++++++++++++++++ src/app/help.rs | 506 +++++++++++++++++++++++++++++++++++++++++ src/app/mod.rs | 17 ++ tests/new_help.rs | 265 +++++++++++++++++++++ 4 files changed, 1131 insertions(+) create mode 100644 benches/04_new_help.rs create mode 100644 src/app/help.rs create mode 100644 tests/new_help.rs diff --git a/benches/04_new_help.rs b/benches/04_new_help.rs new file mode 100644 index 00000000..f1390d8f --- /dev/null +++ b/benches/04_new_help.rs @@ -0,0 +1,343 @@ +#![feature(test)] + +extern crate clap; +extern crate test; + +use test::Bencher; + +use std::io::Cursor; + +use clap::App; +use clap::{Arg, SubCommand}; + +fn build_old_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn build_new_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_new_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn example1<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' + 'Sets an optional output file' + -d... 'Turn debugging information on'") + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) +} + +fn example2<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") +} + +fn example3<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // All application settings go here... + + // A simple "Flag" argument example (i.e. "-d") using the builder pattern + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d")) + + // Two arguments, one "Option" argument (i.e. one that takes a value) such + // as "-c some", and one positional argument (i.e. "myapp some_file") + .args(&[ + Arg::with_name("config") + .help("sets the config file to use") + .takes_value(true) + .short("c") + .long("config"), + Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true) + ]) + + // *Note* the following two examples are convienience methods, if you wish + // to still get the full configurability of Arg::with_name() and the readability + // of arg_from_usage(), you can instantiate a new Arg with Arg::from_usage() and + // still be able to set all the additional properties, just like Arg::with_name() + // + // + // One "Flag" using a usage string + .arg_from_usage("--license 'display the license file'") + + // Two args, one "Positional", and one "Option" using a usage string + .args_from_usage("[output] 'Supply an output file to use' + -i, --int=[IFACE] 'Set an interface to use'") +} + +fn example4<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .about("Parses an input file to do awesome things") + .version("1.0") + .author("Kevin K. ") + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d") + .long("debug")) + .arg(Arg::with_name("config") + .help("sets the config file to use") + .short("c") + .long("config")) + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true)) +} + +fn example5<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // We'll add a flag that represents an awesome meter... + // + // I'll explain each possible setting that "flags" accept. Keep in mind + // that you DO NOT need to set each of these for every flag, only the ones + // you want for your individual case. + .arg(Arg::with_name("awesome") + .help("turns up the awesome") // Displayed when showing help info + .short("a") // Trigger this arg with "-a" + .long("awesome") // Trigger this arg with "--awesome" + .multiple(true) // This flag should allow multiple + // occurrences such as "-aaa" or "-a -a" + .requires("config") // Says, "If the user uses -a, they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a mutually_excludes_all(Vec<&str>) + ) +} + +fn example6<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // We'll add two positional arguments, a input file, and a config file. + // + // I'll explain each possible setting that "positionals" accept. Keep in + // mind that you DO NOT need to set each of these for every flag, only the + // ones that apply to your individual case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .index(1) // Set the order in which the user must + // specify this argument (Starts at 1) + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a mutually_excludes_all(Vec<&str>) + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + ) + .arg(Arg::with_name("config") + .help("the config file to use") + .index(2)) // Note, we do not need to specify required(true) + // if we don't want to, because "input" already + // requires "config" + // Note, we also do not need to specify requires("input") + // because requires lists are automatically two-way +} + +fn example7<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // Assume we an application that accepts an input file via the "-i file" + // or the "--input file" (as wel as "--input=file"). + // Below every setting supported by option arguments is discussed. + // NOTE: You DO NOT need to specify each setting, only those which apply + // to your particular case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .takes_value(true) // MUST be set to true in order to be an "option" argument + .short("i") // This argument is triggered with "-i" + .long("input") // This argument is triggered with "--input" + .multiple(true) // Set to true if you wish to allow multiple occurrences + // such as "-i file -i other_file -i third_file" + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) +} + +fn example8<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // Assume we an application that accepts an input file via the "-i file" + // or the "--input file" (as wel as "--input=file"). + // Below every setting supported by option arguments is discussed. + // NOTE: You DO NOT need to specify each setting, only those which apply + // to your particular case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .takes_value(true) // MUST be set to true in order to be an "option" argument + .short("i") // This argument is triggered with "-i" + .long("input") // This argument is triggered with "--input" + .multiple(true) // Set to true if you wish to allow multiple occurrences + // such as "-i file -i other_file -i third_file" + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) +} + +fn example10<'b, 'c>() -> App<'b, 'c> { + App::new("myapp") + .about("does awesome things") + .arg(Arg::with_name("CONFIG") + .help("The config file to use (default is \"config.json\")") + .short("c") + .takes_value(true)) +} + + +#[bench] +fn example1_old(b: &mut Bencher) { + let app = example1(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example1_new(b: &mut Bencher) { + let app = example1(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example2_old(b: &mut Bencher) { + let app = example2(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example2_new(b: &mut Bencher) { + let app = example2(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example3_old(b: &mut Bencher) { + let app = example3(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example3_new(b: &mut Bencher) { + let app = example3(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example4_old(b: &mut Bencher) { + let app = example4(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example4_new(b: &mut Bencher) { + let app = example4(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example5_old(b: &mut Bencher) { + let app = example5(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example5_new(b: &mut Bencher) { + let app = example5(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example6_old(b: &mut Bencher) { + let app = example6(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example6_new(b: &mut Bencher) { + let app = example6(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example7_old(b: &mut Bencher) { + let app = example7(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example7_new(b: &mut Bencher) { + let app = example7(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example8_old(b: &mut Bencher) { + let app = example8(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example8_new(b: &mut Bencher) { + let app = example8(); + b.iter(|| build_new_help(&app)); +} + + +#[bench] +fn example10_old(b: &mut Bencher) { + let app = example10(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn example10_new(b: &mut Bencher) { + let app = example10(); + b.iter(|| build_new_help(&app)); +} diff --git a/src/app/help.rs b/src/app/help.rs new file mode 100644 index 00000000..69680fcb --- /dev/null +++ b/src/app/help.rs @@ -0,0 +1,506 @@ + +use std::io::{self, Write}; +use std::collections::BTreeMap; +use std::fmt::Display; +use std::cmp; +use std::str; + +use vec_map::VecMap; + +use errors::{Error, Result as ClapResult}; + +use args::{AnyArg, ArgSettings, DispOrder}; +use app::{App, AppSettings}; +use app::parser::Parser; + +use term; + +const TAB: &'static str = " "; + +trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} +impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T where T: AnyArg<'b, 'c> + Display {} + +trait ArgWithOrder<'b, 'c>: ArgWithDisplay<'b, 'c> + DispOrder { + fn as_base(&self) -> &ArgWithDisplay<'b, 'c>; +} +impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T + where T: ArgWithDisplay<'b, 'c> + DispOrder +{ + fn as_base(&self) -> &ArgWithDisplay<'b, 'c> { + self + } +} + +impl<'b, 'c> DispOrder for App<'b, 'c> { + fn disp_ord(&self) -> usize { + 999 + } +} + +pub struct Help<'a> { + writer: &'a mut Write, + next_line_help: bool, + hide_pv: bool, + term_w: Option, +} + +impl<'a> Help<'a> { + pub fn new(w: &'a mut Write, next_line_help: bool, hide_pv: bool) -> Self { + Help { + writer: w, + next_line_help: next_line_help, + hide_pv: hide_pv, + term_w: term::dimensions().map(|(w, _)| w), + } + } + + pub fn write_app_help(w: &'a mut Write, app: &App) -> ClapResult<()> { + let ref parser = app.p; + let nlh = parser.is_set(AppSettings::NextLineHelp); + let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); + Self::new(w, nlh, hide_v).write_help(&parser) + } +} + + +fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { + x +} + +// AnyArg +impl<'a> Help<'a> { + fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + where I: Iterator> + { + let mut longest = 0; + let mut arg_v = Vec::with_capacity(10); + for arg in args.filter(|arg| { + !(arg.is_set(ArgSettings::Hidden)) || arg.is_set(ArgSettings::NextLineHelp) + }) { + if arg.longest_filter() { + longest = cmp::max(longest, arg.to_string().len()); + } + if !arg.is_set(ArgSettings::Hidden) { + arg_v.push(arg) + } + } + for arg in arg_v { + try!(self.write_arg(arg.as_base(), longest)); + } + Ok(()) + } + + fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + where I: Iterator> + { + let mut longest = 0; + let mut ord_m = VecMap::new(); + for arg in args.filter(|arg| { + !(arg.is_set(ArgSettings::Hidden)) || arg.is_set(ArgSettings::NextLineHelp) + }) { + if arg.longest_filter() { + longest = cmp::max(longest, arg.to_string().len()); + } + if !arg.is_set(ArgSettings::Hidden) { + let btm = ord_m.entry(arg.disp_ord()).or_insert(BTreeMap::new()); + btm.insert(arg.name(), arg); + } + } + for (_, btm) in ord_m.into_iter() { + for (_, arg) in btm.into_iter() { + try!(self.write_arg(arg.as_base(), longest)); + } + } + Ok(()) + } + + fn write_arg<'b, 'c>(&mut self, + arg: &ArgWithDisplay<'b, 'c>, + longest: usize) + -> io::Result<()> { + debugln!("fn=write_to;"); + try!(self.short(arg)); + try!(self.long(arg, longest)); + try!(self.val(arg, longest)); + try!(self.help(arg, longest)); + try!(self.writer.write(b"\n")); + Ok(()) + } + + fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + debugln!("fn=short;"); + try!(write!(self.writer, "{}", TAB)); + if let Some(s) = arg.short() { + write!(self.writer, "-{}", s) + } else if arg.has_switch() { + write!(self.writer, "{}", TAB) + } else { + Ok(()) + } + } + + fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { + debugln!("fn=long;"); + if !arg.has_switch() { + return Ok(()); + } + if arg.takes_value() { + if let Some(l) = arg.long() { + try!(write!(self.writer, + "{}--{}", + if arg.short().is_some() { + ", " + } else { + "" + }, + l)); + } + try!(write!(self.writer, " ")); + } else { + // write_spaces! fails when using self.writer + let ref mut w = self.writer; + if let Some(l) = arg.long() { + try!(write!(w, + "{}--{}", + if arg.short().is_some() { + ", " + } else { + "" + }, + l)); + if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) { + write_spaces!((longest + 4) - (l.len() + 2), w); + } + } else { + if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) { + // 6 is tab (4) + -- (2) + write_spaces!((longest + 6), w); + } + } + } + Ok(()) + } + + fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { + debugln!("fn=val;"); + if !arg.takes_value() { + return Ok(()); + } + if let Some(ref vec) = arg.val_names() { + let mut it = vec.iter().peekable(); + while let Some((_, val)) = it.next() { + try!(write!(self.writer, "<{}>", val)); + if it.peek().is_some() { + try!(write!(self.writer, " ")); + } + } + let num = vec.len(); + if arg.is_set(ArgSettings::Multiple) && num == 1 { + try!(write!(self.writer, "...")); + } + } else if let Some(num) = arg.num_vals() { + let mut it = (0..num).peekable(); + while let Some(_) = it.next() { + try!(write!(self.writer, "<{}>", arg.name())); + if it.peek().is_some() { + try!(write!(self.writer, " ")); + } + } + } else { + try!(write!(self.writer, "{}", arg)); + } + let ref mut w = self.writer; + if arg.has_switch() { + if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) { + let self_len = arg.to_string().len(); + // subtract ourself + let mut spcs = longest - self_len; + // Since we're writing spaces from the tab point we first need to know if we + // had a long and short, or just short + if arg.long().is_some() { + // Only account 4 after the val + spcs += 4; + } else { + // Only account for ', --' + 4 after the val + spcs += 8; + } + + write_spaces!(spcs, w); + } + } else if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) { + write_spaces!(longest + 4 - (arg.to_string().len()), w); + } + Ok(()) + } + + fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { + debugln!("fn=help;"); + let spec_vals = self.spec_vals(arg); + let mut help = String::new(); + let h = arg.help().unwrap_or(""); + let spcs = if self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) { + 8 // "tab" + "tab" + } else { + longest + 12 + }; + // determine if our help fits or needs to wrap + let width = self.term_w.unwrap_or(0); + debugln!("Term width...{}", width); + let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= width); + debugln!("Too long...{:?}", too_long); + + // Is help on next line, if so newline + 2x tab + if self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) { + try!(write!(self.writer, "\n{}{}", TAB, TAB)); + } + + debug!("Too long..."); + if too_long { + sdebugln!("Yes"); + help.push_str(h); + help.push_str(&*spec_vals); + debugln!("help: {}", help); + debugln!("help len: {}", help.len()); + // Determine how many newlines we need to insert + let avail_chars = width - spcs; + debugln!("Usable space: {}", avail_chars); + let longest_w = { + let mut lw = 0; + for l in help.split(' ').map(|s| s.len()) { + if l > lw { + lw = l; + } + } + lw + }; + debugln!("Longest word...{}", longest_w); + debug!("Enough space..."); + if longest_w < avail_chars { + sdebugln!("Yes"); + let mut indices = vec![]; + let mut idx = 0; + loop { + idx += avail_chars - 1; + if idx >= help.len() { + break; + } + // 'a' arbitrary non space char + if help.chars().nth(idx).unwrap_or('a') != ' ' { + idx = find_idx_of_space(&*help, idx); + } + debugln!("Adding idx: {}", idx); + debugln!("At {}: {:?}", idx, help.chars().nth(idx)); + indices.push(idx); + if &help[idx..].len() <= &avail_chars { + break; + } + } + for (i, idx) in indices.iter().enumerate() { + debugln!("iter;i={},idx={}", i, idx); + let j = idx + (2 * i); + debugln!("removing: {}", j); + debugln!("at {}: {:?}", j, help.chars().nth(j)); + help.remove(j); + help.insert(j, '{'); + help.insert(j + 1, 'n'); + help.insert(j + 2, '}'); + } + } else { + sdebugln!("No"); + } + } else { + sdebugln!("No"); + } + let help = if !help.is_empty() { + &*help + } else if !spec_vals.is_empty() { + help.push_str(h); + help.push_str(&*spec_vals); + &*help + } else { + h + }; + if help.contains("{n}") { + if let Some(part) = help.split("{n}").next() { + try!(write!(self.writer, "{}", part)); + } + let ref mut w = self.writer; + for part in help.split("{n}").skip(1) { + try!(write!(w, "\n")); + if self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) { + try!(write!(w, "{}{}", TAB, TAB)); + } else if arg.has_switch() { + write_spaces!(longest + 12, w); + } else { + write_spaces!(longest + 8, w); + } + try!(write!(w, "{}", part)); + } + } else { + try!(write!(self.writer, "{}", help)); + } + Ok(()) + } + + fn spec_vals(&self, a: &ArgWithDisplay) -> String { + debugln!("fn=spec_vals;"); + if let Some(ref pv) = a.default_val() { + debugln!("Writing defaults"); + return format!(" [default: {}] {}", + pv, + if !self.hide_pv { + if let Some(ref pv) = a.possible_vals() { + format!(" [values: {}]", pv.join(", ")) + } else { + "".into() + } + } else { + "".into() + }); + } else if !self.hide_pv { + debugln!("Writing values"); + if let Some(ref pv) = a.possible_vals() { + debugln!("Possible vals...{:?}", pv); + return format!(" [values: {}]", pv.join(", ")); + } + } + String::new() + } +} + + +// Parser +impl<'a> Help<'a> { + pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { + + let flags = !parser.has_flags(); + let pos = !parser.has_positionals(); + let opts = !parser.has_opts(); + let subcmds = !parser.has_subcommands(); + + let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); + + if unified_help && (flags || opts) { + let opts_flags = parser.iter_flags() + .map(as_arg_trait) + .chain(parser.iter_opts().map(as_arg_trait)); + try!(write!(self.writer, "\nOPTIONS:\n")); + try!(self.write_args(opts_flags)); + } else { + if flags { + try!(write!(self.writer, "\nFLAGS:\n")); + try!(self.write_args(parser.iter_flags() + .map(as_arg_trait))); + } + if opts { + try!(write!(self.writer, "\nOPTIONS:\n")); + try!(self.write_args(parser.iter_opts().map(as_arg_trait))); + } + } + + if pos { + try!(write!(self.writer, "\nARGS:\n")); + try!(self.write_args_unsorted(parser.iter_positionals().map(as_arg_trait))); + } + + if subcmds { + try!(write!(self.writer, "\nSUBCOMMANDS:\n")); + + let mut longest = 0; + + let mut ord_m = VecMap::new(); + for sc in parser.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { + let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); + btm.insert(sc.p.meta.name.clone(), sc); + longest = cmp::max(longest, sc.p.meta.name.len()); + } + + for (_, btm) in ord_m.into_iter() { + for (_, sc) in btm.into_iter() { + try!(self.write_arg(sc, longest)); + } + } + } + // + + + Ok(()) + } + + fn write_version(&mut self, parser: &Parser) -> io::Result<()> { + try!(write!(self.writer, "{}", parser.meta.version.unwrap_or("".into()))); + Ok(()) + } + + fn write_bin_name(&mut self, parser: &Parser) -> io::Result<()> { + if let Some(bn) = parser.meta.bin_name.as_ref() { + if bn.contains(' ') { + // Incase we're dealing with subcommands i.e. git mv is translated to git-mv + try!(write!(self.writer, "{}", bn.replace(" ", "-"))) + } else { + try!(write!(self.writer, "{}", &parser.meta.name[..])) + } + } else { + try!(write!(self.writer, "{}", &parser.meta.name[..])) + } + Ok(()) + } + + pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { + if let Some(h) = parser.meta.help_str { + try!(writeln!(self.writer, "{}", h).map_err(Error::from)); + Ok(()) + } else { + self.write_default_help(&parser) + } + } + + #[cfg_attr(feature = "lints", allow(for_kv_map))] + pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { + + // Print the version + try!(self.write_bin_name(&parser)); + try!(self.writer.write(b" ")); + try!(self.write_version(&parser)); + try!(self.writer.write(b"\n")); + if let Some(author) = parser.meta.author { + try!(write!(self.writer, "{}\n", author)); + } + if let Some(about) = parser.meta.about { + try!(write!(self.writer, "{}\n", about)); + } + + try!(write!(self.writer, "\n{}", parser.create_usage(&[]))); + + let flags = !parser.has_flags(); + let pos = !parser.has_positionals(); + let opts = !parser.has_opts(); + let subcmds = !parser.has_subcommands(); + + if flags || opts || pos || subcmds { + try!(write!(self.writer, "\n")); + try!(self.write_all_args(&parser)); + } + + if let Some(h) = parser.meta.more_help { + try!(write!(self.writer, "\n{}", h)); + } + + self.writer.flush().map_err(Error::from) + } +} + + +fn find_idx_of_space(full: &str, start: usize) -> usize { + debugln!("fn=find_idx_of_space;"); + let haystack = &full[..start]; + debugln!("haystack: {}", haystack); + for (i, c) in haystack.chars().rev().enumerate() { + debugln!("iter;c={},i={}", c, i); + if c == ' ' { + debugln!("Found space returning start-i...{}", start - (i + 1)); + return start - (i + 1); + } + } + 0 +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 04170c8c..43116350 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -4,6 +4,7 @@ mod settings; mod macros; mod parser; mod meta; +mod help; pub use self::settings::AppSettings; @@ -23,6 +24,7 @@ use vec_map::VecMap; use args::{Arg, HelpWriter, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher}; use app::parser::Parser; +use app::help::Help; use errors::Error; use errors::Result as ClapResult; @@ -631,6 +633,21 @@ impl<'a, 'b> App<'a, 'b> { self.p.write_help(w) } + /// Writes the full help message to the user to a `io::Write` object + /// USING A NEW implementation. + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_help(&mut out).ok().expect("failed to write to stdout"); + /// ``` + pub fn write_new_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_app_help(w, &self) + } + /// Starts the parsing process, upon a failed parse an error will be displayed to the user and /// the process with exit with the appropriate error code. By default this method gets matches /// from `env::args_os` diff --git a/tests/new_help.rs b/tests/new_help.rs new file mode 100644 index 00000000..2a8192dd --- /dev/null +++ b/tests/new_help.rs @@ -0,0 +1,265 @@ +extern crate clap; + +extern crate test; +use test::Bencher; + +use std::io::Cursor; + +use clap::App; +use clap::{Arg, SubCommand}; + +fn build_old_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn build_new_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_new_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn compare(app: &App) -> bool { + let old = build_old_help(&app); + let new = build_new_help(&app); + let b = old == new; + if !b { + println!(""); + println!("--> old"); + println!("{}", old); + println!("--> new"); + println!("{}", new); + println!("--") + } + b +} + +#[test] +fn test_new_help() { + assert!(compare(&example1())); + assert!(compare(&example2())); + assert!(compare(&example3())); + assert!(compare(&example4())); + assert!(compare(&example5())); + assert!(compare(&example6())); + assert!(compare(&example7())); + assert!(compare(&example8())); + assert!(compare(&example10())); +} + +fn example1<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' + 'Sets an optional output file' + -d... 'Turn debugging information on'") + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) +} + +fn example2<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") +} + +fn example3<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // All application settings go here... + + // A simple "Flag" argument example (i.e. "-d") using the builder pattern + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d")) + + // Two arguments, one "Option" argument (i.e. one that takes a value) such + // as "-c some", and one positional argument (i.e. "myapp some_file") + .args(&[ + Arg::with_name("config") + .help("sets the config file to use") + .takes_value(true) + .short("c") + .long("config"), + Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true) + ]) + + // *Note* the following two examples are convienience methods, if you wish + // to still get the full configurability of Arg::with_name() and the readability + // of arg_from_usage(), you can instantiate a new Arg with Arg::from_usage() and + // still be able to set all the additional properties, just like Arg::with_name() + // + // + // One "Flag" using a usage string + .arg_from_usage("--license 'display the license file'") + + // Two args, one "Positional", and one "Option" using a usage string + .args_from_usage("[output] 'Supply an output file to use' + -i, --int=[IFACE] 'Set an interface to use'") +} + +fn example4<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .about("Parses an input file to do awesome things") + .version("1.0") + .author("Kevin K. ") + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d") + .long("debug")) + .arg(Arg::with_name("config") + .help("sets the config file to use") + .short("c") + .long("config")) + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true)) +} + +fn example5<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // We'll add a flag that represents an awesome meter... + // + // I'll explain each possible setting that "flags" accept. Keep in mind + // that you DO NOT need to set each of these for every flag, only the ones + // you want for your individual case. + .arg(Arg::with_name("awesome") + .help("turns up the awesome") // Displayed when showing help info + .short("a") // Trigger this arg with "-a" + .long("awesome") // Trigger this arg with "--awesome" + .multiple(true) // This flag should allow multiple + // occurrences such as "-aaa" or "-a -a" + .requires("config") // Says, "If the user uses -a, they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a mutually_excludes_all(Vec<&str>) + ) +} + +fn example6<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // We'll add two positional arguments, a input file, and a config file. + // + // I'll explain each possible setting that "positionals" accept. Keep in + // mind that you DO NOT need to set each of these for every flag, only the + // ones that apply to your individual case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .index(1) // Set the order in which the user must + // specify this argument (Starts at 1) + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a mutually_excludes_all(Vec<&str>) + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + ) + .arg(Arg::with_name("config") + .help("the config file to use") + .index(2)) // Note, we do not need to specify required(true) + // if we don't want to, because "input" already + // requires "config" + // Note, we also do not need to specify requires("input") + // because requires lists are automatically two-way +} + +fn example7<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // Assume we an application that accepts an input file via the "-i file" + // or the "--input file" (as wel as "--input=file"). + // Below every setting supported by option arguments is discussed. + // NOTE: You DO NOT need to specify each setting, only those which apply + // to your particular case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .takes_value(true) // MUST be set to true in order to be an "option" argument + .short("i") // This argument is triggered with "-i" + .long("input") // This argument is triggered with "--input" + .multiple(true) // Set to true if you wish to allow multiple occurrences + // such as "-i file -i other_file -i third_file" + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) +} + +fn example8<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + // Regular App configuration goes here... + + // Assume we an application that accepts an input file via the "-i file" + // or the "--input file" (as wel as "--input=file"). + // Below every setting supported by option arguments is discussed. + // NOTE: You DO NOT need to specify each setting, only those which apply + // to your particular case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .takes_value(true) // MUST be set to true in order to be an "option" argument + .short("i") // This argument is triggered with "-i" + .long("input") // This argument is triggered with "--input" + .multiple(true) // Set to true if you wish to allow multiple occurrences + // such as "-i file -i other_file -i third_file" + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specifiy a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) +} + +fn example10<'b, 'c>() -> App<'b, 'c> { + App::new("myapp") + .about("does awesome things") + .arg(Arg::with_name("CONFIG") + .help("The config file to use (default is \"config.json\")") + .short("c") + .takes_value(true)) +} + + +#[bench] +fn old_example1(b: &mut Bencher) { + let app = example1(); + b.iter(|| build_old_help(&app)); +} + +#[bench] +fn new_example1(b: &mut Bencher) { + let app = example1(); + b.iter(|| build_new_help(&app)); +} From 81e121edd616f7285593f11120c63bcccae0d23e Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 3 Apr 2016 01:20:55 -0300 Subject: [PATCH 08/10] feat(HELP): Add a Templated Help system. The strategy is to copy the template from the the reader to wrapped stream until a tag is found. Depending on its value, the appropriate content is copied to the wrapped stream. The copy from template is then resumed, repeating this sequence until reading the complete template. Tags arg given inside curly brackets: Valid tags are: * `{bin}` - Binary name. * `{version}` - Version number. * `{author}` - Author information. * `{usage}` - Automatically generated or given usage string. * `{all-args}` - Help for all arguments (options, flags, positionals arguments, and subcommands) including titles. * `{unified}` - Unified help for options and flags. * `{flags}` - Help for flags. * `{options}` - Help for options. * `{positionals}` - Help for positionals arguments. * `{subcommands}` - Help for subcommands. * `{after-help}` - Help for flags. --- benches/04_new_help.rs | 24 +++ src/app/help.rs | 376 +++++++++++++++++++++++++++------ src/app/meta.rs | 3 + src/app/mod.rs | 35 ++- tests/example1_tmpl_full.txt | 15 ++ tests/example1_tmpl_simple.txt | 8 + tests/new_help.rs | 96 +++++++-- 7 files changed, 468 insertions(+), 89 deletions(-) create mode 100644 tests/example1_tmpl_full.txt create mode 100644 tests/example1_tmpl_simple.txt diff --git a/benches/04_new_help.rs b/benches/04_new_help.rs index f1390d8f..5b90c77b 100644 --- a/benches/04_new_help.rs +++ b/benches/04_new_help.rs @@ -341,3 +341,27 @@ fn example10_new(b: &mut Bencher) { let app = example10(); b.iter(|| build_new_help(&app)); } + +#[bench] +fn example4_template(b: &mut Bencher) { +/* +MyApp 1.0 +Kevin K. +Parses an input file to do awesome things + +USAGE: + test [FLAGS] + +FLAGS: + -c, --config sets the config file to use + -d, --debug turn on debugging information + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + the input file to use +*/ + + let app = example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n"); + b.iter(|| build_new_help(&app)); +} diff --git a/src/app/help.rs b/src/app/help.rs index 69680fcb..b659d042 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -1,5 +1,5 @@ -use std::io::{self, Write}; +use std::io::{self, Cursor, Read, Write}; use std::collections::BTreeMap; use std::fmt::Display; use std::cmp; @@ -17,6 +17,7 @@ use term; const TAB: &'static str = " "; +// These are just convenient traits to make the code easier to read. trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T where T: AnyArg<'b, 'c> + Display {} @@ -31,12 +32,20 @@ impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T } } +fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { + x +} + impl<'b, 'c> DispOrder for App<'b, 'c> { fn disp_ord(&self) -> usize { 999 } } + +/// CLAP Help Writer. +/// +/// Wraps a writer stream providing different methods to generate help for CLAP objects. pub struct Help<'a> { writer: &'a mut Write, next_line_help: bool, @@ -44,7 +53,9 @@ pub struct Help<'a> { term_w: Option, } +// Public Functions impl<'a> Help<'a> { + /// Create a new Help instance. pub fn new(w: &'a mut Write, next_line_help: bool, hide_pv: bool) -> Self { Help { writer: w, @@ -54,21 +65,30 @@ impl<'a> Help<'a> { } } + /// Reads help settings from an App + /// and write its help to the wrapped stream. pub fn write_app_help(w: &'a mut Write, app: &App) -> ClapResult<()> { - let ref parser = app.p; - let nlh = parser.is_set(AppSettings::NextLineHelp); - let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); - Self::new(w, nlh, hide_v).write_help(&parser) + let nlh = app.p.is_set(AppSettings::NextLineHelp); + let hide_v = app.p.is_set(AppSettings::HidePossibleValuesInHelp); + Self::new(w, nlh, hide_v).write_help(&app.p) + } + + /// Writes the parser help to the wrapped stream. + pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { + if let Some(h) = parser.meta.help_str { + try!(writeln!(self.writer, "{}", h).map_err(Error::from)); + } else if let Some(ref tmpl) = parser.meta.template { + try!(self.write_templated_help(&parser, tmpl)); + } else { + try!(self.write_default_help(&parser)); + } + Ok(()) } } - -fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { - x -} - -// AnyArg +// Methods to write AnyArg help. impl<'a> Help<'a> { + /// Writes help for each argument in the order they were declared to the wrapped stream. fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> where I: Iterator> { @@ -90,6 +110,7 @@ impl<'a> Help<'a> { Ok(()) } + /// Sorts arguments by length and display order and write their help to the wrapped stream. fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> where I: Iterator> { @@ -114,6 +135,7 @@ impl<'a> Help<'a> { Ok(()) } + /// Writes help for an argument to the wrapped stream. fn write_arg<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) @@ -127,6 +149,7 @@ impl<'a> Help<'a> { Ok(()) } + /// Writes argument's short command to the wrapped stream. fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { debugln!("fn=short;"); try!(write!(self.writer, "{}", TAB)); @@ -139,6 +162,7 @@ impl<'a> Help<'a> { } } + /// Writes argument's long command to the wrapped stream. fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { debugln!("fn=long;"); if !arg.has_switch() { @@ -157,10 +181,8 @@ impl<'a> Help<'a> { } try!(write!(self.writer, " ")); } else { - // write_spaces! fails when using self.writer - let ref mut w = self.writer; if let Some(l) = arg.long() { - try!(write!(w, + try!(write!(self.writer, "{}--{}", if arg.short().is_some() { ", " @@ -169,18 +191,19 @@ impl<'a> Help<'a> { }, l)); if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) { - write_spaces!((longest + 4) - (l.len() + 2), w); + write_nspaces!(self.writer, (longest + 4) - (l.len() + 2)); } } else { if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) { // 6 is tab (4) + -- (2) - write_spaces!((longest + 6), w); + write_nspaces!(self.writer, (longest + 6)); } } } Ok(()) } + /// Writes argument's possible values to the wrapped stream. fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { debugln!("fn=val;"); if !arg.takes_value() { @@ -209,7 +232,6 @@ impl<'a> Help<'a> { } else { try!(write!(self.writer, "{}", arg)); } - let ref mut w = self.writer; if arg.has_switch() { if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) { let self_len = arg.to_string().len(); @@ -225,14 +247,15 @@ impl<'a> Help<'a> { spcs += 8; } - write_spaces!(spcs, w); + write_nspaces!(self.writer, spcs); } } else if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) { - write_spaces!(longest + 4 - (arg.to_string().len()), w); + write_nspaces!(self.writer, longest + 4 - (arg.to_string().len())); } Ok(()) } + /// Writes argument's help to the wrapped stream. fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { debugln!("fn=help;"); let spec_vals = self.spec_vals(arg); @@ -313,28 +336,27 @@ impl<'a> Help<'a> { } let help = if !help.is_empty() { &*help - } else if !spec_vals.is_empty() { + } else if spec_vals.is_empty() { + h + } else { help.push_str(h); help.push_str(&*spec_vals); &*help - } else { - h }; if help.contains("{n}") { if let Some(part) = help.split("{n}").next() { try!(write!(self.writer, "{}", part)); } - let ref mut w = self.writer; for part in help.split("{n}").skip(1) { - try!(write!(w, "\n")); + try!(write!(self.writer, "\n")); if self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) { - try!(write!(w, "{}{}", TAB, TAB)); + try!(write!(self.writer, "{}{}", TAB, TAB)); } else if arg.has_switch() { - write_spaces!(longest + 12, w); + write_nspaces!(self.writer, longest + 12); } else { - write_spaces!(longest + 8, w); + write_nspaces!(self.writer, longest + 8); } - try!(write!(w, "{}", part)); + try!(write!(self.writer, "{}", part)); } } else { try!(write!(self.writer, "{}", help)); @@ -348,14 +370,14 @@ impl<'a> Help<'a> { debugln!("Writing defaults"); return format!(" [default: {}] {}", pv, - if !self.hide_pv { + if self.hide_pv { + "".into() + } else { if let Some(ref pv) = a.possible_vals() { format!(" [values: {}]", pv.join(", ")) } else { "".into() } - } else { - "".into() }); } else if !self.hide_pv { debugln!("Writing values"); @@ -369,8 +391,10 @@ impl<'a> Help<'a> { } -// Parser +// Methods to write Parser help. impl<'a> Help<'a> { + /// Writes help for all arguments (options, flags, args, subcommands) + /// including titles of a Parser Object to the wrapped stream. pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { let flags = !parser.has_flags(); @@ -380,58 +404,78 @@ impl<'a> Help<'a> { let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); + let mut first = true; + if unified_help && (flags || opts) { let opts_flags = parser.iter_flags() .map(as_arg_trait) .chain(parser.iter_opts().map(as_arg_trait)); - try!(write!(self.writer, "\nOPTIONS:\n")); + try!(write!(self.writer, "OPTIONS:\n")); try!(self.write_args(opts_flags)); + first = false; } else { if flags { - try!(write!(self.writer, "\nFLAGS:\n")); + try!(write!(self.writer, "FLAGS:\n")); try!(self.write_args(parser.iter_flags() .map(as_arg_trait))); + first = false; } if opts { - try!(write!(self.writer, "\nOPTIONS:\n")); + if !first { + try!(self.writer.write(b"\n")); + } + try!(write!(self.writer, "OPTIONS:\n")); try!(self.write_args(parser.iter_opts().map(as_arg_trait))); + first = false; } } if pos { - try!(write!(self.writer, "\nARGS:\n")); + if !first { + try!(self.writer.write(b"\n")); + } + try!(write!(self.writer, "ARGS:\n")); try!(self.write_args_unsorted(parser.iter_positionals().map(as_arg_trait))); + first = false; } if subcmds { - try!(write!(self.writer, "\nSUBCOMMANDS:\n")); - - let mut longest = 0; - - let mut ord_m = VecMap::new(); - for sc in parser.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { - let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); - btm.insert(sc.p.meta.name.clone(), sc); - longest = cmp::max(longest, sc.p.meta.name.len()); - } - - for (_, btm) in ord_m.into_iter() { - for (_, sc) in btm.into_iter() { - try!(self.write_arg(sc, longest)); - } + if !first { + try!(self.writer.write(b"\n")); } + try!(write!(self.writer, "SUBCOMMANDS:\n")); + try!(self.write_subcommands(&parser)); } - // - Ok(()) } + /// Writes help for subcommands of a Parser Object to the wrapped stream. + fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { + let mut longest = 0; + + let mut ord_m = VecMap::new(); + for sc in parser.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { + let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); + btm.insert(sc.p.meta.name.clone(), sc); + longest = cmp::max(longest, sc.p.meta.name.len()); + } + + for (_, btm) in ord_m.into_iter() { + for (_, sc) in btm.into_iter() { + try!(self.write_arg(sc, longest)); + } + } + Ok(()) + } + + /// Writes version of a Parser Object to the wrapped stream. fn write_version(&mut self, parser: &Parser) -> io::Result<()> { try!(write!(self.writer, "{}", parser.meta.version.unwrap_or("".into()))); Ok(()) } + /// Writes binary name of a Parser Object to the wrapped stream. fn write_bin_name(&mut self, parser: &Parser) -> io::Result<()> { if let Some(bn) = parser.meta.bin_name.as_ref() { if bn.contains(' ') { @@ -446,16 +490,7 @@ impl<'a> Help<'a> { Ok(()) } - pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { - if let Some(h) = parser.meta.help_str { - try!(writeln!(self.writer, "{}", h).map_err(Error::from)); - Ok(()) - } else { - self.write_default_help(&parser) - } - } - - #[cfg_attr(feature = "lints", allow(for_kv_map))] + /// Writes default help for a Parser Object to the wrapped stream. pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { // Print the version @@ -470,7 +505,10 @@ impl<'a> Help<'a> { try!(write!(self.writer, "{}\n", about)); } - try!(write!(self.writer, "\n{}", parser.create_usage(&[]))); + try!(write!(self.writer, + "\nUSAGE:\n{}{}\n\n", + TAB, + parser.create_usage_no_title(&[]))); let flags = !parser.has_flags(); let pos = !parser.has_positionals(); @@ -478,18 +516,224 @@ impl<'a> Help<'a> { let subcmds = !parser.has_subcommands(); if flags || opts || pos || subcmds { - try!(write!(self.writer, "\n")); try!(self.write_all_args(&parser)); } if let Some(h) = parser.meta.more_help { - try!(write!(self.writer, "\n{}", h)); + try!(write!(self.writer, "{}\n", h)); } self.writer.flush().map_err(Error::from) } } +/// Possible results for a copying function that stops when a given +/// byte was found. +enum CopyUntilResult { + DelimiterFound(usize), + DelimiterNotFound(usize), + ReaderEmpty, + ReadError(io::Error), + WriteError(io::Error), +} + +/// Copies the contents of a reader into a writer until a delimiter byte is found. +/// On success, the total number of bytes that were +/// copied from reader to writer is returned. +fn copy_until(r: &mut R, w: &mut W, delimiter_byte: u8) -> CopyUntilResult { + + let mut count = 0; + for wb in r.bytes() { + match wb { + Ok(b) => { + if b == delimiter_byte { + return CopyUntilResult::DelimiterFound(count); + } + match w.write(&[b]) { + Ok(c) => count += c, + Err(e) => return CopyUntilResult::WriteError(e), + } + } + Err(e) => return CopyUntilResult::ReadError(e), + } + } + if count > 0 { + CopyUntilResult::DelimiterNotFound(count) + } else { + CopyUntilResult::ReaderEmpty + } +} + +/// Copies the contents of a reader into a writer until a {tag} is found, +/// copying the tag content to a buffer and returning its size. +/// In addition to Errors, there are three possible outputs: +/// - None: The reader was consumed. +/// - Some(Ok(0)): No tag was captured but the reader still contains data. +/// - Some(Ok(length>0)): a tag with `length` was captured to the tag_buffer. +fn copy_and_capture(r: &mut R, + w: &mut W, + tag_buffer: &mut Cursor>) + -> Option> { + use self::CopyUntilResult::*; + + // Find the opening byte. + match copy_until(r, w, b'{') { + + // The end of the reader was reached without finding the opening tag. + // (either with or without having copied data to the writer) + // Return None indicating that we are done. + ReaderEmpty | DelimiterNotFound(_) => None, + + // Something went wrong. + ReadError(e) | WriteError(e) => Some(Err(e)), + + // The opening byte was found. + // (either with or without having copied data to the writer) + DelimiterFound(_) => { + + // Lets reset the buffer first and find out how long it is. + tag_buffer.set_position(0); + let buffer_size = tag_buffer.get_ref().len(); + + // Find the closing byte,limiting the reader to the length of the buffer. + let mut rb = r.take(buffer_size as u64); + match copy_until(&mut rb, tag_buffer, b'}') { + + // We were already at the end of the reader. + // Return None indicating that we are done. + ReaderEmpty => None, + + // The closing tag was found. + // Return the tag_length. + DelimiterFound(tag_length) => Some(Ok(tag_length)), + + // The end of the reader was found without finding the closing tag. + // Write the opening byte and captured text to the writer. + // Return 0 indicating that nothing was caputred but the reader still contains data. + DelimiterNotFound(not_tag_length) => { + match w.write(b"{") { + Err(e) => Some(Err(e)), + _ => { + match w.write(&tag_buffer.get_ref()[0..not_tag_length]) { + Err(e) => Some(Err(e)), + _ => Some(Ok(0)), + } + } + } + } + + ReadError(e) | WriteError(e) => Some(Err(e)), + } + } + } +} + + +// Methods to write Parser help using templates. +impl<'a> Help<'a> { + /// Write help to stream for the parser in the format defined by the template. + /// + /// Tags arg given inside curly brackets: + /// Valid tags are: + /// * `{bin}` - Binary name. + /// * `{version}` - Version number. + /// * `{author}` - Author information. + /// * `{usage}` - Automatically generated or given usage string. + /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, + /// and subcommands) including titles. + /// * `{unified}` - Unified help for options and flags. + /// * `{flags}` - Help for flags. + /// * `{options}` - Help for options. + /// * `{positionals}` - Help for positionals arguments. + /// * `{subcommands}` - Help for subcommands. + /// * `{after-help}` - Help for flags. + /// + /// The template system is, on purpose, very simple. Therefore the tags have to writen + /// in the lowercase and without spacing. + fn write_templated_help(&mut self, parser: &Parser, template: &str) -> ClapResult<()> { + let mut tmplr = Cursor::new(&template); + let mut tag_buf = Cursor::new(vec![0u8; 15]); + + // The strategy is to copy the template from the the reader to wrapped stream + // until a tag is found. Depending on its value, the appropriate content is copied + // to the wrapped stream. + // The copy from template is then resumed, repeating this sequence until reading + // the complete template. + + loop { + let tag_length = match copy_and_capture(&mut tmplr, &mut self.writer, &mut tag_buf) { + None => return Ok(()), + Some(Err(e)) => return Err(Error::from(e)), + Some(Ok(val)) if val > 0 => val, + _ => continue, + }; + + match &tag_buf.get_ref()[0..tag_length] { + b"?" => { + try!(self.writer.write(b"Could not decode tag name")); + } + b"bin" => { + try!(self.write_bin_name(&parser)); + } + b"version" => { + try!(write!(self.writer, + "{}", + parser.meta.version.unwrap_or("unknown version"))); + } + b"author" => { + try!(write!(self.writer, + "{}", + parser.meta.author.unwrap_or("unknown author"))); + } + b"about" => { + try!(write!(self.writer, + "{}", + parser.meta.about.unwrap_or("unknown about"))); + } + b"usage" => { + try!(write!(self.writer, "{}", parser.create_usage_no_title(&[]))); + } + b"all-args" => { + try!(self.write_all_args(&parser)); + } + b"unified" => { + let opts_flags = parser.iter_flags() + .map(as_arg_trait) + .chain(parser.iter_opts().map(as_arg_trait)); + try!(self.write_args(opts_flags)); + } + b"flags" => { + try!(self.write_args(parser.iter_flags() + .map(as_arg_trait))); + } + b"options" => { + try!(self.write_args(parser.iter_opts() + .map(as_arg_trait))); + } + b"positionals" => { + try!(self.write_args(parser.iter_positionals() + .map(as_arg_trait))); + } + b"subcommands" => { + try!(self.write_subcommands(&parser)); + } + b"after-help" => { + try!(write!(self.writer, + "{}", + parser.meta.more_help.unwrap_or("unknown after-help"))); + } + // Unknown tag, write it back. + ref r => { + try!(self.writer.write(b"{")); + try!(self.writer.write(r)); + try!(self.writer.write(b"}")); + } + } + + } + } +} + fn find_idx_of_space(full: &str, start: usize) -> usize { debugln!("fn=find_idx_of_space;"); diff --git a/src/app/meta.rs b/src/app/meta.rs index 04626e0b..c49f4637 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -11,6 +11,7 @@ pub struct AppMeta<'b> { pub usage: Option, pub help_str: Option<&'b str>, pub disp_ord: usize, + pub template: Option<&'b str>, } impl<'b> Default for AppMeta<'b> { @@ -26,6 +27,7 @@ impl<'b> Default for AppMeta<'b> { bin_name: None, help_str: None, disp_ord: 999, + template: None, } } } @@ -53,6 +55,7 @@ impl<'b> Clone for AppMeta<'b> { bin_name: self.bin_name.clone(), help_str: self.help_str, disp_ord: self.disp_ord, + template: self.template, } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 43116350..460739d5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -313,6 +313,39 @@ impl<'a, 'b> App<'a, 'b> { self } + /// Sets the help template to be used, overriding the default format. + /// + /// Tags arg given inside curly brackets: + /// Valid tags are: + /// * `{bin}` - Binary name. + /// * `{version}` - Version number. + /// * `{author}` - Author information. + /// * `{usage}` - Automatically generated or given usage string. + /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, + /// and subcommands) including titles. + /// * `{unified}` - Unified help for options and flags. + /// * `{flags}` - Help for flags. + /// * `{options}` - Help for options. + /// * `{positionals}` - Help for positionals arguments. + /// * `{subcommands}` - Help for subcommands. + /// * `{after-help}` - Help for flags. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .version("1.0") + /// .template("{bin} ({version}) - {usage}") + /// # ; + /// ``` + /// **NOTE:**The template system is, on purpose, very simple. Therefore the tags have to writen + /// in the lowercase and without spacing. + pub fn template>(mut self, s: S) -> Self { + self.p.meta.template = Some(s.into()); + self + } + /// Enables a single Application level settings. /// /// See `AppSettings` for a full list of possibilities and examples. @@ -642,7 +675,7 @@ impl<'a, 'b> App<'a, 'b> { /// use std::io; /// let mut app = App::new("myprog"); /// let mut out = io::stdout(); - /// app.write_help(&mut out).ok().expect("failed to write to stdout"); + /// app.write_new_help(&mut out).ok().expect("failed to write to stdout"); /// ``` pub fn write_new_help(&self, w: &mut W) -> ClapResult<()> { Help::write_app_help(w, &self) diff --git a/tests/example1_tmpl_full.txt b/tests/example1_tmpl_full.txt new file mode 100644 index 00000000..6ae57fa5 --- /dev/null +++ b/tests/example1_tmpl_full.txt @@ -0,0 +1,15 @@ +{bin} {version} +{author} +{about} + +USAGE: + {usage} + +FLAGS: +{flags} +OPTIONS: +{options} +ARGS: +{positionals} +SUBCOMMANDS: +{subcommands} diff --git a/tests/example1_tmpl_simple.txt b/tests/example1_tmpl_simple.txt new file mode 100644 index 00000000..af6c4b07 --- /dev/null +++ b/tests/example1_tmpl_simple.txt @@ -0,0 +1,8 @@ +{bin} {version} +{author} +{about} + +USAGE: + {usage} + +{all-args} diff --git a/tests/new_help.rs b/tests/new_help.rs index 2a8192dd..7a9bb043 100644 --- a/tests/new_help.rs +++ b/tests/new_help.rs @@ -1,13 +1,13 @@ extern crate clap; -extern crate test; -use test::Bencher; - use std::io::Cursor; use clap::App; use clap::{Arg, SubCommand}; +static EXAMPLE1_TMPL_S : &'static str = include_str!("example1_tmpl_simple.txt"); +static EXAMPLE1_TMPS_F : &'static str = include_str!("example1_tmpl_full.txt"); + fn build_old_help(app: &App) -> String { let mut buf = Cursor::new(Vec::with_capacity(50)); app.write_help(&mut buf).unwrap(); @@ -23,22 +23,41 @@ fn build_new_help(app: &App) -> String { } fn compare(app: &App) -> bool { - let old = build_old_help(&app); - let new = build_new_help(&app); - let b = old == new; + let hlp1f = build_old_help(&app); + let hlp1 = hlp1f.trim(); + let hlp2f = build_new_help(&app); + let hlp2 = hlp2f.trim(); + let b = hlp1 == hlp2; if !b { println!(""); println!("--> old"); - println!("{}", old); + println!("{}", hlp1); println!("--> new"); - println!("{}", new); + println!("{}", hlp2); + println!("--") + } + b +} + +fn compare2(app1: &App, app2: &App) -> bool { + let hlp1f = build_new_help(&app1); + let hlp1 = hlp1f.trim(); + let hlp2f = build_new_help(&app2); + let hlp2 = hlp2f.trim(); + let b = hlp1 == hlp2; + if !b { + println!(""); + println!("--> hlp1"); + println!("{}", hlp1); + println!("--> hlp2"); + println!("{}", hlp2); println!("--") } b } #[test] -fn test_new_help() { +fn comparison_with_old_help() { assert!(compare(&example1())); assert!(compare(&example2())); assert!(compare(&example3())); @@ -50,6 +69,52 @@ fn test_new_help() { assert!(compare(&example10())); } +#[test] +fn comparison_with_template() { + assert!(compare2(&example1(), &example1().template(EXAMPLE1_TMPL_S))); + assert!(compare2(&example1(), &example1().template(EXAMPLE1_TMPS_F))); +} + +#[test] +fn template_empty() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(""); + assert_eq!(build_new_help(&app), ""); +} + +#[test] +fn template_notag() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(" no tag "); + assert_eq!(build_new_help(&app), " no tag "); +} + +#[test] +fn template_unknowntag() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(" {unknown_tag} "); + assert_eq!(build_new_help(&app), " {unknown_tag} "); +} + +#[test] +fn template_author_version() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template("{author}\n{version}\n{about}\n{bin}"); + assert_eq!(build_new_help(&app), "Kevin K. \n1.0\nDoes awesome things\nMyApp"); +} + fn example1<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") .version("1.0") @@ -250,16 +315,3 @@ fn example10<'b, 'c>() -> App<'b, 'c> { .short("c") .takes_value(true)) } - - -#[bench] -fn old_example1(b: &mut Bencher) { - let app = example1(); - b.iter(|| build_old_help(&app)); -} - -#[bench] -fn new_example1(b: &mut Bencher) { - let app = example1(); - b.iter(|| build_new_help(&app)); -} From 8d23806bd67530ad412c34a1dcdcb1435555573d Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sat, 9 Apr 2016 15:25:21 -0300 Subject: [PATCH 09/10] fix(HELP): Adjust Help to semantic changes introduced in 6933b84 --- src/app/help.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/help.rs b/src/app/help.rs index b659d042..4524afb7 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -397,10 +397,10 @@ impl<'a> Help<'a> { /// including titles of a Parser Object to the wrapped stream. pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { - let flags = !parser.has_flags(); - let pos = !parser.has_positionals(); - let opts = !parser.has_opts(); - let subcmds = !parser.has_subcommands(); + let flags = parser.has_flags(); + let pos = parser.has_positionals(); + let opts = parser.has_opts(); + let subcmds = parser.has_subcommands(); let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); @@ -510,10 +510,10 @@ impl<'a> Help<'a> { TAB, parser.create_usage_no_title(&[]))); - let flags = !parser.has_flags(); - let pos = !parser.has_positionals(); - let opts = !parser.has_opts(); - let subcmds = !parser.has_subcommands(); + let flags = parser.has_flags(); + let pos = parser.has_positionals(); + let opts = parser.has_opts(); + let subcmds = parser.has_subcommands(); if flags || opts || pos || subcmds { try!(self.write_all_args(&parser)); From 627ae38dc0c382c37deb371d1c5dda5d632ab7bf Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sat, 9 Apr 2016 21:35:24 -0300 Subject: [PATCH 10/10] refactor(HELP): Removed code for old help system and tests that helped with the transitions --- benches/04_new_help.rs | 148 ++++---------- src/app/help.rs | 50 ++++- src/app/mod.rs | 41 +--- src/app/parser.rs | 146 +------------ src/args/arg_builder/flag.rs | 8 +- src/args/arg_builder/option.rs | 8 +- src/args/arg_builder/positional.rs | 8 +- src/args/help_writer.rs | 288 -------------------------- src/args/mod.rs | 2 - tests/new_help.rs | 317 ----------------------------- tests/template_help.rs | 91 +++++++++ 11 files changed, 178 insertions(+), 929 deletions(-) delete mode 100644 src/args/help_writer.rs delete mode 100644 tests/new_help.rs create mode 100644 tests/template_help.rs diff --git a/benches/04_new_help.rs b/benches/04_new_help.rs index 5b90c77b..abc7a453 100644 --- a/benches/04_new_help.rs +++ b/benches/04_new_help.rs @@ -10,21 +10,14 @@ use std::io::Cursor; use clap::App; use clap::{Arg, SubCommand}; -fn build_old_help(app: &App) -> String { +fn build_help(app: &App) -> String { let mut buf = Cursor::new(Vec::with_capacity(50)); app.write_help(&mut buf).unwrap(); let content = buf.into_inner(); String::from_utf8(content).unwrap() } -fn build_new_help(app: &App) -> String { - let mut buf = Cursor::new(Vec::with_capacity(50)); - app.write_new_help(&mut buf).unwrap(); - let content = buf.into_inner(); - String::from_utf8(content).unwrap() -} - -fn example1<'b, 'c>() -> App<'b, 'c> { +fn app_example1<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") .version("1.0") .author("Kevin K. ") @@ -37,14 +30,14 @@ fn example1<'b, 'c>() -> App<'b, 'c> { .arg_from_usage("-l, --list 'lists test values'")) } -fn example2<'b, 'c>() -> App<'b, 'c> { +fn app_example2<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") .version("1.0") .author("Kevin K. ") .about("Does awesome things") } -fn example3<'b, 'c>() -> App<'b, 'c> { +fn app_example3<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") // All application settings go here... @@ -81,7 +74,7 @@ fn example3<'b, 'c>() -> App<'b, 'c> { -i, --int=[IFACE] 'Set an interface to use'") } -fn example4<'b, 'c>() -> App<'b, 'c> { +fn app_example4<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") .about("Parses an input file to do awesome things") .version("1.0") @@ -100,7 +93,7 @@ fn example4<'b, 'c>() -> App<'b, 'c> { .required(true)) } -fn example5<'b, 'c>() -> App<'b, 'c> { +fn app_example5<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") // Regular App configuration goes here... @@ -125,7 +118,7 @@ fn example5<'b, 'c>() -> App<'b, 'c> { ) } -fn example6<'b, 'c>() -> App<'b, 'c> { +fn app_example6<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") // Regular App configuration goes here... @@ -158,7 +151,7 @@ fn example6<'b, 'c>() -> App<'b, 'c> { // because requires lists are automatically two-way } -fn example7<'b, 'c>() -> App<'b, 'c> { +fn app_example7<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") // Regular App configuration goes here... @@ -187,7 +180,7 @@ fn example7<'b, 'c>() -> App<'b, 'c> { ) } -fn example8<'b, 'c>() -> App<'b, 'c> { +fn app_example8<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") // Regular App configuration goes here... @@ -216,7 +209,7 @@ fn example8<'b, 'c>() -> App<'b, 'c> { ) } -fn example10<'b, 'c>() -> App<'b, 'c> { +fn app_example10<'b, 'c>() -> App<'b, 'c> { App::new("myapp") .about("does awesome things") .arg(Arg::with_name("CONFIG") @@ -225,121 +218,58 @@ fn example10<'b, 'c>() -> App<'b, 'c> { .takes_value(true)) } - #[bench] -fn example1_old(b: &mut Bencher) { - let app = example1(); - b.iter(|| build_old_help(&app)); +fn example1(b: &mut Bencher) { + let app = app_example1(); + b.iter(|| build_help(&app)); } #[bench] -fn example1_new(b: &mut Bencher) { - let app = example1(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example2_old(b: &mut Bencher) { - let app = example2(); - b.iter(|| build_old_help(&app)); +fn example2(b: &mut Bencher) { + let app = app_example2(); + b.iter(|| build_help(&app)); } #[bench] -fn example2_new(b: &mut Bencher) { - let app = example2(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example3_old(b: &mut Bencher) { - let app = example3(); - b.iter(|| build_old_help(&app)); +fn example3(b: &mut Bencher) { + let app = app_example3(); + b.iter(|| build_help(&app)); } #[bench] -fn example3_new(b: &mut Bencher) { - let app = example3(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example4_old(b: &mut Bencher) { - let app = example4(); - b.iter(|| build_old_help(&app)); +fn example4(b: &mut Bencher) { + let app = app_example4(); + b.iter(|| build_help(&app)); } #[bench] -fn example4_new(b: &mut Bencher) { - let app = example4(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example5_old(b: &mut Bencher) { - let app = example5(); - b.iter(|| build_old_help(&app)); +fn example5(b: &mut Bencher) { + let app = app_example5(); + b.iter(|| build_help(&app)); } #[bench] -fn example5_new(b: &mut Bencher) { - let app = example5(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example6_old(b: &mut Bencher) { - let app = example6(); - b.iter(|| build_old_help(&app)); +fn example6(b: &mut Bencher) { + let app = app_example6(); + b.iter(|| build_help(&app)); } #[bench] -fn example6_new(b: &mut Bencher) { - let app = example6(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example7_old(b: &mut Bencher) { - let app = example7(); - b.iter(|| build_old_help(&app)); +fn example7(b: &mut Bencher) { + let app = app_example7(); + b.iter(|| build_help(&app)); } #[bench] -fn example7_new(b: &mut Bencher) { - let app = example7(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example8_old(b: &mut Bencher) { - let app = example8(); - b.iter(|| build_old_help(&app)); +fn example8(b: &mut Bencher) { + let app = app_example8(); + b.iter(|| build_help(&app)); } #[bench] -fn example8_new(b: &mut Bencher) { - let app = example8(); - b.iter(|| build_new_help(&app)); -} - - -#[bench] -fn example10_old(b: &mut Bencher) { - let app = example10(); - b.iter(|| build_old_help(&app)); -} - -#[bench] -fn example10_new(b: &mut Bencher) { - let app = example10(); - b.iter(|| build_new_help(&app)); +fn example10(b: &mut Bencher) { + let app = app_example10(); + b.iter(|| build_help(&app)); } #[bench] @@ -362,6 +292,6 @@ ARGS: the input file to use */ - let app = example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n"); - b.iter(|| build_new_help(&app)); + let app = app_example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n"); + b.iter(|| build_help(&app)); } diff --git a/src/app/help.rs b/src/app/help.rs index 4524afb7..6d2c65df 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -15,6 +15,22 @@ use app::parser::Parser; use term; +#[cfg(all(feature = "wrap_help", not(target_os = "windows")))] +use unicode_width::UnicodeWidthStr; + +use strext::_StrExt; + +#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))] +fn str_width(s: &str) -> usize { + s.len() +} + +#[cfg(all(feature = "wrap_help", not(target_os = "windows")))] +fn str_width(s: &str) -> usize { + UnicodeWidthStr::width(s) +} + + const TAB: &'static str = " "; // These are just convenient traits to make the code easier to read. @@ -68,9 +84,15 @@ impl<'a> Help<'a> { /// Reads help settings from an App /// and write its help to the wrapped stream. pub fn write_app_help(w: &'a mut Write, app: &App) -> ClapResult<()> { - let nlh = app.p.is_set(AppSettings::NextLineHelp); - let hide_v = app.p.is_set(AppSettings::HidePossibleValuesInHelp); - Self::new(w, nlh, hide_v).write_help(&app.p) + Self::write_parser_help(w, &app.p) + } + + /// Reads help settings from a Parser + /// and write its help to the wrapped stream. + pub fn write_parser_help(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { + let nlh = parser.is_set(AppSettings::NextLineHelp); + let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); + Self::new(w, nlh, hide_v).write_help(&parser) } /// Writes the parser help to the wrapped stream. @@ -229,6 +251,8 @@ impl<'a> Help<'a> { try!(write!(self.writer, " ")); } } + } else if arg.has_switch() { + try!(write!(self.writer, "<{}>", arg.name())); } else { try!(write!(self.writer, "{}", arg)); } @@ -269,7 +293,8 @@ impl<'a> Help<'a> { // determine if our help fits or needs to wrap let width = self.term_w.unwrap_or(0); debugln!("Term width...{}", width); - let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= width); + let too_long = self.term_w.is_some() && (spcs + str_width(h) + + str_width(&*spec_vals) >= width); debugln!("Too long...{:?}", too_long); // Is help on next line, if so newline + 2x tab @@ -283,13 +308,13 @@ impl<'a> Help<'a> { help.push_str(h); help.push_str(&*spec_vals); debugln!("help: {}", help); - debugln!("help len: {}", help.len()); + debugln!("help width: {}", str_width(&*help)); // Determine how many newlines we need to insert let avail_chars = width - spcs; debugln!("Usable space: {}", avail_chars); let longest_w = { let mut lw = 0; - for l in help.split(' ').map(|s| s.len()) { + for l in help.split(' ').map(|s| str_width(s)) { if l > lw { lw = l; } @@ -297,7 +322,7 @@ impl<'a> Help<'a> { lw }; debugln!("Longest word...{}", longest_w); - debug!("Enough space..."); + debug!("Enough space to wrap..."); if longest_w < avail_chars { sdebugln!("Yes"); let mut indices = vec![]; @@ -314,7 +339,7 @@ impl<'a> Help<'a> { debugln!("Adding idx: {}", idx); debugln!("At {}: {:?}", idx, help.chars().nth(idx)); indices.push(idx); - if &help[idx..].len() <= &avail_chars { + if str_width(&help[idx..]) <= avail_chars { break; } } @@ -735,9 +760,14 @@ impl<'a> Help<'a> { } -fn find_idx_of_space(full: &str, start: usize) -> usize { +fn find_idx_of_space(full: &str, mut start: usize) -> usize { debugln!("fn=find_idx_of_space;"); - let haystack = &full[..start]; + let haystack = if full._is_char_boundary(start) { + &full[..start] + } else { + while !full._is_char_boundary(start) { start -= 1; } + &full[..start] + }; debugln!("haystack: {}", haystack); for (i, c) in haystack.chars().rev().enumerate() { debugln!("iter;c={},i={}", c, i); diff --git a/src/app/mod.rs b/src/app/mod.rs index 460739d5..2878fe07 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -22,7 +22,7 @@ use std::fmt; use yaml_rust::Yaml; use vec_map::VecMap; -use args::{Arg, HelpWriter, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher}; +use args::{Arg, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher}; use app::parser::Parser; use app::help::Help; use errors::Error; @@ -663,21 +663,6 @@ impl<'a, 'b> App<'a, 'b> { /// app.write_help(&mut out).ok().expect("failed to write to stdout"); /// ``` pub fn write_help(&self, w: &mut W) -> ClapResult<()> { - self.p.write_help(w) - } - - /// Writes the full help message to the user to a `io::Write` object - /// USING A NEW implementation. - /// # Examples - /// - /// ```no_run - /// # use clap::App; - /// use std::io; - /// let mut app = App::new("myprog"); - /// let mut out = io::stdout(); - /// app.write_new_help(&mut out).ok().expect("failed to write to stdout"); - /// ``` - pub fn write_new_help(&self, w: &mut W) -> ClapResult<()> { Help::write_app_help(w, &self) } @@ -852,30 +837,6 @@ impl<'a, 'b> App<'a, 'b> { e.exit() } - - #[doc(hidden)] - pub fn write_self_help(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()> - where W: Write - { - let hw = HelpWriter::new(self, longest, nlh); - hw.write_to(w) - - // try!(write!(w, " {}", self.p.meta.name)); - // write_spaces!((longest_sc + 4) - (self.p.meta.name.len()), w); - // if let Some(a) = self.p.meta.about { - // if a.contains("{n}") { - // let mut ab = a.split("{n}"); - // while let Some(part) = ab.next() { - // try!(write!(w, "{}\n", part)); - // write_spaces!(longest_sc + 8, w); - // try!(write!(w, "{}", ab.next().unwrap_or(""))); - // } - // } else { - // try!(write!(w, "{}", a)); - // } - // } - // write!(w, "\n") - } } #[cfg(feature = "yaml")] diff --git a/src/app/parser.rs b/src/app/parser.rs index 23f06017..86e866ca 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -8,6 +8,7 @@ use std::os::unix::ffi::OsStrExt; use vec_map::{self, VecMap}; +use app::help::Help; use app::App; use args::{Arg, FlagBuilder, OptBuilder, ArgGroup, PosBuilder}; use app::settings::{AppSettings, AppFlags}; @@ -1341,8 +1342,8 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { } else { usage.push_str(" [OPTIONS]"); } - if !self.is_set(AppSettings::UnifiedHelpMessage) - && self.has_opts() + if !self.is_set(AppSettings::UnifiedHelpMessage) + && self.has_opts() && self.opts.iter().any(|a| !a.settings.is_set(ArgSettings::Required)) { usage.push_str(" [OPTIONS]"); } @@ -1421,146 +1422,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { #[cfg_attr(feature = "lints", allow(for_kv_map))] pub fn write_help(&self, w: &mut W) -> ClapResult<()> { - if let Some(h) = self.meta.help_str { - return writeln!(w, "{}", h).map_err(Error::from); - } - - // Print the version - try!(self.write_version(w)); - if let Some(author) = self.meta.author { - try!(write!(w, "{}\n", author)); - } - if let Some(about) = self.meta.about { - try!(write!(w, "{}\n", about)); - } - - try!(write!(w, "\n{}", self.create_usage(&[]))); - - let flags = self.has_flags(); - let pos = self.has_positionals(); - let opts = self.has_opts(); - let subcmds = self.has_subcommands(); - let unified_help = self.is_set(AppSettings::UnifiedHelpMessage); - - let mut longest_flag = 0; - for fl in self.flags.iter() - .filter(|f| f.long.is_some() && !(f.settings.is_set(ArgSettings::Hidden) || f.settings.is_set(ArgSettings::NextLineHelp))) - .map(|a| a.to_string().len()) { - if fl > longest_flag { - longest_flag = fl; - } - } - let mut longest_opt = 0; - for ol in self.opts.iter() - .filter(|o| !(o.settings.is_set(ArgSettings::Hidden) || o.settings.is_set(ArgSettings::NextLineHelp))) - .map(|a| a.to_string().len()) { - if ol > longest_opt { - longest_opt = ol; - } - } - let mut longest_pos = 0; - for pl in self.positionals - .values() - .filter(|p| !(p.settings.is_set(ArgSettings::Hidden) || p.settings.is_set(ArgSettings::NextLineHelp))) - .map(|f| f.to_string().len()) { - if pl > longest_pos { - longest_pos = pl; - } - } - let mut longest_sc = 0; - for scl in self.subcommands - .iter() - .filter(|s| !s.p.is_set(AppSettings::Hidden)) - .map(|s| s.p.meta.name.len()) { - if scl > longest_sc { - longest_sc = scl; - } - } - - if flags || opts || pos || subcmds { - try!(write!(w, "\n")); - } - - let longest = if !unified_help || longest_opt == 0 { - longest_flag - } else { - longest_opt - }; - let nlh = self.settings.is_set(AppSettings::NextLineHelp); - if unified_help && (flags || opts) { - try!(write!(w, "\nOPTIONS:\n")); - let mut ord_m = VecMap::new(); - for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) { - let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new()); - let mut v = vec![]; - try!(f.write_help(&mut v, longest, nlh)); - btm.insert(f.name, v); - } - for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) { - let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new()); - let mut v = vec![]; - try!(o.write_help(&mut v, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); - btm.insert(o.name, v); - } - for (_, btm) in ord_m.into_iter() { - for (_, a) in btm.into_iter() { - // Only valid UTF-8 is supported, so panicing on invalid UTF-8 is ok - try!(write!(w, "{}", unsafe { String::from_utf8_unchecked(a) })); - } - } - } else { - if flags { - let mut ord_m = VecMap::new(); - try!(write!(w, "\nFLAGS:\n")); - for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) { - let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new()); - btm.insert(f.name, f); - } - for (_, btm) in ord_m.into_iter() { - for (_, f) in btm.into_iter() { - try!(f.write_help(w, longest, nlh)); - } - } - } - if opts { - let mut ord_m = VecMap::new(); - try!(write!(w, "\nOPTIONS:\n")); - for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) { - let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new()); - btm.insert(o.name, o); - } - for (_, btm) in ord_m.into_iter() { - for (_, o) in btm.into_iter() { - try!(o.write_help(w, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); - } - } - } - } - if pos { - try!(write!(w, "\nARGS:\n")); - for v in self.positionals.values() - .filter(|p| !p.settings.is_set(ArgSettings::Hidden)) { - try!(v.write_help(w, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); - } - } - if subcmds { - let mut ord_m = VecMap::new(); - try!(write!(w, "\nSUBCOMMANDS:\n")); - for sc in self.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { - let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); - btm.insert(sc.p.meta.name.clone(), sc); - } - for (_, btm) in ord_m.into_iter() { - for (_, sc) in btm.into_iter() { - try!(sc.write_self_help(w, longest_sc, nlh)); - } - } - } - - if let Some(h) = self.meta.more_help { - try!(write!(w, "\n{}", h)); - } - w.flush().map_err(Error::from) + Help::write_parser_help(w, &self) } fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 0471246a..c8d0d9bb 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -1,14 +1,13 @@ // use std::collections::HashSet; use std::fmt::{Display, Formatter, Result}; use std::convert::From; -use std::io; use std::rc::Rc; use std::result::Result as StdResult; use vec_map::VecMap; use Arg; -use args::{AnyArg, HelpWriter, DispOrder}; +use args::{AnyArg, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[derive(Debug)] @@ -48,11 +47,6 @@ impl<'n, 'e> FlagBuilder<'n, 'e> { ..Default::default() } } - - pub fn write_help(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()> { - let hw = HelpWriter::new(self, longest, nlh); - hw.write_to(w) - } } impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 01c05f06..d64441e9 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -1,11 +1,10 @@ use std::rc::Rc; use std::fmt::{Display, Formatter, Result}; use std::result::Result as StdResult; -use std::io; use vec_map::VecMap; -use args::{AnyArg, Arg, HelpWriter, DispOrder}; +use args::{AnyArg, Arg, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[allow(missing_debug_implementations)] @@ -104,11 +103,6 @@ impl<'n, 'e> OptBuilder<'n, 'e> { ob } - pub fn write_help(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> { - let mut hw = HelpWriter::new(self, longest, nlh); - hw.skip_pv = skip_pv; - hw.write_to(w) - } } impl<'n, 'e> Display for OptBuilder<'n, 'e> { diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index c08980f2..ffa039da 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -1,12 +1,11 @@ use std::fmt::{Display, Formatter, Result}; use std::result::Result as StdResult; use std::rc::Rc; -use std::io; use vec_map::VecMap; use Arg; -use args::{AnyArg, HelpWriter, DispOrder}; +use args::{AnyArg, DispOrder}; use args::settings::{ArgFlags, ArgSettings}; #[allow(missing_debug_implementations)] @@ -105,11 +104,6 @@ impl<'n, 'e> PosBuilder<'n, 'e> { pb } - pub fn write_help(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> { - let mut hw = HelpWriter::new(self, longest, nlh); - hw.skip_pv = skip_pv; - hw.write_to(w) - } } impl<'n, 'e> Display for PosBuilder<'n, 'e> { diff --git a/src/args/help_writer.rs b/src/args/help_writer.rs deleted file mode 100644 index ffba459f..00000000 --- a/src/args/help_writer.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::io; -use std::fmt::Display; - -#[cfg(all(feature = "wrap_help", not(target_os = "windows")))] -use unicode_width::UnicodeWidthStr; - -use args::AnyArg; -use args::settings::ArgSettings; -use term; -use strext::_StrExt; - -#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))] -fn str_width(s: &str) -> usize { - s.len() -} - -#[cfg(all(feature = "wrap_help", not(target_os = "windows")))] -fn str_width(s: &str) -> usize { - UnicodeWidthStr::width(s) -} - -const TAB: &'static str = " "; - -pub struct HelpWriter<'a, A> where A: 'a { - a: &'a A, - l: usize, - nlh: bool, - pub skip_pv: bool, - term_w: Option, -} - -impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display { - pub fn new(a: &'a A, l: usize, nlh: bool) -> Self { - HelpWriter { - a: a, - l: l, - nlh: nlh, - skip_pv: false, - term_w: term::dimensions().map(|(w, _)| w), - } - } - pub fn write_to(&self, w: &mut W) -> io::Result<()> { - debugln!("fn=write_to;"); - try!(self.short(w)); - try!(self.long(w)); - try!(self.val(w)); - try!(self.help(w)); - write!(w, "\n") - } - - fn short(&self, w: &mut W) -> io::Result<()> - where W: io::Write - { - debugln!("fn=short;"); - try!(write!(w, "{}", TAB)); - if let Some(s) = self.a.short() { - write!(w, "-{}", s) - } else if self.a.has_switch() { - write!(w, "{}", TAB) - } else { - Ok(()) - } - } - - fn long(&self, w: &mut W) -> io::Result<()> - where W: io::Write - { - debugln!("fn=long;"); - if !self.a.has_switch() { - return Ok(()); - } - if self.a.takes_value() { - if let Some(l) = self.a.long() { - try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l)); - } - try!(write!(w, " ")); - } else { - if let Some(l) = self.a.long() { - try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l)); - if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) { - write_spaces!((self.l + 4) - (l.len() + 2), w); - } - } else { - if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) { - // 6 is tab (4) + -- (2) - write_spaces!((self.l + 6), w); - } - } - } - Ok(()) - } - - fn val(&self, w: &mut W) -> io::Result<()> - where W: io::Write - { - debugln!("fn=val;"); - if !self.a.takes_value() { - return Ok(()); - } - if let Some(ref vec) = self.a.val_names() { - let mut it = vec.iter().peekable(); - while let Some((_, val)) = it.next() { - try!(write!(w, "<{}>", val)); - if it.peek().is_some() { try!(write!(w, " ")); } - } - let num = vec.len(); - if self.a.is_set(ArgSettings::Multiple) && num == 1 { - try!(write!(w, "...")); - } - } else if let Some(num) = self.a.num_vals() { - let mut it = (0..num).peekable(); - while let Some(_) = it.next() { - try!(write!(w, "<{}>", self.a.name())); - if it.peek().is_some() { try!(write!(w, " ")); } - } - } else if self.a.has_switch() { - try!(write!(w, "<{}>", self.a.name())); - } else { - try!(write!(w, "{}", self.a)); - } - if self.a.has_switch() { - if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) { - let self_len = self.a.to_string().len(); - // subtract ourself - let mut spcs = self.l - self_len; - // Since we're writing spaces from the tab point we first need to know if we - // had a long and short, or just short - if self.a.long().is_some() { - // Only account 4 after the val - spcs += 4; - } else { - // Only account for ', --' + 4 after the val - spcs += 8; - } - write_spaces!(spcs, w); - } - } else if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) { - write_spaces!(self.l + 4 - (self.a.to_string().len()), w); - } - Ok(()) - } - - fn help(&self, w: &mut W) -> io::Result<()> - where W: io::Write - { - debugln!("fn=help;"); - let spec_vals = self.spec_vals(); - let mut help = String::new(); - let h = self.a.help().unwrap_or(""); - let spcs = if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) { - 8 // "tab" + "tab" - } else { - self.l + 12 - }; - // determine if our help fits or needs to wrap - let width = self.term_w.unwrap_or(0); - debugln!("Term width...{}", width); - let too_long = self.term_w.is_some() && (spcs + str_width(h) + str_width(&*spec_vals) >= width); - debugln!("Too long...{:?}", too_long); - - // Is help on next line, if so newline + 2x tab - if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) { - try!(write!(w, "\n{}{}", TAB, TAB)); - } - - debug!("Too long..."); - if too_long { - sdebugln!("Yes"); - help.push_str(h); - help.push_str(&*spec_vals); - debugln!("help: {}", help); - debugln!("help width: {}", str_width(&*help)); - // Determine how many newlines we need to insert - let avail_chars = width - spcs; - debugln!("Usable space: {}", avail_chars); - let longest_w = { - let mut lw = 0; - for l in help.split(' ').map(|s| str_width(s)) { - if l > lw { - lw = l; - } - } - lw - }; - debugln!("Longest word...{}", longest_w); - debug!("Enough space to wrap..."); - if longest_w < avail_chars { - sdebugln!("Yes"); - let mut indices = vec![]; - let mut idx = 0; - loop { - idx += avail_chars - 1; - if idx >= help.len() { break; } - // 'a' arbitrary non space char - if help.chars().nth(idx).unwrap_or('a') != ' ' { - idx = find_idx_of_space(&*help, idx); - } - debugln!("Adding idx: {}", idx); - debugln!("At {}: {:?}", idx, help.chars().nth(idx)); - indices.push(idx); - if str_width(&help[idx..]) <= avail_chars { - break; - } - } - for (i, idx) in indices.iter().enumerate() { - debugln!("iter;i={},idx={}", i, idx); - let j = idx + (2 * i); - debugln!("removing: {}", j); - debugln!("at {}: {:?}", j, help.chars().nth(j)); - help.remove(j); - help.insert(j, '{'); - help.insert(j + 1 , 'n'); - help.insert(j + 2, '}'); - } - } else { - sdebugln!("No"); - } - } else { sdebugln!("No"); } - let help = if !help.is_empty() { - &*help - } else if !spec_vals.is_empty() { - help.push_str(h); - help.push_str(&*spec_vals); - &*help - } else { - h - }; - if help.contains("{n}") { - if let Some(part) = help.split("{n}").next() { - try!(write!(w, "{}", part)); - } - for part in help.split("{n}").skip(1) { - try!(write!(w, "\n")); - if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) { - try!(write!(w, "{}{}", TAB, TAB)); - } else if self.a.has_switch() { - write_spaces!(self.l + 12, w); - } else { - write_spaces!(self.l + 8, w); - } - try!(write!(w, "{}", part)); - } - } else { - try!(write!(w, "{}", help)); - } - Ok(()) - } - - fn spec_vals(&self) -> String { - debugln!("fn=spec_vals;"); - if let Some(ref pv) = self.a.default_val() { - debugln!("Writing defaults"); - return format!(" [default: {}] {}", pv, - if !self.skip_pv { - if let Some(ref pv) = self.a.possible_vals() { - format!(" [values: {}]", pv.join(", ")) - } else { "".into() } - } else { "".into() } - ); - } else if !self.skip_pv { - debugln!("Writing values"); - if let Some(ref pv) = self.a.possible_vals() { - debugln!("Possible vals...{:?}", pv); - return format!(" [values: {}]", pv.join(", ")); - } - } - String::new() - } -} - -fn find_idx_of_space(full: &str, mut start: usize) -> usize { - debugln!("fn=find_idx_of_space;"); - let haystack = if full._is_char_boundary(start) { - &full[..start] - } else { - while !full._is_char_boundary(start) { start -= 1; } - &full[..start] - }; - debugln!("haystack: {}", haystack); - for (i, c) in haystack.chars().rev().enumerate() { - debugln!("iter;c={},i={}", c, i); - if c == ' ' { - debugln!("Found space returning start-i...{}", start - (i + 1)); - return start - (i + 1); - } - } - 0 -} diff --git a/src/args/mod.rs b/src/args/mod.rs index 38cffd8d..2a55f3ed 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -7,7 +7,6 @@ pub use self::matched_arg::MatchedArg; pub use self::group::ArgGroup; pub use self::any_arg::{AnyArg, DispOrder}; pub use self::settings::ArgSettings; -pub use self::help_writer::HelpWriter; mod arg; pub mod any_arg; @@ -19,4 +18,3 @@ mod matched_arg; mod group; #[allow(dead_code)] pub mod settings; -mod help_writer; diff --git a/tests/new_help.rs b/tests/new_help.rs deleted file mode 100644 index 7a9bb043..00000000 --- a/tests/new_help.rs +++ /dev/null @@ -1,317 +0,0 @@ -extern crate clap; - -use std::io::Cursor; - -use clap::App; -use clap::{Arg, SubCommand}; - -static EXAMPLE1_TMPL_S : &'static str = include_str!("example1_tmpl_simple.txt"); -static EXAMPLE1_TMPS_F : &'static str = include_str!("example1_tmpl_full.txt"); - -fn build_old_help(app: &App) -> String { - let mut buf = Cursor::new(Vec::with_capacity(50)); - app.write_help(&mut buf).unwrap(); - let content = buf.into_inner(); - String::from_utf8(content).unwrap() -} - -fn build_new_help(app: &App) -> String { - let mut buf = Cursor::new(Vec::with_capacity(50)); - app.write_new_help(&mut buf).unwrap(); - let content = buf.into_inner(); - String::from_utf8(content).unwrap() -} - -fn compare(app: &App) -> bool { - let hlp1f = build_old_help(&app); - let hlp1 = hlp1f.trim(); - let hlp2f = build_new_help(&app); - let hlp2 = hlp2f.trim(); - let b = hlp1 == hlp2; - if !b { - println!(""); - println!("--> old"); - println!("{}", hlp1); - println!("--> new"); - println!("{}", hlp2); - println!("--") - } - b -} - -fn compare2(app1: &App, app2: &App) -> bool { - let hlp1f = build_new_help(&app1); - let hlp1 = hlp1f.trim(); - let hlp2f = build_new_help(&app2); - let hlp2 = hlp2f.trim(); - let b = hlp1 == hlp2; - if !b { - println!(""); - println!("--> hlp1"); - println!("{}", hlp1); - println!("--> hlp2"); - println!("{}", hlp2); - println!("--") - } - b -} - -#[test] -fn comparison_with_old_help() { - assert!(compare(&example1())); - assert!(compare(&example2())); - assert!(compare(&example3())); - assert!(compare(&example4())); - assert!(compare(&example5())); - assert!(compare(&example6())); - assert!(compare(&example7())); - assert!(compare(&example8())); - assert!(compare(&example10())); -} - -#[test] -fn comparison_with_template() { - assert!(compare2(&example1(), &example1().template(EXAMPLE1_TMPL_S))); - assert!(compare2(&example1(), &example1().template(EXAMPLE1_TMPS_F))); -} - -#[test] -fn template_empty() { - let app = App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") - .template(""); - assert_eq!(build_new_help(&app), ""); -} - -#[test] -fn template_notag() { - let app = App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") - .template(" no tag "); - assert_eq!(build_new_help(&app), " no tag "); -} - -#[test] -fn template_unknowntag() { - let app = App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") - .template(" {unknown_tag} "); - assert_eq!(build_new_help(&app), " {unknown_tag} "); -} - -#[test] -fn template_author_version() { - let app = App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") - .template("{author}\n{version}\n{about}\n{bin}"); - assert_eq!(build_new_help(&app), "Kevin K. \n1.0\nDoes awesome things\nMyApp"); -} - -fn example1<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") - .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' - 'Sets an optional output file' - -d... 'Turn debugging information on'") - .subcommand(SubCommand::with_name("test") - .about("does testing things") - .arg_from_usage("-l, --list 'lists test values'")) -} - -fn example2<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - .version("1.0") - .author("Kevin K. ") - .about("Does awesome things") -} - -fn example3<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // All application settings go here... - - // A simple "Flag" argument example (i.e. "-d") using the builder pattern - .arg(Arg::with_name("debug") - .help("turn on debugging information") - .short("d")) - - // Two arguments, one "Option" argument (i.e. one that takes a value) such - // as "-c some", and one positional argument (i.e. "myapp some_file") - .args(&[ - Arg::with_name("config") - .help("sets the config file to use") - .takes_value(true) - .short("c") - .long("config"), - Arg::with_name("input") - .help("the input file to use") - .index(1) - .required(true) - ]) - - // *Note* the following two examples are convienience methods, if you wish - // to still get the full configurability of Arg::with_name() and the readability - // of arg_from_usage(), you can instantiate a new Arg with Arg::from_usage() and - // still be able to set all the additional properties, just like Arg::with_name() - // - // - // One "Flag" using a usage string - .arg_from_usage("--license 'display the license file'") - - // Two args, one "Positional", and one "Option" using a usage string - .args_from_usage("[output] 'Supply an output file to use' - -i, --int=[IFACE] 'Set an interface to use'") -} - -fn example4<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - .about("Parses an input file to do awesome things") - .version("1.0") - .author("Kevin K. ") - .arg(Arg::with_name("debug") - .help("turn on debugging information") - .short("d") - .long("debug")) - .arg(Arg::with_name("config") - .help("sets the config file to use") - .short("c") - .long("config")) - .arg(Arg::with_name("input") - .help("the input file to use") - .index(1) - .required(true)) -} - -fn example5<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // Regular App configuration goes here... - - // We'll add a flag that represents an awesome meter... - // - // I'll explain each possible setting that "flags" accept. Keep in mind - // that you DO NOT need to set each of these for every flag, only the ones - // you want for your individual case. - .arg(Arg::with_name("awesome") - .help("turns up the awesome") // Displayed when showing help info - .short("a") // Trigger this arg with "-a" - .long("awesome") // Trigger this arg with "--awesome" - .multiple(true) // This flag should allow multiple - // occurrences such as "-aaa" or "-a -a" - .requires("config") // Says, "If the user uses -a, they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a mutually_excludes_all(Vec<&str>) - ) -} - -fn example6<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // Regular App configuration goes here... - - // We'll add two positional arguments, a input file, and a config file. - // - // I'll explain each possible setting that "positionals" accept. Keep in - // mind that you DO NOT need to set each of these for every flag, only the - // ones that apply to your individual case. - .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .index(1) // Set the order in which the user must - // specify this argument (Starts at 1) - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a mutually_excludes_all(Vec<&str>) - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - ) - .arg(Arg::with_name("config") - .help("the config file to use") - .index(2)) // Note, we do not need to specify required(true) - // if we don't want to, because "input" already - // requires "config" - // Note, we also do not need to specify requires("input") - // because requires lists are automatically two-way -} - -fn example7<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // Regular App configuration goes here... - - // Assume we an application that accepts an input file via the "-i file" - // or the "--input file" (as wel as "--input=file"). - // Below every setting supported by option arguments is discussed. - // NOTE: You DO NOT need to specify each setting, only those which apply - // to your particular case. - .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .takes_value(true) // MUST be set to true in order to be an "option" argument - .short("i") // This argument is triggered with "-i" - .long("input") // This argument is triggered with "--input" - .multiple(true) // Set to true if you wish to allow multiple occurrences - // such as "-i file -i other_file -i third_file" - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a conflicts_with_all(Vec<&str>) - ) -} - -fn example8<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // Regular App configuration goes here... - - // Assume we an application that accepts an input file via the "-i file" - // or the "--input file" (as wel as "--input=file"). - // Below every setting supported by option arguments is discussed. - // NOTE: You DO NOT need to specify each setting, only those which apply - // to your particular case. - .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .takes_value(true) // MUST be set to true in order to be an "option" argument - .short("i") // This argument is triggered with "-i" - .long("input") // This argument is triggered with "--input" - .multiple(true) // Set to true if you wish to allow multiple occurrences - // such as "-i file -i other_file -i third_file" - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a conflicts_with_all(Vec<&str>) - ) -} - -fn example10<'b, 'c>() -> App<'b, 'c> { - App::new("myapp") - .about("does awesome things") - .arg(Arg::with_name("CONFIG") - .help("The config file to use (default is \"config.json\")") - .short("c") - .takes_value(true)) -} diff --git a/tests/template_help.rs b/tests/template_help.rs new file mode 100644 index 00000000..313479c3 --- /dev/null +++ b/tests/template_help.rs @@ -0,0 +1,91 @@ +extern crate clap; + +use std::io::Cursor; + +use clap::{App, SubCommand}; + +static EXAMPLE1_TMPL_S : &'static str = include_str!("example1_tmpl_simple.txt"); +static EXAMPLE1_TMPS_F : &'static str = include_str!("example1_tmpl_full.txt"); + +fn build_new_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn compare2(app1: &App, app2: &App) -> bool { + let hlp1f = build_new_help(&app1); + let hlp1 = hlp1f.trim(); + let hlp2f = build_new_help(&app2); + let hlp2 = hlp2f.trim(); + let b = hlp1 == hlp2; + if !b { + println!(""); + println!("--> hlp1"); + println!("{}", hlp1); + println!("--> hlp2"); + println!("{}", hlp2); + println!("--") + } + b +} + +#[test] +fn comparison_with_template() { + assert!(compare2(&app_example1(), &app_example1().template(EXAMPLE1_TMPL_S))); + assert!(compare2(&app_example1(), &app_example1().template(EXAMPLE1_TMPS_F))); +} + +#[test] +fn template_empty() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(""); + assert_eq!(build_new_help(&app), ""); +} + +#[test] +fn template_notag() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(" no tag "); + assert_eq!(build_new_help(&app), " no tag "); +} + +#[test] +fn template_unknowntag() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template(" {unknown_tag} "); + assert_eq!(build_new_help(&app), " {unknown_tag} "); +} + +#[test] +fn template_author_version() { + let app = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .template("{author}\n{version}\n{about}\n{bin}"); + assert_eq!(build_new_help(&app), "Kevin K. \n1.0\nDoes awesome things\nMyApp"); +} + +fn app_example1<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' + 'Sets an optional output file' + -d... 'Turn debugging information on'") + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) +}