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)); +}