diff --git a/benches/04_new_help.rs b/benches/04_new_help.rs index f1390d8f696..5b90c77b806 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 69680fcb99d..b659d0424ed 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) } -} - -fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { - x + /// 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(()) + } } -// 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")); + if !first { + try!(self.writer.write(b"\n")); + } + try!(write!(self.writer, "SUBCOMMANDS:\n")); + try!(self.write_subcommands(&parser)); + } - let mut longest = 0; + Ok(()) + } - 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()); - } + /// 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; - for (_, btm) in ord_m.into_iter() { - for (_, sc) in btm.into_iter() { - try!(self.write_arg(sc, longest)); - } - } + 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 04626e0bed9..c49f46379bf 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 43116350ec3..460739d5619 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 00000000000..6ae57fa5ce2 --- /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 00000000000..af6c4b07632 --- /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 2a8192dd4b1..7a9bb043c8d 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)); -}