Auto merge of #478 - hgrecco:template, r=kbknapp

A new Help Engine with templating capabilities

This set of commits brings a new Help System to CLAP.

Major changes are:
- The help format is (almost) completely defined in `help.rs` instead of being scattered across multiple files.
- The HELP object contains a writer and its methods accept AnyArgs, not the other way around.
- A template option allows the user to change completely the organization of the autogenerated help.
This commit is contained in:
Homu 2016-04-18 08:12:39 +09:00
commit fdbd12e830
15 changed files with 1297 additions and 482 deletions

297
benches/04_new_help.rs Normal file
View file

@ -0,0 +1,297 @@
#![feature(test)]
extern crate clap;
extern crate test;
use test::Bencher;
use std::io::Cursor;
use clap::App;
use clap::{Arg, SubCommand};
fn build_help(app: &App) -> String {
let mut buf = Cursor::new(Vec::with_capacity(50));
app.write_help(&mut buf).unwrap();
let content = buf.into_inner();
String::from_utf8(content).unwrap()
}
fn app_example1<'b, 'c>() -> App<'b, 'c> {
App::new("MyApp")
.version("1.0")
.author("Kevin K. <kbknapp@gmail.com>")
.about("Does awesome things")
.args_from_usage("-c, --config=[FILE] 'Sets a custom config file'
<output> '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 app_example2<'b, 'c>() -> App<'b, 'c> {
App::new("MyApp")
.version("1.0")
.author("Kevin K. <kbknapp@gmail.com>")
.about("Does awesome things")
}
fn app_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 app_example4<'b, 'c>() -> App<'b, 'c> {
App::new("MyApp")
.about("Parses an input file to do awesome things")
.version("1.0")
.author("Kevin K. <kbknapp@gmail.com>")
.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 app_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 app_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 app_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 app_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 app_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(b: &mut Bencher) {
let app = app_example1();
b.iter(|| build_help(&app));
}
#[bench]
fn example2(b: &mut Bencher) {
let app = app_example2();
b.iter(|| build_help(&app));
}
#[bench]
fn example3(b: &mut Bencher) {
let app = app_example3();
b.iter(|| build_help(&app));
}
#[bench]
fn example4(b: &mut Bencher) {
let app = app_example4();
b.iter(|| build_help(&app));
}
#[bench]
fn example5(b: &mut Bencher) {
let app = app_example5();
b.iter(|| build_help(&app));
}
#[bench]
fn example6(b: &mut Bencher) {
let app = app_example6();
b.iter(|| build_help(&app));
}
#[bench]
fn example7(b: &mut Bencher) {
let app = app_example7();
b.iter(|| build_help(&app));
}
#[bench]
fn example8(b: &mut Bencher) {
let app = app_example8();
b.iter(|| build_help(&app));
}
#[bench]
fn example10(b: &mut Bencher) {
let app = app_example10();
b.iter(|| build_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 = app_example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n");
b.iter(|| build_help(&app));
}

780
src/app/help.rs Normal file
View file

@ -0,0 +1,780 @@
use std::io::{self, Cursor, Read, 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;
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
use unicode_width::UnicodeWidthStr;
use strext::_StrExt;
#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))]
fn str_width(s: &str) -> usize {
s.len()
}
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
fn str_width(s: &str) -> usize {
UnicodeWidthStr::width(s)
}
const TAB: &'static str = " ";
// These are just convenient traits to make the code easier to read.
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
}
}
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,
hide_pv: bool,
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,
next_line_help: next_line_help,
hide_pv: hide_pv,
term_w: term::dimensions().map(|(w, _)| w),
}
}
/// 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<()> {
Self::write_parser_help(w, &app.p)
}
/// Reads help settings from a Parser
/// and write its help to the wrapped stream.
pub fn write_parser_help(w: &'a mut Write, parser: &Parser) -> ClapResult<()> {
let nlh = parser.is_set(AppSettings::NextLineHelp);
let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp);
Self::new(w, nlh, hide_v).write_help(&parser)
}
/// Writes the parser help to the wrapped stream.
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(())
}
}
// 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>>
{
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(())
}
/// 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>>
{
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(())
}
/// Writes help for an argument to the wrapped stream.
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(())
}
/// 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));
if let Some(s) = arg.short() {
write!(self.writer, "-{}", s)
} else if arg.has_switch() {
write!(self.writer, "{}", TAB)
} else {
Ok(())
}
}
/// 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() {
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 {
if let Some(l) = arg.long() {
try!(write!(self.writer,
"{}--{}",
if arg.short().is_some() {
", "
} else {
""
},
l));
if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) {
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_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() {
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 if arg.has_switch() {
try!(write!(self.writer, "<{}>", arg.name()));
} else {
try!(write!(self.writer, "{}", arg));
}
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_nspaces!(self.writer, spcs);
}
} else if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) {
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);
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 + str_width(h) +
str_width(&*spec_vals) >= width);
debugln!("Too long...{:?}", too_long);
// Is help on next line, if so newline + 2x tab
if self.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 width: {}", str_width(&*help));
// Determine how many newlines we need to insert
let avail_chars = width - spcs;
debugln!("Usable space: {}", avail_chars);
let longest_w = {
let mut lw = 0;
for l in help.split(' ').map(|s| str_width(s)) {
if l > lw {
lw = l;
}
}
lw
};
debugln!("Longest word...{}", longest_w);
debug!("Enough space to wrap...");
if longest_w < avail_chars {
sdebugln!("Yes");
let mut indices = vec![];
let mut idx = 0;
loop {
idx += avail_chars - 1;
if idx >= help.len() {
break;
}
// 'a' arbitrary non space char
if help.chars().nth(idx).unwrap_or('a') != ' ' {
idx = find_idx_of_space(&*help, idx);
}
debugln!("Adding idx: {}", idx);
debugln!("At {}: {:?}", idx, help.chars().nth(idx));
indices.push(idx);
if str_width(&help[idx..]) <= avail_chars {
break;
}
}
for (i, idx) in indices.iter().enumerate() {
debugln!("iter;i={},idx={}", i, idx);
let j = idx + (2 * i);
debugln!("removing: {}", j);
debugln!("at {}: {:?}", j, help.chars().nth(j));
help.remove(j);
help.insert(j, '{');
help.insert(j + 1, 'n');
help.insert(j + 2, '}');
}
} else {
sdebugln!("No");
}
} else {
sdebugln!("No");
}
let help = if !help.is_empty() {
&*help
} else if spec_vals.is_empty() {
h
} else {
help.push_str(h);
help.push_str(&*spec_vals);
&*help
};
if help.contains("{n}") {
if let Some(part) = help.split("{n}").next() {
try!(write!(self.writer, "{}", part));
}
for part in help.split("{n}").skip(1) {
try!(write!(self.writer, "\n"));
if self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) {
try!(write!(self.writer, "{}{}", TAB, TAB));
} else if arg.has_switch() {
write_nspaces!(self.writer, longest + 12);
} else {
write_nspaces!(self.writer, longest + 8);
}
try!(write!(self.writer, "{}", 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 {
"".into()
} else {
if let Some(ref pv) = a.possible_vals() {
format!(" [values: {}]", pv.join(", "))
} 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()
}
}
// 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();
let pos = parser.has_positionals();
let opts = parser.has_opts();
let subcmds = parser.has_subcommands();
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, "OPTIONS:\n"));
try!(self.write_args(opts_flags));
first = false;
} else {
if flags {
try!(write!(self.writer, "FLAGS:\n"));
try!(self.write_args(parser.iter_flags()
.map(as_arg_trait)));
first = false;
}
if opts {
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 {
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 {
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(' ') {
// 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(())
}
/// Writes default help for a Parser Object to the wrapped stream.
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,
"\nUSAGE:\n{}{}\n\n",
TAB,
parser.create_usage_no_title(&[])));
let flags = parser.has_flags();
let pos = parser.has_positionals();
let opts = parser.has_opts();
let subcmds = parser.has_subcommands();
if flags || opts || pos || subcmds {
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)
}
}
/// 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, mut start: usize) -> usize {
debugln!("fn=find_idx_of_space;");
let haystack = if full._is_char_boundary(start) {
&full[..start]
} else {
while !full._is_char_boundary(start) { start -= 1; }
&full[..start]
};
debugln!("haystack: {}", haystack);
for (i, c) in haystack.chars().rev().enumerate() {
debugln!("iter;c={},i={}", c, i);
if c == ' ' {
debugln!("Found space returning start-i...{}", start - (i + 1));
return start - (i + 1);
}
}
0
}

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

@ -4,6 +4,7 @@ mod settings;
mod macros;
mod parser;
mod meta;
mod help;
pub use self::settings::AppSettings;
@ -21,8 +22,9 @@ use std::fmt;
use yaml_rust::Yaml;
use vec_map::VecMap;
use args::{Arg, HelpWriter, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher};
use args::{Arg, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher};
use app::parser::Parser;
use app::help::Help;
use errors::Error;
use errors::Result as ClapResult;
@ -317,6 +319,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.
@ -634,7 +669,7 @@ impl<'a, 'b> App<'a, 'b> {
/// app.write_help(&mut out).ok().expect("failed to write to stdout");
/// ```
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
self.p.write_help(w)
Help::write_app_help(w, &self)
}
/// Starts the parsing process, upon a failed parse an error will be displayed to the user and
@ -808,30 +843,6 @@ impl<'a, 'b> App<'a, 'b> {
e.exit()
}
#[doc(hidden)]
pub fn write_self_help<W>(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()>
where W: Write
{
let hw = HelpWriter::new(self, longest, nlh);
hw.write_to(w)
// try!(write!(w, " {}", self.p.meta.name));
// write_spaces!((longest_sc + 4) - (self.p.meta.name.len()), w);
// if let Some(a) = self.p.meta.about {
// if a.contains("{n}") {
// let mut ab = a.split("{n}");
// while let Some(part) = ab.next() {
// try!(write!(w, "{}\n", part));
// write_spaces!(longest_sc + 8, w);
// try!(write!(w, "{}", ab.next().unwrap_or("")));
// }
// } else {
// try!(write!(w, "{}", a));
// }
// }
// write!(w, "\n")
}
}
#[cfg(feature = "yaml")]
@ -942,6 +953,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.p.meta.about }
fn default_val(&self) -> Option<&'n str> { None }
fn longest_filter(&self) -> bool { true }
}
impl<'n, 'e> fmt::Display for App<'n, 'e> {

View file

@ -6,8 +6,9 @@ use std::fmt::Display;
#[cfg(feature = "debug")]
use std::os::unix::ffi::OsStrExt;
use vec_map::VecMap;
use vec_map::{self, VecMap};
use app::help::Help;
use app::App;
use args::{Arg, FlagBuilder, OptBuilder, ArgGroup, PosBuilder};
use app::settings::{AppSettings, AppFlags};
@ -1309,10 +1310,20 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
// Creates a usage string if one was not provided by the user manually. This happens just
// after all arguments were parsed, but before any subcommands have been parsed
// (so as to give subcommands their own usage recursively)
fn create_usage(&self, used: &[&str]) -> String {
pub fn create_usage(&self, used: &[&str]) -> String {
debugln!("fn=create_usage;");
let mut usage = String::with_capacity(75);
usage.push_str("USAGE:\n ");
usage.push_str(&self.create_usage_no_title(&used));
usage
}
// Creates a usage string (*without title*) if one was not provided by the user
// manually. This happens just
// after all arguments were parsed, but before any subcommands have been parsed
// (so as to give subcommands their own usage recursively)
pub fn create_usage_no_title(&self, used: &[&str]) -> String {
let mut usage = String::with_capacity(75);
if let Some(u) = self.meta.usage_str {
usage.push_str(&*u);
} else if used.is_empty() {
@ -1331,8 +1342,8 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
} else {
usage.push_str(" [OPTIONS]");
}
if !self.is_set(AppSettings::UnifiedHelpMessage)
&& self.has_opts()
if !self.is_set(AppSettings::UnifiedHelpMessage)
&& self.has_opts()
&& self.opts.iter().any(|a| !a.settings.is_set(ArgSettings::Required)) {
usage.push_str(" [OPTIONS]");
}
@ -1411,146 +1422,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
#[cfg_attr(feature = "lints", allow(for_kv_map))]
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
if let Some(h) = self.meta.help_str {
return writeln!(w, "{}", h).map_err(Error::from);
}
// Print the version
try!(self.write_version(w));
if let Some(author) = self.meta.author {
try!(write!(w, "{}\n", author));
}
if let Some(about) = self.meta.about {
try!(write!(w, "{}\n", about));
}
try!(write!(w, "\n{}", self.create_usage(&[])));
let flags = self.has_flags();
let pos = self.has_positionals();
let opts = self.has_opts();
let subcmds = self.has_subcommands();
let unified_help = self.is_set(AppSettings::UnifiedHelpMessage);
let mut longest_flag = 0;
for fl in self.flags.iter()
.filter(|f| f.long.is_some() && !(f.settings.is_set(ArgSettings::Hidden) || f.settings.is_set(ArgSettings::NextLineHelp)))
.map(|a| a.to_string().len()) {
if fl > longest_flag {
longest_flag = fl;
}
}
let mut longest_opt = 0;
for ol in self.opts.iter()
.filter(|o| !(o.settings.is_set(ArgSettings::Hidden) || o.settings.is_set(ArgSettings::NextLineHelp)))
.map(|a| a.to_string().len()) {
if ol > longest_opt {
longest_opt = ol;
}
}
let mut longest_pos = 0;
for pl in self.positionals
.values()
.filter(|p| !(p.settings.is_set(ArgSettings::Hidden) || p.settings.is_set(ArgSettings::NextLineHelp)))
.map(|f| f.to_string().len()) {
if pl > longest_pos {
longest_pos = pl;
}
}
let mut longest_sc = 0;
for scl in self.subcommands
.iter()
.filter(|s| !s.p.is_set(AppSettings::Hidden))
.map(|s| s.p.meta.name.len()) {
if scl > longest_sc {
longest_sc = scl;
}
}
if flags || opts || pos || subcmds {
try!(write!(w, "\n"));
}
let longest = if !unified_help || longest_opt == 0 {
longest_flag
} else {
longest_opt
};
let nlh = self.settings.is_set(AppSettings::NextLineHelp);
if unified_help && (flags || opts) {
try!(write!(w, "\nOPTIONS:\n"));
let mut ord_m = VecMap::new();
for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) {
let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new());
let mut v = vec![];
try!(f.write_help(&mut v, longest, nlh));
btm.insert(f.name, v);
}
for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) {
let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new());
let mut v = vec![];
try!(o.write_help(&mut v, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
btm.insert(o.name, v);
}
for (_, btm) in ord_m.into_iter() {
for (_, a) in btm.into_iter() {
// Only valid UTF-8 is supported, so panicing on invalid UTF-8 is ok
try!(write!(w, "{}", unsafe { String::from_utf8_unchecked(a) }));
}
}
} else {
if flags {
let mut ord_m = VecMap::new();
try!(write!(w, "\nFLAGS:\n"));
for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) {
let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new());
btm.insert(f.name, f);
}
for (_, btm) in ord_m.into_iter() {
for (_, f) in btm.into_iter() {
try!(f.write_help(w, longest, nlh));
}
}
}
if opts {
let mut ord_m = VecMap::new();
try!(write!(w, "\nOPTIONS:\n"));
for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) {
let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new());
btm.insert(o.name, o);
}
for (_, btm) in ord_m.into_iter() {
for (_, o) in btm.into_iter() {
try!(o.write_help(w, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
}
}
}
}
if pos {
try!(write!(w, "\nARGS:\n"));
for v in self.positionals.values()
.filter(|p| !p.settings.is_set(ArgSettings::Hidden)) {
try!(v.write_help(w, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
}
}
if subcmds {
let mut ord_m = VecMap::new();
try!(write!(w, "\nSUBCOMMANDS:\n"));
for sc in self.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) {
let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new());
btm.insert(sc.p.meta.name.clone(), sc);
}
for (_, btm) in ord_m.into_iter() {
for (_, sc) in btm.into_iter() {
try!(sc.write_self_help(w, longest_sc, nlh));
}
}
}
if let Some(h) = self.meta.more_help {
try!(write!(w, "\n{}", h));
}
w.flush().map_err(Error::from)
Help::write_parser_help(w, &self)
}
fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
@ -1570,6 +1442,18 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
}
Ok(())
}
pub fn iter_flags(&self) -> Iter<FlagBuilder> {
self.flags.iter()
}
pub fn iter_opts(&self) -> Iter<OptBuilder> {
self.opts.iter()
}
pub fn iter_positionals(&self) -> vec_map::Values<PosBuilder> {
self.positionals.values()
}
}
impl<'a, 'b> Clone for Parser<'a, 'b> where 'a: 'b {

View file

@ -25,4 +25,9 @@ pub trait AnyArg<'n, 'e> {
fn val_names(&self) -> Option<&VecMap<&'e str>>;
fn help(&self) -> Option<&'e str>;
fn default_val(&self) -> Option<&'n str>;
fn longest_filter(&self) -> bool;
}
pub trait DispOrder {
fn disp_ord(&self) -> usize;
}

View file

@ -1,14 +1,13 @@
// use std::collections::HashSet;
use std::fmt::{Display, Formatter, Result};
use std::convert::From;
use std::io;
use std::rc::Rc;
use std::result::Result as StdResult;
use vec_map::VecMap;
use Arg;
use args::{AnyArg, HelpWriter};
use args::{AnyArg, DispOrder};
use args::settings::{ArgFlags, ArgSettings};
#[derive(Debug)]
@ -48,11 +47,6 @@ impl<'n, 'e> FlagBuilder<'n, 'e> {
..Default::default()
}
}
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()> {
let hw = HelpWriter::new(self, longest, nlh);
hw.write_to(w)
}
}
impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> {
@ -129,6 +123,11 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
fn val_delim(&self) -> Option<char> { None }
fn help(&self) -> Option<&'e str> { self.help }
fn default_val(&self) -> Option<&'n str> { None }
fn longest_filter(&self) -> bool { self.long.is_some() }
}
impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.disp_ord }
}
#[cfg(test)]

View file

@ -1,11 +1,10 @@
use std::rc::Rc;
use std::fmt::{Display, Formatter, Result};
use std::result::Result as StdResult;
use std::io;
use vec_map::VecMap;
use args::{AnyArg, Arg, HelpWriter};
use args::{AnyArg, Arg, DispOrder};
use args::settings::{ArgFlags, ArgSettings};
#[allow(missing_debug_implementations)]
@ -104,11 +103,6 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
ob
}
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
let mut hw = HelpWriter::new(self, longest, nlh);
hw.skip_pv = skip_pv;
hw.write_to(w)
}
}
impl<'n, 'e> Display for OptBuilder<'n, 'e> {
@ -193,6 +187,11 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.help }
fn default_val(&self) -> Option<&'n str> { self.default_val }
fn longest_filter(&self) -> bool { true }
}
impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.disp_ord }
}
#[cfg(test)]

View file

@ -1,12 +1,11 @@
use std::fmt::{Display, Formatter, Result};
use std::result::Result as StdResult;
use std::rc::Rc;
use std::io;
use vec_map::VecMap;
use Arg;
use args::{AnyArg, HelpWriter};
use args::{AnyArg, DispOrder};
use args::settings::{ArgFlags, ArgSettings};
#[allow(missing_debug_implementations)]
@ -105,11 +104,6 @@ impl<'n, 'e> PosBuilder<'n, 'e> {
pb
}
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
let mut hw = HelpWriter::new(self, longest, nlh);
hw.skip_pv = skip_pv;
hw.write_to(w)
}
}
impl<'n, 'e> Display for PosBuilder<'n, 'e> {
@ -180,6 +174,11 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.help }
fn default_val(&self) -> Option<&'n str> { self.default_val }
fn longest_filter(&self) -> bool { true }
}
impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.disp_ord }
}
#[cfg(test)]

View file

@ -1,288 +0,0 @@
use std::io;
use std::fmt::Display;
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
use unicode_width::UnicodeWidthStr;
use args::AnyArg;
use args::settings::ArgSettings;
use term;
use strext::_StrExt;
#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))]
fn str_width(s: &str) -> usize {
s.len()
}
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
fn str_width(s: &str) -> usize {
UnicodeWidthStr::width(s)
}
const TAB: &'static str = " ";
pub struct HelpWriter<'a, A> where A: 'a {
a: &'a A,
l: usize,
nlh: bool,
pub skip_pv: bool,
term_w: Option<usize>,
}
impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
pub fn new(a: &'a A, l: usize, nlh: bool) -> Self {
HelpWriter {
a: a,
l: l,
nlh: nlh,
skip_pv: false,
term_w: term::dimensions().map(|(w, _)| w),
}
}
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
debugln!("fn=write_to;");
try!(self.short(w));
try!(self.long(w));
try!(self.val(w));
try!(self.help(w));
write!(w, "\n")
}
fn short<W>(&self, w: &mut W) -> io::Result<()>
where W: io::Write
{
debugln!("fn=short;");
try!(write!(w, "{}", TAB));
if let Some(s) = self.a.short() {
write!(w, "-{}", s)
} else if self.a.has_switch() {
write!(w, "{}", TAB)
} else {
Ok(())
}
}
fn long<W>(&self, w: &mut W) -> io::Result<()>
where W: io::Write
{
debugln!("fn=long;");
if !self.a.has_switch() {
return Ok(());
}
if self.a.takes_value() {
if let Some(l) = self.a.long() {
try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l));
}
try!(write!(w, " "));
} else {
if let Some(l) = self.a.long() {
try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l));
if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) {
write_spaces!((self.l + 4) - (l.len() + 2), w);
}
} else {
if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) {
// 6 is tab (4) + -- (2)
write_spaces!((self.l + 6), w);
}
}
}
Ok(())
}
fn val<W>(&self, w: &mut W) -> io::Result<()>
where W: io::Write
{
debugln!("fn=val;");
if !self.a.takes_value() {
return Ok(());
}
if let Some(ref vec) = self.a.val_names() {
let mut it = vec.iter().peekable();
while let Some((_, val)) = it.next() {
try!(write!(w, "<{}>", val));
if it.peek().is_some() { try!(write!(w, " ")); }
}
let num = vec.len();
if self.a.is_set(ArgSettings::Multiple) && num == 1 {
try!(write!(w, "..."));
}
} else if let Some(num) = self.a.num_vals() {
let mut it = (0..num).peekable();
while let Some(_) = it.next() {
try!(write!(w, "<{}>", self.a.name()));
if it.peek().is_some() { try!(write!(w, " ")); }
}
} else if self.a.has_switch() {
try!(write!(w, "<{}>", self.a.name()));
} else {
try!(write!(w, "{}", self.a));
}
if self.a.has_switch() {
if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) {
let self_len = self.a.to_string().len();
// subtract ourself
let mut spcs = self.l - self_len;
// Since we're writing spaces from the tab point we first need to know if we
// had a long and short, or just short
if self.a.long().is_some() {
// Only account 4 after the val
spcs += 4;
} else {
// Only account for ', --' + 4 after the val
spcs += 8;
}
write_spaces!(spcs, w);
}
} else if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) {
write_spaces!(self.l + 4 - (self.a.to_string().len()), w);
}
Ok(())
}
fn help<W>(&self, w: &mut W) -> io::Result<()>
where W: io::Write
{
debugln!("fn=help;");
let spec_vals = self.spec_vals();
let mut help = String::new();
let h = self.a.help().unwrap_or("");
let spcs = if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
8 // "tab" + "tab"
} else {
self.l + 12
};
// determine if our help fits or needs to wrap
let width = self.term_w.unwrap_or(0);
debugln!("Term width...{}", width);
let too_long = self.term_w.is_some() && (spcs + str_width(h) + str_width(&*spec_vals) >= width);
debugln!("Too long...{:?}", too_long);
// Is help on next line, if so newline + 2x tab
if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
try!(write!(w, "\n{}{}", TAB, TAB));
}
debug!("Too long...");
if too_long {
sdebugln!("Yes");
help.push_str(h);
help.push_str(&*spec_vals);
debugln!("help: {}", help);
debugln!("help width: {}", str_width(&*help));
// Determine how many newlines we need to insert
let avail_chars = width - spcs;
debugln!("Usable space: {}", avail_chars);
let longest_w = {
let mut lw = 0;
for l in help.split(' ').map(|s| str_width(s)) {
if l > lw {
lw = l;
}
}
lw
};
debugln!("Longest word...{}", longest_w);
debug!("Enough space to wrap...");
if longest_w < avail_chars {
sdebugln!("Yes");
let mut indices = vec![];
let mut idx = 0;
loop {
idx += avail_chars - 1;
if idx >= help.len() { break; }
// 'a' arbitrary non space char
if help.chars().nth(idx).unwrap_or('a') != ' ' {
idx = find_idx_of_space(&*help, idx);
}
debugln!("Adding idx: {}", idx);
debugln!("At {}: {:?}", idx, help.chars().nth(idx));
indices.push(idx);
if str_width(&help[idx..]) <= avail_chars {
break;
}
}
for (i, idx) in indices.iter().enumerate() {
debugln!("iter;i={},idx={}", i, idx);
let j = idx + (2 * i);
debugln!("removing: {}", j);
debugln!("at {}: {:?}", j, help.chars().nth(j));
help.remove(j);
help.insert(j, '{');
help.insert(j + 1 , 'n');
help.insert(j + 2, '}');
}
} else {
sdebugln!("No");
}
} else { sdebugln!("No"); }
let help = if !help.is_empty() {
&*help
} else if !spec_vals.is_empty() {
help.push_str(h);
help.push_str(&*spec_vals);
&*help
} else {
h
};
if help.contains("{n}") {
if let Some(part) = help.split("{n}").next() {
try!(write!(w, "{}", part));
}
for part in help.split("{n}").skip(1) {
try!(write!(w, "\n"));
if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
try!(write!(w, "{}{}", TAB, TAB));
} else if self.a.has_switch() {
write_spaces!(self.l + 12, w);
} else {
write_spaces!(self.l + 8, w);
}
try!(write!(w, "{}", part));
}
} else {
try!(write!(w, "{}", help));
}
Ok(())
}
fn spec_vals(&self) -> String {
debugln!("fn=spec_vals;");
if let Some(ref pv) = self.a.default_val() {
debugln!("Writing defaults");
return format!(" [default: {}] {}", pv,
if !self.skip_pv {
if let Some(ref pv) = self.a.possible_vals() {
format!(" [values: {}]", pv.join(", "))
} else { "".into() }
} else { "".into() }
);
} else if !self.skip_pv {
debugln!("Writing values");
if let Some(ref pv) = self.a.possible_vals() {
debugln!("Possible vals...{:?}", pv);
return format!(" [values: {}]", pv.join(", "));
}
}
String::new()
}
}
fn find_idx_of_space(full: &str, mut start: usize) -> usize {
debugln!("fn=find_idx_of_space;");
let haystack = if full._is_char_boundary(start) {
&full[..start]
} else {
while !full._is_char_boundary(start) { start -= 1; }
&full[..start]
};
debugln!("haystack: {}", haystack);
for (i, c) in haystack.chars().rev().enumerate() {
debugln!("iter;c={},i={}", c, i);
if c == ' ' {
debugln!("Found space returning start-i...{}", start - (i + 1));
return start - (i + 1);
}
}
0
}

View file

@ -5,9 +5,8 @@ pub use self::subcommand::SubCommand;
pub use self::arg_builder::{FlagBuilder, OptBuilder, PosBuilder};
pub use self::matched_arg::MatchedArg;
pub use self::group::ArgGroup;
pub use self::any_arg::AnyArg;
pub use self::any_arg::{AnyArg, DispOrder};
pub use self::settings::ArgSettings;
pub use self::help_writer::HelpWriter;
mod arg;
pub mod any_arg;
@ -19,4 +18,3 @@ mod matched_arg;
mod group;
#[allow(dead_code)]
pub mod settings;
mod help_writer;

View file

@ -610,6 +610,19 @@ macro_rules! write_spaces {
})
}
// Helper/deduplication macro for printing the correct number of spaces in help messages
// used in:
// src/args/arg_builder/*.rs
// src/app/mod.rs
macro_rules! write_nspaces {
($dst:expr, $num:expr) => ({
debugln!("macro=write_spaces!;");
for _ in 0..$num {
try!($dst.write(b" "));
}
})
}
// convenience macro for remove an item from a vec
macro_rules! vec_remove {
($vec:expr, $to_rem:expr) => {

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}

91
tests/template_help.rs Normal file
View file

@ -0,0 +1,91 @@
extern crate clap;
use std::io::Cursor;
use clap::{App, SubCommand};
static EXAMPLE1_TMPL_S : &'static str = include_str!("example1_tmpl_simple.txt");
static EXAMPLE1_TMPS_F : &'static str = include_str!("example1_tmpl_full.txt");
fn build_new_help(app: &App) -> String {
let mut buf = Cursor::new(Vec::with_capacity(50));
app.write_help(&mut buf).unwrap();
let content = buf.into_inner();
String::from_utf8(content).unwrap()
}
fn compare2(app1: &App, app2: &App) -> bool {
let hlp1f = build_new_help(&app1);
let hlp1 = hlp1f.trim();
let hlp2f = build_new_help(&app2);
let hlp2 = hlp2f.trim();
let b = hlp1 == hlp2;
if !b {
println!("");
println!("--> hlp1");
println!("{}", hlp1);
println!("--> hlp2");
println!("{}", hlp2);
println!("--")
}
b
}
#[test]
fn comparison_with_template() {
assert!(compare2(&app_example1(), &app_example1().template(EXAMPLE1_TMPL_S)));
assert!(compare2(&app_example1(), &app_example1().template(EXAMPLE1_TMPS_F)));
}
#[test]
fn template_empty() {
let app = App::new("MyApp")
.version("1.0")
.author("Kevin K. <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 app_example1<'b, 'c>() -> App<'b, 'c> {
App::new("MyApp")
.version("1.0")
.author("Kevin K. <kbknapp@gmail.com>")
.about("Does awesome things")
.args_from_usage("-c, --config=[FILE] 'Sets a custom config file'
<output> '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'"))
}