diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b0d75d0..2d7d4395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ + +### v2.5.2 (2016-05-31) + + +#### Improvements + +* removes extra newline from help output ([86e61d19](https://github.com/kbknapp/clap-rs/commit/86e61d19a748fb9870fcf1175308984e51ca1115)) +* allows printing version to any io::Write object ([921f5f79](https://github.com/kbknapp/clap-rs/commit/921f5f7916597f1d028cd4a65bfe76a01c801724)) +* removes extra newline when printing version ([7e2e2cbb](https://github.com/kbknapp/clap-rs/commit/7e2e2cbb4a8a0f050bb8072a376f742fc54b8589)) + +#### Bug Fixes + +* fixes bug where args are printed out of order with templates ([3935431d](https://github.com/kbknapp/clap-rs/commit/3935431d5633f577c0826ae2142794b301f4b8ca)) +* fixes bug where one can't override version or help flags ([90d7d6a2](https://github.com/kbknapp/clap-rs/commit/90d7d6a2ea8240122dd9bf8d82d3c4f5ebb5c703), closes [#514](https://github.com/kbknapp/clap-rs/issues/514)) +* fixes issue where before_help wasn't printed ([b3faff60](https://github.com/kbknapp/clap-rs/commit/b3faff6030f76a23f26afcfa6a90169002ed7106)) + +#### Documentation + +* inter-links all types and pages ([3312893d](https://github.com/kbknapp/clap-rs/commit/3312893ddaef3f44d68d8d26ed3d08010be50d97), closes [#505](https://github.com/kbknapp/clap-rs/issues/505)) +* makes all publicly available types viewable in docs ([52ca6505](https://github.com/kbknapp/clap-rs/commit/52ca6505b4fec7b5c2d53d160c072d395eb21da6)) + ### v2.5.1 (2016-05-11) @@ -6,8 +27,6 @@ * **Subcommand Aliases**: fixes lifetime issue when setting multiple aliases at once ([ac42f6cf0](https://github.com/kbknapp/clap-rs/commit/ac42f6cf0de6c4920f703807d63061803930b18d)) - - ## v2.5.0 (2016-05-10) diff --git a/Cargo.toml b/Cargo.toml index 76e1e723..a94b8f42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.5.1" +version = "2.5.2" authors = ["Kevin K. "] exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] description = "A simple to use, efficient, and full featured Command Line Argument Parser" diff --git a/README.md b/README.md index 62cce930..b2dbdf3a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,16 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New +Here's what's new in v.2.5.2 + +* Removes trailing newlines from help and version output +* Allows printing version to any io::Write object +* Inter-links all types and pages +* Makes all publicly available types viewable in docs +* Fixes bug where one can't override version or help flags +* Fixes bug where args are printed out of order when using templates +* Fixes issue where `App::before_help` wasn't printed properly + Here's what's new in v.2.5.0 * Subcommands now support aliases - think of them as hidden subcommands that dispatch to said subcommand automatically @@ -50,12 +60,12 @@ Here's what's new in v2.4.3 * Improvements * Positional arguments which are part of a group are now formatted in a more readable way (fewer brackets) * Positional arguments use the standard `<>` brackets to reduce confusion - * The default help string for the `help` subcommand has been shortened to fit in 80 columns + * The default help string for the `help` subcommand has been shortened to fit in 80 columns Here's the highlights from v2.4.0 -* **Before Help:** adds support for displaying info before help message -* **Required Unless:** adds support for allowing args that are required unless certain other args are present +* **Before Help:** adds support for displaying info before help message +* **Required Unless:** adds support for allowing args that are required unless certain other args are present * Bug fixes Here's the highlights from v2.3.0 diff --git a/clap-test.rs b/clap-test.rs index d2becbe3..e3425d60 100644 --- a/clap-test.rs +++ b/clap-test.rs @@ -60,6 +60,16 @@ mod test { assert_eq!(str::from_utf8(&help).unwrap(), out); } + pub fn check_version(mut a: App, out: &str) { + // We call a get_matches method to cause --help and --version to be built + let _ = a.get_matches_from_safe_borrow(vec![""]); + + // Now we check the output of print_version() + let mut ver = vec![]; + a.write_version(&mut ver).ok().expect("failed to print help"); + assert_eq!(str::from_utf8(&ver).unwrap(), out); + } + pub fn check_complex_output(args: &str, out: &str) { let mut w = vec![]; let matches = complex_app().get_matches_from(args.split(' ').collect::>()); diff --git a/src/app/help.rs b/src/app/help.rs index 7cbc2d81..cdf90f16 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -88,6 +88,7 @@ pub struct Help<'a> { impl<'a> Help<'a> { /// Create a new `Help` instance. pub fn new(w: &'a mut Write, next_line_help: bool, hide_pv: bool, color: bool) -> Self { + debugln!("fn=Help::new;"); Help { writer: w, next_line_help: next_line_help, @@ -100,12 +101,14 @@ 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<()> { + debugln!("fn=Help::write_app_help;"); 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<()> { + debugln!("fn=Help::write_parser_help;"); let nlh = parser.is_set(AppSettings::NextLineHelp); let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); let color = parser.is_set(AppSettings::ColoredHelp); @@ -114,8 +117,9 @@ impl<'a> Help<'a> { /// Writes the parser help to the wrapped stream. pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { + debugln!("fn=Help::write_help;"); if let Some(h) = parser.meta.help_str { - try!(writeln!(self.writer, "{}", h).map_err(Error::from)); + try!(write!(self.writer, "{}", h).map_err(Error::from)); } else if let Some(ref tmpl) = parser.meta.template { try!(self.write_templated_help(&parser, tmpl)); } else { @@ -131,6 +135,7 @@ impl<'a> Help<'a> { fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> where I: Iterator> { + debugln!("fn=write_args_unsorted;"); let mut longest = 0; let mut arg_v = Vec::with_capacity(10); for arg in args.filter(|arg| { @@ -143,7 +148,13 @@ impl<'a> Help<'a> { arg_v.push(arg) } } + let mut first = true; for arg in arg_v { + if !first { + try!(self.writer.write(b"\n")); + } else { + first = false; + }; try!(self.write_arg(arg.as_base(), longest)); } Ok(()) @@ -153,6 +164,7 @@ impl<'a> Help<'a> { fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> where I: Iterator> { + debugln!("fn=write_args;"); let mut longest = 0; let mut ord_m = VecMap::new(); for arg in args.filter(|arg| { @@ -166,8 +178,14 @@ impl<'a> Help<'a> { btm.insert(arg.name(), arg); } } + let mut first = true; for (_, btm) in ord_m.into_iter() { for (_, arg) in btm.into_iter() { + if !first { + try!(self.writer.write(b"\n")); + } else { + first = false; + } try!(self.write_arg(arg.as_base(), longest)); } } @@ -179,12 +197,11 @@ impl<'a> Help<'a> { arg: &ArgWithDisplay<'b, 'c>, longest: usize) -> io::Result<()> { - debugln!("fn=write_to;"); + debugln!("fn=write_arg;"); 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(()) } @@ -458,45 +475,33 @@ 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!(color!(self, "OPTIONS:\n", Warning)); try!(self.write_args(opts_flags)); - first = false; } else { if flags { try!(color!(self, "FLAGS:\n", Warning)); try!(self.write_args(parser.iter_flags() .map(as_arg_trait))); - first = false; } if opts { - if !first { - try!(self.writer.write(b"\n")); - } + try!(self.writer.write(b"\n\n")); try!(color!(self, "OPTIONS:\n", Warning)); try!(self.write_args(parser.iter_opts().map(as_arg_trait))); - first = false; } } if pos { - if !first { - try!(self.writer.write(b"\n")); - } + try!(self.writer.write(b"\n\n")); try!(color!(self, "ARGS:\n", Warning)); try!(self.write_args_unsorted(parser.iter_positionals().map(as_arg_trait))); - first = false; } if subcmds { - if !first { - try!(self.writer.write(b"\n")); - } + try!(self.writer.write(b"\n\n")); try!(color!(self, "SUBCOMMANDS:\n", Warning)); try!(self.write_subcommands(&parser)); } @@ -506,6 +511,7 @@ impl<'a> Help<'a> { /// Writes help for subcommands of a Parser Object to the wrapped stream. fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { + debugln!("exec=write_subcommands;"); let mut longest = 0; let mut ord_m = VecMap::new(); @@ -515,8 +521,16 @@ impl<'a> Help<'a> { longest = cmp::max(longest, sc.p.meta.name.len()); } + let mut first = true; for (_, btm) in ord_m.into_iter() { for (_, sc) in btm.into_iter() { + if !first { + debugln!("Writing newline..."); + try!(self.writer.write(b"\n")); + } else { + first = false; + } + debugln!("Writing sc...{}", sc); try!(self.write_arg(sc, longest)); } } @@ -546,6 +560,11 @@ impl<'a> Help<'a> { /// Writes default help for a Parser Object to the wrapped stream. pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { + debugln!("fn=write_default_help;"); + if let Some(h) = parser.meta.pre_help { + try!(write!(self.writer, "{}", h)); + try!(self.writer.write(b"\n\n")); + } // Print the version try!(self.write_bin_name(&parser)); @@ -575,7 +594,10 @@ impl<'a> Help<'a> { } if let Some(h) = parser.meta.more_help { - try!(write!(self.writer, "{}\n", h)); + if flags || opts || pos || subcmds { + try!(self.writer.write(b"\n\n")); + } + try!(write!(self.writer, "{}", h)); } self.writer.flush().map_err(Error::from) @@ -708,6 +730,7 @@ impl<'a> Help<'a> { /// 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<()> { + debugln!("fn=write_templated_help;"); let mut tmplr = Cursor::new(&template); let mut tag_buf = Cursor::new(vec![0u8; 15]); @@ -725,6 +748,12 @@ impl<'a> Help<'a> { _ => continue, }; + debugln!("iter;tag_buf={};", unsafe { + String::from_utf8_unchecked(tag_buf.get_ref()[0..tag_length] + .iter() + .map(|&i|i) + .collect::>()) + }); match &tag_buf.get_ref()[0..tag_length] { b"?" => { try!(self.writer.write(b"Could not decode tag name")); @@ -768,8 +797,8 @@ impl<'a> Help<'a> { .map(as_arg_trait))); } b"positionals" => { - try!(self.write_args(parser.iter_positionals() - .map(as_arg_trait))); + try!(self.write_args_unsorted(parser.iter_positionals() + .map(as_arg_trait))); } b"subcommands" => { try!(self.write_subcommands(&parser)); @@ -791,7 +820,6 @@ impl<'a> Help<'a> { try!(self.writer.write(b"}")); } } - } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 87c2888c..283c8791 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -796,6 +796,22 @@ impl<'a, 'b> App<'a, 'b> { Help::write_app_help(w, &self) } + /// Writes the version message to the user to a [`io::Write`] object + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_version(&mut out).ok().expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + pub fn write_version(&self, w: &mut W) -> ClapResult<()> { + self.p.write_version(w).map_err(From::from) + } + /// 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 all user /// provided arguments from [`env::args_os`] in order to allow for invalid UTF-8 code points, diff --git a/src/app/parser.rs b/src/app/parser.rs index ecdaaa85..20e4791a 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -115,9 +115,9 @@ impl<'a, 'b> Parser<'a, 'b> l)); self.long_list.push(l); if l == "help" { - self.set(AppSettings::NeedsLongHelp); + self.unset(AppSettings::NeedsLongHelp); } else if l == "version" { - self.set(AppSettings::NeedsLongVersion); + self.unset(AppSettings::NeedsLongVersion); } } if a.is_set(ArgSettings::Required) { @@ -378,6 +378,10 @@ impl<'a, 'b> Parser<'a, 'b> self.settings.set(s) } + pub fn unset(&mut self, s: AppSettings) { + self.settings.unset(s) + } + pub fn verify_positionals(&mut self) { // Because you must wait until all arguments have been supplied, this is the first chance // to make assertions on positional argument indexes @@ -929,13 +933,13 @@ impl<'a, 'b> Parser<'a, 'b> debug!("Checking if -{} is help or version...", arg); if let Some(h) = self.help_short { sdebugln!("Help"); - if arg == h { + if arg == h && self.settings.is_set(AppSettings::NeedsLongHelp) { try!(self._help()); } } if let Some(v) = self.version_short { sdebugln!("Help"); - if arg == v { + if arg == v && self.settings.is_set(AppSettings::NeedsLongVersion) { try!(self._version()); } } @@ -1542,22 +1546,22 @@ impl<'a, 'b> Parser<'a, 'b> w.flush().map_err(Error::from) } - fn write_version(&self, w: &mut W) -> io::Result<()> { + pub fn write_version(&self, w: &mut W) -> io::Result<()> { if let Some(bn) = self.meta.bin_name.as_ref() { if bn.contains(' ') { // Incase we're dealing with subcommands i.e. git mv is translated to git-mv - writeln!(w, + write!(w, "{} {}", bn.replace(" ", "-"), self.meta.version.unwrap_or("".into())) } else { - writeln!(w, + write!(w, "{} {}", &self.meta.name[..], self.meta.version.unwrap_or("".into())) } } else { - writeln!(w, + write!(w, "{} {}", &self.meta.name[..], self.meta.version.unwrap_or("".into())) @@ -1570,7 +1574,6 @@ impl<'a, 'b> Parser<'a, 'b> self.write_help(&mut buf_w) } - #[cfg_attr(feature = "lints", allow(for_kv_map))] pub fn write_help(&self, w: &mut W) -> ClapResult<()> { Help::write_parser_help(w, &self) } diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 54df0c15..7acbbe4b 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -102,7 +102,7 @@ OPTIONS: -V, --version Prints version information ARGS: - some pos arg\n")); + some pos arg")); } #[test] @@ -135,5 +135,5 @@ OPTIONS: -o, --opt some option ARGS: - some pos arg\n")); + some pos arg")); } diff --git a/tests/help.rs b/tests/help.rs index 41ae717d..30cce9a8 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -34,8 +34,21 @@ ARGS: SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) - subcmd tests subcommands -"; + subcmd tests subcommands"; + +static AFTER_HELP: &'static str = "some text that comes before the help + +clap-test v1.4.8 +tests clap library + +USAGE: + clap-test [FLAGS] + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +some text that comes after the help"; static SC_HELP: &'static str = "subcmd 0.1 Kevin K. @@ -51,8 +64,7 @@ OPTIONS: -o, --option ... tests options ARGS: - tests positionals -"; + tests positionals"; #[test] fn help_short() { @@ -137,6 +149,16 @@ fn complex_help_output() { test::check_help(test::complex_app(), HELP); } +#[test] +fn after_and_before_help_output() { + let app = App::new("clap-test") + .version("v1.4.8") + .about("tests clap library") + .before_help("some text that comes before the help") + .after_help("some text that comes after the help"); + test::check_help(app, AFTER_HELP); +} + #[test] fn complex_subcommand_help_output() { let mut a = test::complex_app(); diff --git a/tests/hidden_args.rs b/tests/hidden_args.rs index 91eced4e..cf52e485 100644 --- a/tests/hidden_args.rs +++ b/tests/hidden_args.rs @@ -30,5 +30,5 @@ FLAGS: -V, --version Prints version information OPTIONS: - --option some option\n")); + --option some option")); } diff --git a/tests/template_help.rs b/tests/template_help.rs index 313479c3..420111e1 100644 --- a/tests/template_help.rs +++ b/tests/template_help.rs @@ -7,6 +7,41 @@ 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"); +static CUSTOM_TEMPL_HELP: &'static str = "MyApp 1.0 +Kevin K. +Does awesome things + +USAGE: + MyApp [FLAGS] [OPTIONS] [SUBCOMMAND] + +FLAGS: + -d Turn debugging information on +OPTIONS: + -c, --config Sets a custom config file +ARGS: + Sets an optional output file +SUBCOMMANDS: + test does testing things"; + +static SIMPLE_TEMPLATE: &'static str = "MyApp 1.0 +Kevin K. +Does awesome things + +USAGE: + MyApp [FLAGS] [OPTIONS] [SUBCOMMAND] + +FLAGS: + -d Turn debugging information on + +OPTIONS: + -c, --config Sets a custom config file + +ARGS: + Sets an optional output file + +SUBCOMMANDS: + test does testing things"; + fn build_new_help(app: &App) -> String { let mut buf = Cursor::new(Vec::with_capacity(50)); app.write_help(&mut buf).unwrap(); @@ -14,27 +49,29 @@ fn build_new_help(app: &App) -> String { 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; +fn compare_app_str(l: &App, right: &str) -> bool { + let left = build_new_help(&l); + let b = left.trim() == right; if !b { println!(""); - println!("--> hlp1"); - println!("{}", hlp1); - println!("--> hlp2"); - println!("{}", hlp2); + println!("--> left"); + println!("{}", left); + println!("--> right"); + println!("{}", right); 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))); +fn with_template() { + assert!(compare_app_str(&app_example1().template(EXAMPLE1_TMPL_S), SIMPLE_TEMPLATE)); +} + +#[test] +fn custom_template() { + let app = app_example1().template(EXAMPLE1_TMPS_F); + assert!(compare_app_str(&app, CUSTOM_TEMPL_HELP)); } #[test] @@ -44,7 +81,7 @@ fn template_empty() { .author("Kevin K. ") .about("Does awesome things") .template(""); - assert_eq!(build_new_help(&app), ""); + assert!(compare_app_str(&app, "")); } #[test] @@ -53,8 +90,8 @@ fn template_notag() { .version("1.0") .author("Kevin K. ") .about("Does awesome things") - .template(" no tag "); - assert_eq!(build_new_help(&app), " no tag "); + .template("test no tag test"); + assert!(compare_app_str(&app, "test no tag test")); } #[test] @@ -63,8 +100,8 @@ fn template_unknowntag() { .version("1.0") .author("Kevin K. ") .about("Does awesome things") - .template(" {unknown_tag} "); - assert_eq!(build_new_help(&app), " {unknown_tag} "); + .template("test {unknown_tag} test"); + assert!(compare_app_str(&app, "test {unknown_tag} test")); } #[test] @@ -74,7 +111,7 @@ fn template_author_version() { .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"); + assert!(compare_app_str(&app, "Kevin K. \n1.0\nDoes awesome things\nMyApp")); } fn app_example1<'b, 'c>() -> App<'b, 'c> { @@ -83,8 +120,8 @@ fn app_example1<'b, 'c>() -> App<'b, 'c> { .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'") + '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'")) diff --git a/tests/version.rs b/tests/version.rs index c5fc9154..c51a55ee 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -1,7 +1,12 @@ extern crate clap; +extern crate regex; use clap::{App, ErrorKind}; +include!("../clap-test.rs"); + +static VERSION: &'static str = "clap-test v1.4.8"; + #[test] fn version_short() { let m = App::new("test") @@ -25,3 +30,8 @@ fn version_long() { assert!(m.is_err()); assert_eq!(m.unwrap_err().kind, ErrorKind::VersionDisplayed); } + +#[test] +fn complex_version_output() { + test::check_version(test::complex_app(), VERSION); +}