aboutsummaryrefslogtreecommitdiff
path: root/clap/src/app/usage.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clap/src/app/usage.rs')
-rw-r--r--clap/src/app/usage.rs479
1 files changed, 479 insertions, 0 deletions
diff --git a/clap/src/app/usage.rs b/clap/src/app/usage.rs
new file mode 100644
index 0000000..6090588
--- /dev/null
+++ b/clap/src/app/usage.rs
@@ -0,0 +1,479 @@
+// std
+use std::collections::{BTreeMap, VecDeque};
+
+// Internal
+use INTERNAL_ERROR_MSG;
+use args::{AnyArg, ArgMatcher, PosBuilder};
+use args::settings::ArgSettings;
+use app::settings::AppSettings as AS;
+use app::parser::Parser;
+
+// Creates a usage string for display. 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_with_title(p: &Parser, used: &[&str]) -> String {
+ debugln!("usage::create_usage_with_title;");
+ let mut usage = String::with_capacity(75);
+ usage.push_str("USAGE:\n ");
+ usage.push_str(&*create_usage_no_title(p, used));
+ usage
+}
+
+// Creates a usage string to be used in error message (i.e. one with currently used args)
+pub fn create_error_usage<'a, 'b>(
+ p: &Parser<'a, 'b>,
+ matcher: &'b ArgMatcher<'a>,
+ extra: Option<&str>,
+) -> String {
+ let mut args: Vec<_> = matcher
+ .arg_names()
+ .iter()
+ .filter(|n| {
+ if let Some(o) = find_by_name!(p, **n, opts, iter) {
+ !o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden)
+ } else if let Some(p) = find_by_name!(p, **n, positionals, values) {
+ !p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden)
+ } else {
+ true // flags can't be required, so they're always true
+ }
+ })
+ .map(|&n| n)
+ .collect();
+ if let Some(r) = extra {
+ args.push(r);
+ }
+ create_usage_with_title(p, &*args)
+}
+
+// Creates a usage string (*without title*) if one was not provided by the user manually.
+pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String {
+ debugln!("usage::create_usage_no_title;");
+ if let Some(u) = p.meta.usage_str {
+ String::from(&*u)
+ } else if used.is_empty() {
+ create_help_usage(p, true)
+ } else {
+ create_smart_usage(p, used)
+ }
+}
+
+// Creates a usage string for display in help messages (i.e. not for errors)
+pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
+ let mut usage = String::with_capacity(75);
+ let name = p.meta
+ .usage
+ .as_ref()
+ .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name));
+ usage.push_str(&*name);
+ let req_string = if incl_reqs {
+ let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect();
+ reqs.sort();
+ reqs.dedup();
+ get_required_usage_from(p, &reqs, None, None, false)
+ .iter()
+ .fold(String::new(), |a, s| a + &format!(" {}", s)[..])
+ } else {
+ String::new()
+ };
+
+ let flags = needs_flags_tag(p);
+ if flags && !p.is_set(AS::UnifiedHelpMessage) {
+ usage.push_str(" [FLAGS]");
+ } else if flags {
+ usage.push_str(" [OPTIONS]");
+ }
+ if !p.is_set(AS::UnifiedHelpMessage) && p.opts.iter().any(|o| {
+ !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden)
+ }) {
+ usage.push_str(" [OPTIONS]");
+ }
+
+ usage.push_str(&req_string[..]);
+
+ let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last));
+ // places a '--' in the usage string if there are args and options
+ // supporting multiple values
+ if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple))
+ && p.positionals
+ .values()
+ .any(|p| !p.is_set(ArgSettings::Required))
+ && !(p.has_visible_subcommands() || p.is_set(AS::AllowExternalSubcommands))
+ && !has_last
+ {
+ usage.push_str(" [--]");
+ }
+ let not_req_or_hidden = |p: &PosBuilder| {
+ (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last))
+ && !p.is_set(ArgSettings::Hidden)
+ };
+ if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) {
+ if let Some(args_tag) = get_args_tag(p, incl_reqs) {
+ usage.push_str(&*args_tag);
+ } else {
+ usage.push_str(" [ARGS]");
+ }
+ if has_last && incl_reqs {
+ let pos = p.positionals
+ .values()
+ .find(|p| p.b.is_set(ArgSettings::Last))
+ .expect(INTERNAL_ERROR_MSG);
+ debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name());
+ let req = pos.is_set(ArgSettings::Required);
+ if req
+ && p.positionals
+ .values()
+ .any(|p| !p.is_set(ArgSettings::Required))
+ {
+ usage.push_str(" -- <");
+ } else if req {
+ usage.push_str(" [--] <");
+ } else {
+ usage.push_str(" [-- <");
+ }
+ usage.push_str(&*pos.name_no_brackets());
+ usage.push_str(">");
+ usage.push_str(pos.multiple_str());
+ if !req {
+ usage.push_str("]");
+ }
+ }
+ }
+
+ // incl_reqs is only false when this function is called recursively
+ if p.has_visible_subcommands() && incl_reqs || p.is_set(AS::AllowExternalSubcommands) {
+ if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) {
+ if !p.is_set(AS::ArgsNegateSubcommands) {
+ usage.push_str("\n ");
+ usage.push_str(&*create_help_usage(p, false));
+ usage.push_str(" <SUBCOMMAND>");
+ } else {
+ usage.push_str("\n ");
+ usage.push_str(&*name);
+ usage.push_str(" <SUBCOMMAND>");
+ }
+ } else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) {
+ usage.push_str(" <SUBCOMMAND>");
+ } else {
+ usage.push_str(" [SUBCOMMAND]");
+ }
+ }
+ usage.shrink_to_fit();
+ debugln!("usage::create_help_usage: usage={}", usage);
+ usage
+}
+
+// Creates a context aware usage string, or "smart usage" from currently used
+// args, and requirements
+fn create_smart_usage(p: &Parser, used: &[&str]) -> String {
+ debugln!("usage::smart_usage;");
+ let mut usage = String::with_capacity(75);
+ let mut hs: Vec<&str> = p.required().map(|s| &**s).collect();
+ hs.extend_from_slice(used);
+
+ let r_string = get_required_usage_from(p, &hs, None, None, false)
+ .iter()
+ .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
+
+ usage.push_str(
+ &p.meta
+ .usage
+ .as_ref()
+ .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name))[..],
+ );
+ usage.push_str(&*r_string);
+ if p.is_set(AS::SubcommandRequired) {
+ usage.push_str(" <SUBCOMMAND>");
+ }
+ usage.shrink_to_fit();
+ usage
+}
+
+// Gets the `[ARGS]` tag for the usage string
+fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option<String> {
+ debugln!("usage::get_args_tag;");
+ let mut count = 0;
+ 'outer: for pos in p.positionals
+ .values()
+ .filter(|pos| !pos.is_set(ArgSettings::Required))
+ .filter(|pos| !pos.is_set(ArgSettings::Hidden))
+ .filter(|pos| !pos.is_set(ArgSettings::Last))
+ {
+ debugln!("usage::get_args_tag:iter:{}:", pos.b.name);
+ if let Some(g_vec) = p.groups_for_arg(pos.b.name) {
+ for grp_s in &g_vec {
+ debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s);
+ // if it's part of a required group we don't want to count it
+ if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) {
+ continue 'outer;
+ }
+ }
+ }
+ count += 1;
+ debugln!(
+ "usage::get_args_tag:iter: {} Args not required or hidden",
+ count
+ );
+ }
+ if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
+ debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]");
+ return None; // [ARGS]
+ } else if count == 1 && incl_reqs {
+ let pos = p.positionals
+ .values()
+ .find(|pos| {
+ !pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden)
+ && !pos.is_set(ArgSettings::Last)
+ })
+ .expect(INTERNAL_ERROR_MSG);
+ debugln!(
+ "usage::get_args_tag:iter: Exactly one, returning '{}'",
+ pos.name()
+ );
+ return Some(format!(
+ " [{}]{}",
+ pos.name_no_brackets(),
+ pos.multiple_str()
+ ));
+ } else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs {
+ debugln!("usage::get_args_tag:iter: Don't collapse returning all");
+ return Some(
+ p.positionals
+ .values()
+ .filter(|pos| !pos.is_set(ArgSettings::Required))
+ .filter(|pos| !pos.is_set(ArgSettings::Hidden))
+ .filter(|pos| !pos.is_set(ArgSettings::Last))
+ .map(|pos| {
+ format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())
+ })
+ .collect::<Vec<_>>()
+ .join(""),
+ );
+ } else if !incl_reqs {
+ debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
+ let highest_req_pos = p.positionals
+ .iter()
+ .filter_map(|(idx, pos)| {
+ if pos.b.is_set(ArgSettings::Required) && !pos.b.is_set(ArgSettings::Last) {
+ Some(idx)
+ } else {
+ None
+ }
+ })
+ .max()
+ .unwrap_or_else(|| p.positionals.len());
+ return Some(
+ p.positionals
+ .iter()
+ .filter_map(|(idx, pos)| {
+ if idx <= highest_req_pos {
+ Some(pos)
+ } else {
+ None
+ }
+ })
+ .filter(|pos| !pos.is_set(ArgSettings::Required))
+ .filter(|pos| !pos.is_set(ArgSettings::Hidden))
+ .filter(|pos| !pos.is_set(ArgSettings::Last))
+ .map(|pos| {
+ format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())
+ })
+ .collect::<Vec<_>>()
+ .join(""),
+ );
+ }
+ Some("".into())
+}
+
+// Determines if we need the `[FLAGS]` tag in the usage string
+fn needs_flags_tag(p: &Parser) -> bool {
+ debugln!("usage::needs_flags_tag;");
+ 'outer: for f in &p.flags {
+ debugln!("usage::needs_flags_tag:iter: f={};", f.b.name);
+ if let Some(l) = f.s.long {
+ if l == "help" || l == "version" {
+ // Don't print `[FLAGS]` just for help or version
+ continue;
+ }
+ }
+ if let Some(g_vec) = p.groups_for_arg(f.b.name) {
+ for grp_s in &g_vec {
+ debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s);
+ if p.groups.iter().any(|g| &g.name == grp_s && g.required) {
+ debugln!("usage::needs_flags_tag:iter:iter: Group is required");
+ continue 'outer;
+ }
+ }
+ }
+ if f.is_set(ArgSettings::Hidden) {
+ continue;
+ }
+ debugln!("usage::needs_flags_tag:iter: [FLAGS] required");
+ return true;
+ }
+
+ debugln!("usage::needs_flags_tag: [FLAGS] not required");
+ false
+}
+
+// Returns the required args in usage string form by fully unrolling all groups
+pub fn get_required_usage_from<'a, 'b>(
+ p: &Parser<'a, 'b>,
+ reqs: &[&'a str],
+ matcher: Option<&ArgMatcher<'a>>,
+ extra: Option<&str>,
+ incl_last: bool,
+) -> VecDeque<String> {
+ debugln!(
+ "usage::get_required_usage_from: reqs={:?}, extra={:?}",
+ reqs,
+ extra
+ );
+ let mut desc_reqs: Vec<&str> = vec![];
+ desc_reqs.extend(extra);
+ let mut new_reqs: Vec<&str> = vec![];
+ macro_rules! get_requires {
+ (@group $a: ident, $v:ident, $p:ident) => {{
+ if let Some(rl) = p.groups.iter()
+ .filter(|g| g.requires.is_some())
+ .find(|g| &g.name == $a)
+ .map(|g| g.requires.as_ref().unwrap()) {
+ for r in rl {
+ if !$p.contains(&r) {
+ debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}",
+ $a, r);
+ $v.push(r);
+ }
+ }
+ }
+ }};
+ ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{
+ if let Some(rl) = p.$what.$how()
+ .filter(|a| a.b.requires.is_some())
+ .find(|arg| &arg.b.name == $a)
+ .map(|a| a.b.requires.as_ref().unwrap()) {
+ for &(_, r) in rl.iter() {
+ if !$p.contains(&r) {
+ debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}",
+ $a, r);
+ $v.push(r);
+ }
+ }
+ }
+ }};
+ }
+ // initialize new_reqs
+ for a in reqs {
+ get_requires!(a, flags, iter, new_reqs, reqs);
+ get_requires!(a, opts, iter, new_reqs, reqs);
+ get_requires!(a, positionals, values, new_reqs, reqs);
+ get_requires!(@group a, new_reqs, reqs);
+ }
+ desc_reqs.extend_from_slice(&*new_reqs);
+ debugln!(
+ "usage::get_required_usage_from: after init desc_reqs={:?}",
+ desc_reqs
+ );
+ loop {
+ let mut tmp = vec![];
+ for a in &new_reqs {
+ get_requires!(a, flags, iter, tmp, desc_reqs);
+ get_requires!(a, opts, iter, tmp, desc_reqs);
+ get_requires!(a, positionals, values, tmp, desc_reqs);
+ get_requires!(@group a, tmp, desc_reqs);
+ }
+ if tmp.is_empty() {
+ debugln!("usage::get_required_usage_from: no more children");
+ break;
+ } else {
+ debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp);
+ debugln!(
+ "usage::get_required_usage_from: after iter new_reqs={:?}",
+ new_reqs
+ );
+ desc_reqs.extend_from_slice(&*new_reqs);
+ new_reqs.clear();
+ new_reqs.extend_from_slice(&*tmp);
+ debugln!(
+ "usage::get_required_usage_from: after iter desc_reqs={:?}",
+ desc_reqs
+ );
+ }
+ }
+ desc_reqs.extend_from_slice(reqs);
+ desc_reqs.sort();
+ desc_reqs.dedup();
+ debugln!(
+ "usage::get_required_usage_from: final desc_reqs={:?}",
+ desc_reqs
+ );
+ let mut ret_val = VecDeque::new();
+ let args_in_groups = p.groups
+ .iter()
+ .filter(|gn| desc_reqs.contains(&gn.name))
+ .flat_map(|g| p.arg_names_in_group(g.name))
+ .collect::<Vec<_>>();
+
+ let pmap = if let Some(m) = matcher {
+ desc_reqs
+ .iter()
+ .filter(|a| p.positionals.values().any(|p| &&p.b.name == a))
+ .filter(|&pos| !m.contains(pos))
+ .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
+ .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
+ .filter(|pos| !args_in_groups.contains(&pos.b.name))
+ .map(|pos| (pos.index, pos))
+ .collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
+ } else {
+ desc_reqs
+ .iter()
+ .filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a))
+ .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
+ .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
+ .filter(|pos| !args_in_groups.contains(&pos.b.name))
+ .map(|pos| (pos.index, pos))
+ .collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
+ };
+ debugln!(
+ "usage::get_required_usage_from: args_in_groups={:?}",
+ args_in_groups
+ );
+ for &p in pmap.values() {
+ let s = p.to_string();
+ if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) {
+ ret_val.push_back(s);
+ }
+ }
+ for a in desc_reqs
+ .iter()
+ .filter(|name| !p.positionals.values().any(|p| &&p.b.name == name))
+ .filter(|name| !p.groups.iter().any(|g| &&g.name == name))
+ .filter(|name| !args_in_groups.contains(name))
+ .filter(|name| {
+ !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))
+ }) {
+ debugln!("usage::get_required_usage_from:iter:{}:", a);
+ let arg = find_by_name!(p, *a, flags, iter)
+ .map(|f| f.to_string())
+ .unwrap_or_else(|| {
+ find_by_name!(p, *a, opts, iter)
+ .map(|o| o.to_string())
+ .expect(INTERNAL_ERROR_MSG)
+ });
+ ret_val.push_back(arg);
+ }
+ let mut g_vec: Vec<String> = vec![];
+ for g in desc_reqs
+ .iter()
+ .filter(|n| p.groups.iter().any(|g| &&g.name == n))
+ {
+ let g_string = p.args_in_group(g).join("|");
+ let elem = format!("<{}>", &g_string[..g_string.len()]);
+ if !g_vec.contains(&elem) {
+ g_vec.push(elem);
+ }
+ }
+ for g in g_vec {
+ ret_val.push_back(g);
+ }
+
+ ret_val
+}