Added custom usage strings

This commit is contained in:
Kevin K 2015-03-16 14:28:56 -04:00
parent b77920304c
commit 1814d87192

View file

@ -4,6 +4,7 @@ use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::path::Path;
use std::vec::IntoIter; use std::vec::IntoIter;
use argmatches::ArgMatches; use argmatches::ArgMatches;
@ -60,6 +61,8 @@ pub struct App {
short_list: HashSet<char>, short_list: HashSet<char>,
long_list: HashSet<&'static str>, long_list: HashSet<&'static str>,
blacklist: HashSet<&'static str>, blacklist: HashSet<&'static str>,
usage_str: Option<&'static str>,
bin_name: Option<String>
} }
@ -67,7 +70,7 @@ impl App {
/// Creates a new instance of an application requiring a name (such as the binary). Will be displayed /// Creates a new instance of an application requiring a name (such as the binary). Will be displayed
/// to the user when they print version or help and usage information. /// to the user when they print version or help and usage information.
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -94,13 +97,15 @@ impl App {
arg_list: HashSet::new(), arg_list: HashSet::new(),
short_list: HashSet::new(), short_list: HashSet::new(),
long_list: HashSet::new(), long_list: HashSet::new(),
usage_str: None,
blacklist: HashSet::new(), blacklist: HashSet::new(),
bin_name: None,
} }
} }
/// Sets a string of author(s) /// Sets a string of author(s)
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -115,7 +120,7 @@ impl App {
/// Sets a string briefly describing what the program does /// Sets a string briefly describing what the program does
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -130,7 +135,7 @@ impl App {
/// Sets a string of the version number /// Sets a string of the version number
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -138,14 +143,34 @@ impl App {
/// .version("v0.1.24") /// .version("v0.1.24")
/// # .get_matches(); /// # .get_matches();
/// ``` /// ```
pub fn version(mut self, v: &'static str)-> App { pub fn version(mut self, v: &'static str) -> App {
self.version = Some(v); self.version = Some(v);
self self
} }
/// Sets a custom usage string to over-ride the one auto-generated by `clap`
/// *NOTE:* You do not need to specify the "USAGE: " portion, as that will
/// still be applied by `clap`, you only need to specify the portion starting
/// with the binary name.
/// *NOTE:* This will not replace the entire help message, only the portion
/// showing the usage.
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// # let app = App::new("myprog")
/// .usage("myapp [-clDas] <some_file>")
/// # .get_matches();
/// ```
pub fn usage(mut self, u: &'static str) -> App {
self.usage_str = Some(u);
self
}
/// Adds an argument to the list of valid possibilties /// Adds an argument to the list of valid possibilties
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -239,9 +264,9 @@ impl App {
self self
} }
/// Adds arguments to the list of valid possibilties /// Adds multiple arguments to the list of valid possibilties
/// ///
/// Example: /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
@ -257,12 +282,44 @@ impl App {
self self
} }
/// Adds a subcommand to the list of valid possibilties. Subcommands
/// are effectively sub apps, because they can contain their own arguments
/// and subcommands. They also function just like apps, in that they get their
/// own auto generated help and version switches.
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// # let app = App::new("myprog")
/// .subcommand(SubCommand::new("config")
/// .about("Controls configuration features")
/// .arg(Arg::new("config_file")
/// .index(1)
/// .help("Configuration file to use")))
/// // Additional subcommand configuration goes here, such as arguments...
/// )
/// # .get_matches();
/// ```
pub fn subcommand(mut self, subcmd: App) -> App { pub fn subcommand(mut self, subcmd: App) -> App {
if subcmd.name == "help" { self.needs_subcmd_help = false; } if subcmd.name == "help" { self.needs_subcmd_help = false; }
self.subcommands.insert(subcmd.name, Box::new(subcmd)); self.subcommands.insert(subcmd.name, Box::new(subcmd));
self self
} }
/// Adds multiple subcommands to the list of valid possibilties
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// # let app = App::new("myprog")
/// .subcommands( vec![
/// SubCommand::new("config").about("Controls configuration functionality")
/// .arg(Arg::new("config_file").index(1)),
/// SubCommand::new("debug").about("Controls debug functionality")])
/// # .get_matches();
/// ```
pub fn subcommands(mut self, subcmds: Vec<App>) -> App { pub fn subcommands(mut self, subcmds: Vec<App>) -> App {
for subcmd in subcmds.into_iter() { for subcmd in subcmds.into_iter() {
self = self.subcommand(subcmd); self = self.subcommand(subcmd);
@ -277,16 +334,50 @@ impl App {
fn report_error(&self, msg: String, help: bool, quit: bool) { fn report_error(&self, msg: String, help: bool, quit: bool) {
println!("{}", msg); println!("{}", msg);
if help { self.print_help(); } if help { self.print_usage(true); }
if quit { env::set_exit_status(1); self.exit(); } if quit { env::set_exit_status(1); self.exit(); }
} }
fn print_usage(&self, more_info: bool) {
println!("USAGE:");
if let Some(u) = self.usage_str {
println!("\t{}",u);
} else {
let flags = ! self.flags.is_empty();
let pos = ! self.positionals_idx.is_empty();
let req_pos = self.positionals_idx.values().filter_map(|ref x| if x.required { Some(x.name) } else {None})
.fold(String::new(), |acc, ref name| acc + &format!("<{}> ", name.to_uppercase())[..]);
let req_opts = self.opts.values().filter(|ref x| x.required)
.fold(String::new(), |acc, ref o| acc + &format!("{}{} ",if let Some(s) = o.short {
format!("-{} ", s)
} else {
format!("--{}=",o.long.unwrap())
},o.name.to_uppercase()));
let opts = ! self.opts.is_empty();
let subcmds = ! self.subcommands.is_empty();
print!("\t{} {} {} {} {}", if let Some(ref name) = self.bin_name { &name[..] } else { self.name },
if flags {"[FLAGS]"} else {""},
if opts {
if req_opts.is_empty() { "[OPTIONS]" } else { &req_opts[..] }
} else { "" },
if pos {
if req_pos.is_empty() { "[POSITIONAL]"} else { &req_pos[..] }
} else {""},
if subcmds {"[SUBCOMMANDS]"} else {""});
}
if more_info {
println!("\nFor more information try --help");
}
}
fn print_help(&self) { fn print_help(&self) {
self.print_version(false); self.print_version(false);
let mut flags = false; let flags = ! self.flags.is_empty();
let mut pos = false; let pos = ! self.positionals_idx.is_empty();
let mut opts = false; let opts = ! self.opts.is_empty();
let mut subcmds = false; let subcmds = ! self.subcommands.is_empty();
if let Some(author) = self.author { if let Some(author) = self.author {
println!("{}", author); println!("{}", author);
@ -295,12 +386,7 @@ impl App {
println!("{}", about); println!("{}", about);
} }
println!(""); println!("");
println!("USAGE:"); self.print_usage(false);
print!("\t{} {} {} {} {}", self.name,
if ! self.subcommands.is_empty() {subcmds = true; "[SUBCOMMANDS]"} else {""},
if ! self.flags.is_empty() {flags = true; "[FLAGS]"} else {""},
if ! self.opts.is_empty() {opts = true; "[OPTIONS]"} else {""},
if ! self.positionals_idx.is_empty() {pos = true; "[POSITIONAL]"} else {""});
if flags || opts || pos || subcmds { if flags || opts || pos || subcmds {
println!(""); println!("");
} }
@ -318,11 +404,12 @@ impl App {
println!(""); println!("");
println!("OPTIONS:"); println!("OPTIONS:");
for v in self.opts.values() { for v in self.opts.values() {
println!("\t{}{}{}\t\t{}", let mut needs_tab = false;
if let Some(ref s) = v.short{format!("-{}",s)}else{format!(" ")}, println!("\t{}{}{}\t{}",
if let Some(ref l) = v.long {format!(",--{}",l)}else {format!(" ")}, if let Some(ref s) = v.short{format!("-{} ",s)}else{format!(" ")},
format!(" <{}>", v.name), if let Some(ref l) = v.long {format!(",--{}=",l)}else {needs_tab = true; format!(" ")},
if let Some(ref h) = v.help {*h} else {" "} ); format!("{}", v.name.to_uppercase()),
if let Some(ref h) = v.help {if needs_tab {format!("\t{}", *h)} else { format!("{}", *h) } } else {format!(" ")} );
} }
} }
if pos { if pos {
@ -381,7 +468,7 @@ impl App {
if *l == arg { if *l == arg {
if self.blacklist.contains(k) { if self.blacklist.contains(k) {
self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg), self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg),
false, true); true, true);
} }
matches.opts.insert(k, OptArg{ matches.opts.insert(k, OptArg{
name: v.name, name: v.name,
@ -413,7 +500,7 @@ impl App {
if ! multi { if ! multi {
if self.blacklist.contains(k) { if self.blacklist.contains(k) {
self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg), self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg),
false, true); true, true);
} }
matches.flags.insert(k, FlagArg{ matches.flags.insert(k, FlagArg{
name: v.name, name: v.name,
@ -453,7 +540,7 @@ impl App {
if ! found { if ! found {
self.report_error( self.report_error(
format!("Argument --{} isn't valid", arg), format!("Argument --{} isn't valid", arg),
false, true); true, true);
} }
None None
} }
@ -467,7 +554,7 @@ impl App {
if ! self.parse_single_short_flag(matches, c) { if ! self.parse_single_short_flag(matches, c) {
self.report_error( self.report_error(
format!("Argument -{} isn't valid",c), format!("Argument -{} isn't valid",c),
false, true); true, true);
} }
} }
} else { } else {
@ -486,7 +573,7 @@ impl App {
self.report_error( self.report_error(
format!("Argument -{} isn't valid",arg_c), format!("Argument -{} isn't valid",arg_c),
false, true); true, true);
} }
} }
@ -556,7 +643,7 @@ impl App {
} else { } else {
format!("\"{}\"", v.name) format!("\"{}\"", v.name)
}), }),
false, true); true, true);
} }
} }
for (k, v) in matches.opts.iter() { for (k, v) in matches.opts.iter() {
@ -569,7 +656,7 @@ impl App {
} else { } else {
format!("\"{}\"", v.name) format!("\"{}\"", v.name)
}), }),
false, true); true, true);
} }
} }
for (k, v) in matches.positionals.iter() { for (k, v) in matches.positionals.iter() {
@ -631,7 +718,7 @@ impl App {
format!("--{}",long) format!("--{}",long)
}else{ }else{
format!("-{}",opt.short.unwrap()) format!("-{}",opt.short.unwrap())
}),false, true); }),true, true);
} }
matches.opts.insert(nvo, OptArg{ matches.opts.insert(nvo, OptArg{
name: opt.name, name: opt.name,
@ -689,12 +776,12 @@ impl App {
if self.positionals_idx.is_empty() { // || self.positionals_name.is_empty() { if self.positionals_idx.is_empty() { // || self.positionals_name.is_empty() {
self.report_error( self.report_error(
format!("Found positional argument {}, but {} doesn't accept any", arg, self.name), format!("Found positional argument {}, but {} doesn't accept any", arg, self.name),
false, true); true, true);
} }
if let Some(ref p) = self.positionals_idx.get(&pos_counter) { if let Some(ref p) = self.positionals_idx.get(&pos_counter) {
if self.blacklist.contains(p.name) { if self.blacklist.contains(p.name) {
self.report_error(format!("The argument \"{}\" is mutually exclusive with one or more other arguments", arg), self.report_error(format!("The argument \"{}\" is mutually exclusive with one or more other arguments", arg),
false, true); true, true);
} }
matches.positionals.insert(p.name, PosArg{ matches.positionals.insert(p.name, PosArg{
name: p.name, name: p.name,
@ -727,7 +814,7 @@ impl App {
} }
pos_counter += 1; pos_counter += 1;
} else { } else {
self.report_error(format!("Positional argument \"{}\" was found, but {} wasn't expecting any", arg, self.name), false, true); self.report_error(format!("Positional argument \"{}\" was found, but {} wasn't expecting any", arg, self.name), true, true);
} }
} }
} }
@ -736,13 +823,13 @@ impl App {
Some(ref a) => { Some(ref a) => {
self.report_error( self.report_error(
format!("Argument \"{}\" requires a value but none was supplied", a), format!("Argument \"{}\" requires a value but none was supplied", a),
false, true); true, true);
} }
_ => {} _ => {}
} }
if ! self.required.is_empty() { if ! self.required.is_empty() {
self.report_error("One or more required arguments were not supplied".to_string(), self.report_error("One or more required arguments were not supplied".to_string(),
false, true); true, true);
} }
self.validate_blacklist(&matches); self.validate_blacklist(&matches);
@ -762,8 +849,17 @@ impl App {
let mut matches = ArgMatches::new(self.name); let mut matches = ArgMatches::new(self.name);
let args = env::args().collect::<Vec<_>>(); let args = env::args().collect::<Vec<_>>();
let mut it = args.into_iter();
self.get_matches_from(&mut matches, &mut args.into_iter()); if let Some(name) = it.next() {
let p = Path::new(&name[..]);
if let Some(f) = p.file_name() {
match f.to_os_string().into_string() {
Ok(s) => self.bin_name = Some(s),
Err(_) => {}
}
}
}
self.get_matches_from(&mut matches, &mut it );
matches matches
} }