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.
This commit is contained in:
Hernan Grecco 2016-04-03 01:20:55 -03:00
parent 04b5b074d1
commit 81e121edd6
7 changed files with 468 additions and 89 deletions

View file

@ -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. <kbknapp@gmail.com>
Parses an input file to do awesome things
USAGE:
test [FLAGS] <input>
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:
<input> 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));
}

View file

@ -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<usize>,
}
// 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<Item = &'d ArgWithOrder<'b, 'c>>
{
@ -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<Item = &'d ArgWithOrder<'b, 'c>>
{
@ -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: Read, W: Write>(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: Read, W: Write>(r: &mut R,
w: &mut W,
tag_buffer: &mut Cursor<Vec<u8>>)
-> Option<io::Result<usize>> {
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;");

View file

@ -11,6 +11,7 @@ pub struct AppMeta<'b> {
pub usage: Option<String>,
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,
}
}
}

View file

@ -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<S: Into<&'b str>>(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<W: Write>(&self, w: &mut W) -> ClapResult<()> {
Help::write_app_help(w, &self)

View file

@ -0,0 +1,15 @@
{bin} {version}
{author}
{about}
USAGE:
{usage}
FLAGS:
{flags}
OPTIONS:
{options}
ARGS:
{positionals}
SUBCOMMANDS:
{subcommands}

View file

@ -0,0 +1,8 @@
{bin} {version}
{author}
{about}
USAGE:
{usage}
{all-args}

View file

@ -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. <kbknapp@gmail.com>")
.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. <kbknapp@gmail.com>")
.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. <kbknapp@gmail.com>")
.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. <kbknapp@gmail.com>")
.about("Does awesome things")
.template("{author}\n{version}\n{about}\n{bin}");
assert_eq!(build_new_help(&app), "Kevin K. <kbknapp@gmail.com>\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));
}