fix(positional args): all previous positional args become required when

a latter one is required

Closes #50
This commit is contained in:
Kevin K 2015-04-02 20:04:52 -04:00
parent 1a1ce2a7da
commit 343d47dcbf

View file

@ -55,6 +55,7 @@ pub struct App<'a, 'v, 'ab, 'u, 'ar> {
needs_short_version: bool, needs_short_version: bool,
needs_subcmd_help: bool, needs_subcmd_help: bool,
required: HashSet<&'ar str>, required: HashSet<&'ar str>,
matched_reqs: HashSet<&'ar str>,
arg_list: HashSet<&'ar str>, arg_list: HashSet<&'ar str>,
short_list: HashSet<char>, short_list: HashSet<char>,
long_list: HashSet<&'ar str>, long_list: HashSet<&'ar str>,
@ -91,6 +92,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
needs_subcmd_help: true, needs_subcmd_help: true,
needs_short_version: true, needs_short_version: true,
required: HashSet::new(), required: HashSet::new(),
matched_reqs: HashSet::new(),
arg_list: HashSet::new(), arg_list: HashSet::new(),
short_list: HashSet::new(), short_list: HashSet::new(),
long_list: HashSet::new(), long_list: HashSet::new(),
@ -216,6 +218,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if a.takes_value { if a.takes_value {
panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name); panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name);
} }
// Create the Positional Arguemnt Builder with each HashSet = None to only allocate those that require it // Create the Positional Arguemnt Builder with each HashSet = None to only allocate those that require it
let mut pb = PosBuilder { let mut pb = PosBuilder {
name: a.name, name: a.name,
@ -238,7 +241,8 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if let Some(ref r) = a.requires { if let Some(ref r) = a.requires {
let mut rhs = HashSet::new(); let mut rhs = HashSet::new();
// without derefing n = &&str // without derefing n = &&str
for n in r { rhs.insert(*n); } for n in r {
rhs.insert(*n); }
pb.requires = Some(rhs); pb.requires = Some(rhs);
} }
// Check if there is anything in the possible values and add those as well // Check if there is anything in the possible values and add those as well
@ -409,16 +413,31 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
} }
fn print_usage(&self, more_info: bool) { fn print_usage(&self, more_info: bool) {
let tab = " ";
println!("USAGE:"); println!("USAGE:");
if let Some(u) = self.usage_str { if let Some(u) = self.usage_str {
println!(" {}",u); println!("{}{}",tab,u);
} else { } else {
let flags = !self.flags.is_empty(); let flags = !self.flags.is_empty();
let pos = !self.positionals_idx.is_empty(); let pos = !self.positionals_idx.is_empty();
let opts = !self.opts.is_empty(); let opts = !self.opts.is_empty();
let subcmds = !self.subcommands.is_empty(); let subcmds = !self.subcommands.is_empty();
let mut num_req_pos = 0; let mut num_req_pos = 0;
let req_pos = self.positionals_idx.values().filter_map(|ref x| if x.required || self.required.contains(x.name) { let mut matched_pos_reqs = HashSet::new();
// If it's required we also need to ensure all previous positionals are required too
let mut found = false;
for p in self.positionals_idx.values().rev() {
if found {
matched_pos_reqs.insert(p.name);
continue;
}
if self.matched_reqs.contains(p.name) {
matched_pos_reqs.insert(p.name);
found = true;
}
}
let req_pos = self.positionals_idx.values().filter_map(|ref x| if x.required || matched_pos_reqs.contains(x.name) {
num_req_pos += 1; num_req_pos += 1;
if x.multiple { if x.multiple {
Some(format!("<{}>...", x.name)) Some(format!("<{}>...", x.name))
@ -430,7 +449,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
}) })
.fold(String::new(), |acc, ref name| acc + &format!("{} ", name)[..]); .fold(String::new(), |acc, ref name| acc + &format!("{} ", name)[..]);
let mut num_req_opts = 0; let mut num_req_opts = 0;
let req_opts = self.opts.values().filter_map(|x| if x.required || self.required.contains(x.name) { let req_opts = self.opts.values().filter_map(|x| if x.required || self.matched_reqs.contains(x.name) {
num_req_opts += 1; num_req_opts += 1;
Some(x) Some(x)
}else { }else {
@ -442,7 +461,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
format!("{} ",o.short.unwrap()) format!("{} ",o.short.unwrap())
},o.name)); },o.name));
print!(" {} {} {} {} {}", self.bin_name.clone().unwrap_or(self.name.clone()), print!("{}{} {} {} {} {}",tab, self.bin_name.clone().unwrap_or(self.name.clone()),
if flags {"[FLAGS]"} else {""}, if flags {"[FLAGS]"} else {""},
if opts { if opts {
if num_req_opts != self.opts.len() && !req_opts.is_empty() { if num_req_opts != self.opts.len() && !req_opts.is_empty() {
@ -653,7 +672,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
pub fn get_matches(mut self) -> ArgMatches<'ar> { pub fn get_matches(mut self) -> ArgMatches<'ar> {
self.verify_positionals(); self.verify_positionals();
for sc in self.subcommands.values() { for (_,sc) in self.subcommands.iter_mut() {
sc.verify_positionals(); sc.verify_positionals();
} }
@ -674,7 +693,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
matches matches
} }
fn verify_positionals(&self) { fn verify_positionals(&mut self) {
// Because you must wait until all arguments have been supplied, this is the first chance // Because you must wait until all arguments have been supplied, this is the first chance
// to make assertions on positional argument indexes // to make assertions on positional argument indexes
// //
@ -695,6 +714,19 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
panic!("Found positional argument \"{}\" which accepts multiple values but it's not the last positional argument (i.e. others have a higher index)", panic!("Found positional argument \"{}\" which accepts multiple values but it's not the last positional argument (i.e. others have a higher index)",
p.name); p.name);
} }
// If it's required we also need to ensure all previous positionals are required too
let mut found = false;
for (_, p) in self.positionals_idx.iter_mut().rev() {
if found {
p.required = true;
self.required.insert(p.name);
continue;
}
if p.required {
found = true;
}
}
} }
fn get_matches_from(&mut self, matches: &mut ArgMatches<'ar>, it: &mut IntoIter<String>) { fn get_matches_from(&mut self, matches: &mut ArgMatches<'ar>, it: &mut IntoIter<String>) {
@ -738,6 +770,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
needs_val_of = None; needs_val_of = None;
continue; continue;
} }
if arg_slice.starts_with("--") && !pos_only { if arg_slice.starts_with("--") && !pos_only {
if arg_slice.len() == 2 { if arg_slice.len() == 2 {
pos_only = true; pos_only = true;
@ -762,6 +795,9 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
format!("Found positional argument {}, but {} doesn't accept any", arg, self.name), format!("Found positional argument {}, but {} doesn't accept any", arg, self.name),
true, true); true, true);
} }
// If we find that an argument requires a positiona, we need to update all the
// previous positionals too. This will denote where to start
// let mut req_pos_from_name = None;
if let Some(p) = self.positionals_idx.get(&pos_counter) { if let Some(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),
@ -806,13 +842,16 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
self.required.remove(name); self.required.remove(name);
} }
} }
if self.required.contains(p.name) {
// No need to check for existance, returns None if not found
// if self.required.contains(p.name) {
self.required.remove(p.name); self.required.remove(p.name);
} // }
if let Some(ref reqs) = p.requires { if let Some(ref reqs) = p.requires {
// Add all required args which aren't already found in matches to the // Add all required args which aren't already found in matches to the
// final required list // final required list
for n in reqs { for n in reqs {
self.matched_reqs.insert(n);
if matches.positionals.contains_key(n) {continue;} if matches.positionals.contains_key(n) {continue;}
if matches.opts.contains_key(n) {continue;} if matches.opts.contains_key(n) {continue;}
if matches.flags.contains_key(n) {continue;} if matches.flags.contains_key(n) {continue;}
@ -955,13 +994,15 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
self.required.remove(name); self.required.remove(name);
} }
} }
if self.required.contains(v.name) { // No need to check for existance, returns None if not found
// if self.required.contains(v.name) {
self.required.remove(v.name); self.required.remove(v.name);
} // }
if let Some(ref reqs) = v.requires { if let Some(ref reqs) = v.requires {
// Add all required args which aren't already found in matches to the // Add all required args which aren't already found in matches to the
// final required list // final required list
for n in reqs { for n in reqs {
self.matched_reqs.insert(n);
if matches.opts.contains_key(n) { continue; } if matches.opts.contains_key(n) { continue; }
if matches.flags.contains_key(n) { continue; } if matches.flags.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; } if matches.positionals.contains_key(n) { continue; }
@ -1002,9 +1043,10 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
// If this flag was requierd, remove it // If this flag was requierd, remove it
// .. even though Flags shouldn't be required // .. even though Flags shouldn't be required
if self.required.contains(v.name) { // No need to check for existance, returns None if not found
// if self.required.contains(v.name) {
self.required.remove(v.name); self.required.remove(v.name);
} // }
// Add all of this flags "mutually excludes" list to the master list // Add all of this flags "mutually excludes" list to the master list
if let Some(ref bl) = v.blacklist { if let Some(ref bl) = v.blacklist {
@ -1017,6 +1059,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
// Add all required args which aren't already found in matches to the master list // Add all required args which aren't already found in matches to the master list
if let Some(ref reqs) = v.requires { if let Some(ref reqs) = v.requires {
for n in reqs { for n in reqs {
self.matched_reqs.insert(n);
if matches.flags.contains_key(n) { continue; } if matches.flags.contains_key(n) { continue; }
if matches.opts.contains_key(n) { continue; } if matches.opts.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; } if matches.positionals.contains_key(n) { continue; }
@ -1080,13 +1123,15 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
self.required.remove(name); self.required.remove(name);
} }
} }
if self.required.contains(v.name) { // No need to check for existance, returns None if not found
// if self.required.contains(v.name) {
self.required.remove(v.name); self.required.remove(v.name);
} // }
if let Some(ref reqs) = v.requires { if let Some(ref reqs) = v.requires {
// Add all required args which aren't already found in matches to the // Add all required args which aren't already found in matches to the
// final required list // final required list
for n in reqs { for n in reqs {
self.matched_reqs.insert(n);
if matches.opts.contains_key(n) { continue; } if matches.opts.contains_key(n) { continue; }
if matches.flags.contains_key(n) { continue; } if matches.flags.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; } if matches.positionals.contains_key(n) { continue; }
@ -1130,9 +1175,11 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
// If this flag was requierd, remove it // If this flag was requierd, remove it
// .. even though Flags shouldn't be required // .. even though Flags shouldn't be required
if self.required.contains(v.name) {
// No need to check for existance, returns None if it didn't exist
// if self.required.contains(v.name) {
self.required.remove(v.name); self.required.remove(v.name);
} // }
// Add all of this flags "mutually excludes" list to the master list // Add all of this flags "mutually excludes" list to the master list
if let Some(ref bl) = v.blacklist { if let Some(ref bl) = v.blacklist {
@ -1145,6 +1192,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
// Add all required args which aren't already found in matches to the master list // Add all required args which aren't already found in matches to the master list
if let Some(ref reqs) = v.requires { if let Some(ref reqs) = v.requires {
for n in reqs { for n in reqs {
self.matched_reqs.insert(n);
if matches.flags.contains_key(n) { continue; } if matches.flags.contains_key(n) { continue; }
if matches.opts.contains_key(n) { continue; } if matches.opts.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; } if matches.positionals.contains_key(n) { continue; }