mirror of
https://github.com/clap-rs/clap
synced 2024-12-12 13:52:34 +00:00
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:
commit
fdbd12e830
15 changed files with 1297 additions and 482 deletions
297
benches/04_new_help.rs
Normal file
297
benches/04_new_help.rs
Normal 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
780
src/app/help.rs
Normal 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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
15
tests/example1_tmpl_full.txt
Normal file
15
tests/example1_tmpl_full.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
{bin} {version}
|
||||
{author}
|
||||
{about}
|
||||
|
||||
USAGE:
|
||||
{usage}
|
||||
|
||||
FLAGS:
|
||||
{flags}
|
||||
OPTIONS:
|
||||
{options}
|
||||
ARGS:
|
||||
{positionals}
|
||||
SUBCOMMANDS:
|
||||
{subcommands}
|
8
tests/example1_tmpl_simple.txt
Normal file
8
tests/example1_tmpl_simple.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
{bin} {version}
|
||||
{author}
|
||||
{about}
|
||||
|
||||
USAGE:
|
||||
{usage}
|
||||
|
||||
{all-args}
|
91
tests/template_help.rs
Normal file
91
tests/template_help.rs
Normal 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'"))
|
||||
}
|
Loading…
Reference in a new issue