1 use indexmap::IndexSet; 2 3 // Internal 4 use crate::builder::AppSettings as AS; 5 use crate::builder::{Arg, ArgPredicate, Command}; 6 use crate::parser::ArgMatcher; 7 use crate::util::ChildGraph; 8 use crate::util::Id; 9 use crate::INTERNAL_ERROR_MSG; 10 11 pub(crate) struct Usage<'help, 'cmd> { 12 cmd: &'cmd Command<'help>, 13 required: Option<&'cmd ChildGraph<Id>>, 14 } 15 16 impl<'help, 'cmd> Usage<'help, 'cmd> { new(cmd: &'cmd Command<'help>) -> Self17 pub(crate) fn new(cmd: &'cmd Command<'help>) -> Self { 18 Usage { 19 cmd, 20 required: None, 21 } 22 } 23 required(mut self, required: &'cmd ChildGraph<Id>) -> Self24 pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self { 25 self.required = Some(required); 26 self 27 } 28 29 // Creates a usage string for display. This happens just after all arguments were parsed, but before 30 // any subcommands have been parsed (so as to give subcommands their own usage recursively) create_usage_with_title(&self, used: &[Id]) -> String31 pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> String { 32 debug!("Usage::create_usage_with_title"); 33 let mut usage = String::with_capacity(75); 34 usage.push_str("USAGE:\n "); 35 usage.push_str(&*self.create_usage_no_title(used)); 36 usage 37 } 38 39 // Creates a usage string (*without title*) if one was not provided by the user manually. create_usage_no_title(&self, used: &[Id]) -> String40 pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> String { 41 debug!("Usage::create_usage_no_title"); 42 if let Some(u) = self.cmd.get_override_usage() { 43 String::from(&*u) 44 } else if used.is_empty() { 45 self.create_help_usage(true) 46 } else { 47 self.create_smart_usage(used) 48 } 49 } 50 51 // Creates a usage string for display in help messages (i.e. not for errors) create_help_usage(&self, incl_reqs: bool) -> String52 fn create_help_usage(&self, incl_reqs: bool) -> String { 53 debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); 54 let mut usage = String::with_capacity(75); 55 let name = self 56 .cmd 57 .get_usage_name() 58 .or_else(|| self.cmd.get_bin_name()) 59 .unwrap_or_else(|| self.cmd.get_name()); 60 usage.push_str(name); 61 let req_string = if incl_reqs { 62 self.get_required_usage_from(&[], None, false) 63 .iter() 64 .fold(String::new(), |a, s| a + " " + s) 65 } else { 66 String::new() 67 }; 68 69 if self.needs_options_tag() { 70 usage.push_str(" [OPTIONS]"); 71 } 72 73 let allow_missing_positional = self.cmd.is_allow_missing_positional_set(); 74 if !allow_missing_positional { 75 usage.push_str(&req_string); 76 } 77 78 let has_last = self.cmd.get_positionals().any(|p| p.is_last_set()); 79 // places a '--' in the usage string if there are args and options 80 // supporting multiple values 81 if self 82 .cmd 83 .get_non_positionals() 84 .any(|o| o.is_multiple_values_set()) 85 && self.cmd.get_positionals().any(|p| !p.is_required_set()) 86 && !(self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set()) 87 && !has_last 88 { 89 usage.push_str(" [--]"); 90 } 91 let not_req_or_hidden = 92 |p: &Arg| (!p.is_required_set() || p.is_last_set()) && !p.is_hide_set(); 93 if self.cmd.get_positionals().any(not_req_or_hidden) { 94 if let Some(args_tag) = self.get_args_tag(incl_reqs) { 95 usage.push_str(&*args_tag); 96 } else { 97 usage.push_str(" [ARGS]"); 98 } 99 if has_last && incl_reqs { 100 let pos = self 101 .cmd 102 .get_positionals() 103 .find(|p| p.is_last_set()) 104 .expect(INTERNAL_ERROR_MSG); 105 debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name); 106 let req = pos.is_required_set(); 107 if req && self.cmd.get_positionals().any(|p| !p.is_required_set()) { 108 usage.push_str(" -- <"); 109 } else if req { 110 usage.push_str(" [--] <"); 111 } else { 112 usage.push_str(" [-- <"); 113 } 114 usage.push_str(&*pos.name_no_brackets()); 115 usage.push('>'); 116 usage.push_str(pos.multiple_str()); 117 if !req { 118 usage.push(']'); 119 } 120 } 121 } 122 123 if allow_missing_positional { 124 usage.push_str(&req_string); 125 } 126 127 // incl_reqs is only false when this function is called recursively 128 if self.cmd.has_visible_subcommands() && incl_reqs 129 || self.cmd.is_allow_external_subcommands_set() 130 { 131 let placeholder = self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND"); 132 #[allow(deprecated)] 133 if self.cmd.is_subcommand_negates_reqs_set() 134 || self.cmd.is_args_conflicts_with_subcommands_set() 135 { 136 usage.push_str("\n "); 137 if !self.cmd.is_args_conflicts_with_subcommands_set() { 138 usage.push_str(&*self.create_help_usage(false)); 139 } else { 140 usage.push_str(&*name); 141 } 142 usage.push_str(" <"); 143 usage.push_str(placeholder); 144 usage.push('>'); 145 } else if self.cmd.is_subcommand_required_set() 146 || self.cmd.is_set(AS::SubcommandRequiredElseHelp) 147 { 148 usage.push_str(" <"); 149 usage.push_str(placeholder); 150 usage.push('>'); 151 } else { 152 usage.push_str(" ["); 153 usage.push_str(placeholder); 154 usage.push(']'); 155 } 156 } 157 let usage = usage.trim().to_owned(); 158 debug!("Usage::create_help_usage: usage={}", usage); 159 usage 160 } 161 162 // Creates a context aware usage string, or "smart usage" from currently used 163 // args, and requirements create_smart_usage(&self, used: &[Id]) -> String164 fn create_smart_usage(&self, used: &[Id]) -> String { 165 debug!("Usage::create_smart_usage"); 166 let mut usage = String::with_capacity(75); 167 168 let r_string = self 169 .get_required_usage_from(used, None, true) 170 .iter() 171 .fold(String::new(), |acc, s| acc + " " + s); 172 173 usage.push_str( 174 self.cmd 175 .get_usage_name() 176 .or_else(|| self.cmd.get_bin_name()) 177 .unwrap_or_else(|| self.cmd.get_name()), 178 ); 179 usage.push_str(&*r_string); 180 if self.cmd.is_subcommand_required_set() { 181 usage.push_str(" <"); 182 usage.push_str(self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND")); 183 usage.push('>'); 184 } 185 usage.shrink_to_fit(); 186 usage 187 } 188 189 // Gets the `[ARGS]` tag for the usage string get_args_tag(&self, incl_reqs: bool) -> Option<String>190 fn get_args_tag(&self, incl_reqs: bool) -> Option<String> { 191 debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs); 192 let mut count = 0; 193 for pos in self 194 .cmd 195 .get_positionals() 196 .filter(|pos| !pos.is_required_set()) 197 .filter(|pos| !pos.is_hide_set()) 198 .filter(|pos| !pos.is_last_set()) 199 { 200 debug!("Usage::get_args_tag:iter:{}", pos.name); 201 let required = self.cmd.groups_for_arg(&pos.id).any(|grp_s| { 202 debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); 203 // if it's part of a required group we don't want to count it 204 self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) 205 }); 206 if !required { 207 count += 1; 208 debug!( 209 "Usage::get_args_tag:iter: {} Args not required or hidden", 210 count 211 ); 212 } 213 } 214 215 if !self.cmd.is_dont_collapse_args_in_usage_set() && count > 1 { 216 debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]"); 217 218 // [ARGS] 219 None 220 } else if count == 1 && incl_reqs { 221 let pos = self 222 .cmd 223 .get_positionals() 224 .find(|pos| { 225 !pos.is_required_set() 226 && !pos.is_hide_set() 227 && !pos.is_last_set() 228 && !self.cmd.groups_for_arg(&pos.id).any(|grp_s| { 229 debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); 230 // if it's part of a required group we don't want to count it 231 self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) 232 }) 233 }) 234 .expect(INTERNAL_ERROR_MSG); 235 236 debug!( 237 "Usage::get_args_tag:iter: Exactly one, returning '{}'", 238 pos.name 239 ); 240 241 Some(format!( 242 " [{}]{}", 243 pos.name_no_brackets(), 244 pos.multiple_str() 245 )) 246 } else if self.cmd.is_dont_collapse_args_in_usage_set() 247 && self.cmd.has_positionals() 248 && incl_reqs 249 { 250 debug!("Usage::get_args_tag:iter: Don't collapse returning all"); 251 Some( 252 self.cmd 253 .get_positionals() 254 .filter(|pos| !pos.is_required_set()) 255 .filter(|pos| !pos.is_hide_set()) 256 .filter(|pos| !pos.is_last_set()) 257 .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) 258 .collect::<Vec<_>>() 259 .join(""), 260 ) 261 } else if !incl_reqs { 262 debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); 263 let highest_req_pos = self 264 .cmd 265 .get_positionals() 266 .filter_map(|pos| { 267 if pos.is_required_set() && !pos.is_last_set() { 268 Some(pos.index) 269 } else { 270 None 271 } 272 }) 273 .max() 274 .unwrap_or_else(|| Some(self.cmd.get_positionals().count())); 275 Some( 276 self.cmd 277 .get_positionals() 278 .filter(|pos| pos.index <= highest_req_pos) 279 .filter(|pos| !pos.is_required_set()) 280 .filter(|pos| !pos.is_hide_set()) 281 .filter(|pos| !pos.is_last_set()) 282 .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) 283 .collect::<Vec<_>>() 284 .join(""), 285 ) 286 } else { 287 Some("".into()) 288 } 289 } 290 291 // Determines if we need the `[OPTIONS]` tag in the usage string needs_options_tag(&self) -> bool292 fn needs_options_tag(&self) -> bool { 293 debug!("Usage::needs_options_tag"); 294 'outer: for f in self.cmd.get_non_positionals() { 295 debug!("Usage::needs_options_tag:iter: f={}", f.name); 296 297 // Don't print `[OPTIONS]` just for help or version 298 if f.long == Some("help") || f.long == Some("version") { 299 debug!("Usage::needs_options_tag:iter Option is built-in"); 300 continue; 301 } 302 303 if f.is_hide_set() { 304 debug!("Usage::needs_options_tag:iter Option is hidden"); 305 continue; 306 } 307 if f.is_required_set() { 308 debug!("Usage::needs_options_tag:iter Option is required"); 309 continue; 310 } 311 for grp_s in self.cmd.groups_for_arg(&f.id) { 312 debug!("Usage::needs_options_tag:iter:iter: grp_s={:?}", grp_s); 313 if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) { 314 debug!("Usage::needs_options_tag:iter:iter: Group is required"); 315 continue 'outer; 316 } 317 } 318 319 debug!("Usage::needs_options_tag:iter: [OPTIONS] required"); 320 return true; 321 } 322 323 debug!("Usage::needs_options_tag: [OPTIONS] not required"); 324 false 325 } 326 327 // Returns the required args in usage string form by fully unrolling all groups 328 // `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We 329 // can't do that for required usages being built for subcommands because it would look like: 330 // `prog [foo] -- [last] <subcommand>` which is totally wrong. get_required_usage_from( &self, incls: &[Id], matcher: Option<&ArgMatcher>, incl_last: bool, ) -> IndexSet<String>331 pub(crate) fn get_required_usage_from( 332 &self, 333 incls: &[Id], 334 matcher: Option<&ArgMatcher>, 335 incl_last: bool, 336 ) -> IndexSet<String> { 337 debug!( 338 "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", 339 incls, 340 matcher.is_some(), 341 incl_last 342 ); 343 let mut ret_val = IndexSet::new(); 344 345 let mut unrolled_reqs = IndexSet::new(); 346 347 let required_owned; 348 let required = if let Some(required) = self.required { 349 required 350 } else { 351 required_owned = self.cmd.required_graph(); 352 &required_owned 353 }; 354 355 for a in required.iter() { 356 let is_relevant = |(val, req_arg): &(ArgPredicate<'_>, Id)| -> Option<Id> { 357 let required = match val { 358 ArgPredicate::Equals(_) => { 359 if let Some(matcher) = matcher { 360 matcher.check_explicit(a, *val) 361 } else { 362 false 363 } 364 } 365 ArgPredicate::IsPresent => true, 366 }; 367 required.then(|| req_arg.clone()) 368 }; 369 370 for aa in self.cmd.unroll_arg_requires(is_relevant, a) { 371 // if we don't check for duplicates here this causes duplicate error messages 372 // see https://github.com/clap-rs/clap/issues/2770 373 unrolled_reqs.insert(aa); 374 } 375 // always include the required arg itself. it will not be enumerated 376 // by unroll_requirements_for_arg. 377 unrolled_reqs.insert(a.clone()); 378 } 379 380 debug!( 381 "Usage::get_required_usage_from: unrolled_reqs={:?}", 382 unrolled_reqs 383 ); 384 385 let mut required_groups_members = IndexSet::new(); 386 let mut required_opts = IndexSet::new(); 387 let mut required_groups = IndexSet::new(); 388 let mut required_positionals = Vec::new(); 389 for req in unrolled_reqs.iter().chain(incls.iter()) { 390 if let Some(arg) = self.cmd.find(req) { 391 let is_present = matcher 392 .map(|m| m.check_explicit(req, ArgPredicate::IsPresent)) 393 .unwrap_or(false); 394 debug!( 395 "Usage::get_required_usage_from:iter:{:?} arg is_present={}", 396 req, is_present 397 ); 398 if !is_present { 399 if arg.is_positional() { 400 if incl_last || !arg.is_last_set() { 401 required_positionals.push((arg.index.unwrap(), arg.to_string())); 402 } 403 } else { 404 required_opts.insert(arg.to_string()); 405 } 406 } 407 } else { 408 debug_assert!(self.cmd.find_group(req).is_some()); 409 let group_members = self.cmd.unroll_args_in_group(req); 410 let is_present = matcher 411 .map(|m| { 412 group_members 413 .iter() 414 .any(|arg| m.check_explicit(arg, ArgPredicate::IsPresent)) 415 }) 416 .unwrap_or(false); 417 debug!( 418 "Usage::get_required_usage_from:iter:{:?} group is_present={}", 419 req, is_present 420 ); 421 if !is_present { 422 let elem = self.cmd.format_group(req); 423 required_groups.insert(elem); 424 required_groups_members.extend( 425 group_members 426 .iter() 427 .flat_map(|id| self.cmd.find(id)) 428 .map(|arg| arg.to_string()), 429 ); 430 } 431 } 432 } 433 434 required_opts.retain(|arg| !required_groups_members.contains(arg)); 435 ret_val.extend(required_opts); 436 437 ret_val.extend(required_groups); 438 439 required_positionals.sort_by_key(|(ind, _)| *ind); // sort by index 440 for (_, p) in required_positionals { 441 if !required_groups_members.contains(&p) { 442 ret_val.insert(p); 443 } 444 } 445 446 debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val); 447 ret_val 448 } 449 } 450