1 #![cfg_attr(not(feature = "usage"), allow(unused_imports))] 2 #![cfg_attr(not(feature = "usage"), allow(unused_variables))] 3 #![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))] 4 #![cfg_attr(not(feature = "usage"), allow(dead_code))] 5 6 // Internal 7 use crate::builder::ArgAction; 8 use crate::builder::StyledStr; 9 use crate::builder::Styles; 10 use crate::builder::{ArgPredicate, Command}; 11 use crate::parser::ArgMatcher; 12 use crate::util::ChildGraph; 13 use crate::util::FlatSet; 14 use crate::util::Id; 15 16 static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND"; 17 const USAGE_SEP: &str = "\n "; 18 19 pub(crate) struct Usage<'cmd> { 20 cmd: &'cmd Command, 21 styles: &'cmd Styles, 22 required: Option<&'cmd ChildGraph<Id>>, 23 } 24 25 impl<'cmd> Usage<'cmd> { new(cmd: &'cmd Command) -> Self26 pub(crate) fn new(cmd: &'cmd Command) -> Self { 27 Usage { 28 cmd, 29 styles: cmd.get_styles(), 30 required: None, 31 } 32 } 33 required(mut self, required: &'cmd ChildGraph<Id>) -> Self34 pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self { 35 self.required = Some(required); 36 self 37 } 38 39 // Creates a usage string for display. This happens just after all arguments were parsed, but before 40 // any subcommands have been parsed (so as to give subcommands their own usage recursively) create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr>41 pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> { 42 debug!("Usage::create_usage_with_title"); 43 use std::fmt::Write as _; 44 let mut styled = StyledStr::new(); 45 let _ = write!( 46 styled, 47 "{}Usage:{} ", 48 self.styles.get_usage().render(), 49 self.styles.get_usage().render_reset() 50 ); 51 if self.write_usage_no_title(&mut styled, used) { 52 styled.trim_end(); 53 } else { 54 return None; 55 } 56 debug!("Usage::create_usage_with_title: usage={styled}"); 57 Some(styled) 58 } 59 60 // Creates a usage string (*without title*) if one was not provided by the user manually. create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr>61 pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> { 62 debug!("Usage::create_usage_no_title"); 63 64 let mut styled = StyledStr::new(); 65 if self.write_usage_no_title(&mut styled, used) { 66 styled.trim_end(); 67 debug!("Usage::create_usage_no_title: usage={styled}"); 68 Some(styled) 69 } else { 70 None 71 } 72 } 73 74 // Creates a usage string (*without title*) if one was not provided by the user manually. write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool75 fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool { 76 debug!("Usage::create_usage_no_title"); 77 if let Some(u) = self.cmd.get_override_usage() { 78 styled.push_styled(u); 79 true 80 } else { 81 #[cfg(feature = "usage")] 82 { 83 if used.is_empty() { 84 self.write_help_usage(styled); 85 } else { 86 self.write_smart_usage(styled, used); 87 } 88 true 89 } 90 91 #[cfg(not(feature = "usage"))] 92 { 93 false 94 } 95 } 96 } 97 } 98 99 #[cfg(feature = "usage")] 100 impl<'cmd> Usage<'cmd> { 101 // Creates a usage string for display in help messages (i.e. not for errors) write_help_usage(&self, styled: &mut StyledStr)102 fn write_help_usage(&self, styled: &mut StyledStr) { 103 debug!("Usage::write_help_usage"); 104 use std::fmt::Write; 105 106 if self.cmd.has_visible_subcommands() && self.cmd.is_flatten_help_set() { 107 if !self.cmd.is_subcommand_required_set() 108 || self.cmd.is_args_conflicts_with_subcommands_set() 109 { 110 self.write_arg_usage(styled, &[], true); 111 styled.trim_end(); 112 let _ = write!(styled, "{}", USAGE_SEP); 113 } 114 let mut cmd = self.cmd.clone(); 115 cmd.build(); 116 for (i, sub) in cmd 117 .get_subcommands() 118 .filter(|c| !c.is_hide_set()) 119 .enumerate() 120 { 121 if i != 0 { 122 styled.trim_end(); 123 let _ = write!(styled, "{}", USAGE_SEP); 124 } 125 Usage::new(sub).write_usage_no_title(styled, &[]); 126 } 127 } else { 128 self.write_arg_usage(styled, &[], true); 129 self.write_subcommand_usage(styled); 130 } 131 } 132 133 // Creates a context aware usage string, or "smart usage" from currently used 134 // args, and requirements write_smart_usage(&self, styled: &mut StyledStr, used: &[Id])135 fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) { 136 debug!("Usage::create_smart_usage"); 137 use std::fmt::Write; 138 let placeholder = &self.styles.get_placeholder(); 139 140 self.write_arg_usage(styled, used, true); 141 142 if self.cmd.is_subcommand_required_set() { 143 let value_name = self 144 .cmd 145 .get_subcommand_value_name() 146 .unwrap_or(DEFAULT_SUB_VALUE_NAME); 147 let _ = write!( 148 styled, 149 "{}<{value_name}>{}", 150 placeholder.render(), 151 placeholder.render_reset() 152 ); 153 } 154 } 155 write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool)156 fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) { 157 debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}"); 158 use std::fmt::Write as _; 159 let literal = &self.styles.get_literal(); 160 let placeholder = &self.styles.get_placeholder(); 161 162 let bin_name = self.cmd.get_usage_name_fallback(); 163 if !bin_name.is_empty() { 164 // the trim won't properly remove a leading space due to the formatting 165 let _ = write!( 166 styled, 167 "{}{bin_name}{} ", 168 literal.render(), 169 literal.render_reset() 170 ); 171 } 172 173 if used.is_empty() && self.needs_options_tag() { 174 let _ = write!( 175 styled, 176 "{}[OPTIONS]{} ", 177 placeholder.render(), 178 placeholder.render_reset() 179 ); 180 } 181 182 self.write_args(styled, used, !incl_reqs); 183 } 184 write_subcommand_usage(&self, styled: &mut StyledStr)185 fn write_subcommand_usage(&self, styled: &mut StyledStr) { 186 debug!("Usage::write_subcommand_usage"); 187 use std::fmt::Write as _; 188 189 // incl_reqs is only false when this function is called recursively 190 if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() { 191 let literal = &self.styles.get_literal(); 192 let placeholder = &self.styles.get_placeholder(); 193 let value_name = self 194 .cmd 195 .get_subcommand_value_name() 196 .unwrap_or(DEFAULT_SUB_VALUE_NAME); 197 if self.cmd.is_subcommand_negates_reqs_set() 198 || self.cmd.is_args_conflicts_with_subcommands_set() 199 { 200 styled.trim_end(); 201 let _ = write!(styled, "{}", USAGE_SEP); 202 if self.cmd.is_args_conflicts_with_subcommands_set() { 203 let bin_name = self.cmd.get_usage_name_fallback(); 204 // Short-circuit full usage creation since no args will be relevant 205 let _ = write!( 206 styled, 207 "{}{bin_name}{} ", 208 literal.render(), 209 literal.render_reset() 210 ); 211 } else { 212 self.write_arg_usage(styled, &[], false); 213 } 214 let _ = write!( 215 styled, 216 "{}<{value_name}>{}", 217 placeholder.render(), 218 placeholder.render_reset() 219 ); 220 } else if self.cmd.is_subcommand_required_set() { 221 let _ = write!( 222 styled, 223 "{}<{value_name}>{}", 224 placeholder.render(), 225 placeholder.render_reset() 226 ); 227 } else { 228 let _ = write!( 229 styled, 230 "{}[{value_name}]{}", 231 placeholder.render(), 232 placeholder.render_reset() 233 ); 234 } 235 } 236 } 237 238 // Determines if we need the `[OPTIONS]` tag in the usage string needs_options_tag(&self) -> bool239 fn needs_options_tag(&self) -> bool { 240 debug!("Usage::needs_options_tag"); 241 'outer: for f in self.cmd.get_non_positionals() { 242 debug!("Usage::needs_options_tag:iter: f={}", f.get_id()); 243 244 // Don't print `[OPTIONS]` just for help or version 245 if f.get_long() == Some("help") || f.get_long() == Some("version") { 246 debug!("Usage::needs_options_tag:iter Option is built-in"); 247 continue; 248 } 249 match f.get_action() { 250 ArgAction::Set 251 | ArgAction::Append 252 | ArgAction::SetTrue 253 | ArgAction::SetFalse 254 | ArgAction::Count => {} 255 ArgAction::Help 256 | ArgAction::HelpShort 257 | ArgAction::HelpLong 258 | ArgAction::Version => { 259 debug!("Usage::needs_options_tag:iter Option is built-in"); 260 continue; 261 } 262 } 263 264 if f.is_hide_set() { 265 debug!("Usage::needs_options_tag:iter Option is hidden"); 266 continue; 267 } 268 if f.is_required_set() { 269 debug!("Usage::needs_options_tag:iter Option is required"); 270 continue; 271 } 272 for grp_s in self.cmd.groups_for_arg(f.get_id()) { 273 debug!("Usage::needs_options_tag:iter:iter: grp_s={grp_s:?}"); 274 if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) { 275 debug!("Usage::needs_options_tag:iter:iter: Group is required"); 276 continue 'outer; 277 } 278 } 279 280 debug!("Usage::needs_options_tag:iter: [OPTIONS] required"); 281 return true; 282 } 283 284 debug!("Usage::needs_options_tag: [OPTIONS] not required"); 285 false 286 } 287 288 // Returns the required args in usage string form by fully unrolling all groups write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool)289 pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) { 290 debug!("Usage::write_args: incls={incls:?}",); 291 use std::fmt::Write as _; 292 let literal = &self.styles.get_literal(); 293 294 let required_owned; 295 let required = if let Some(required) = self.required { 296 required 297 } else { 298 required_owned = self.cmd.required_graph(); 299 &required_owned 300 }; 301 302 let mut unrolled_reqs = Vec::new(); 303 for a in required.iter() { 304 let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { 305 let required = match val { 306 ArgPredicate::Equals(_) => false, 307 ArgPredicate::IsPresent => true, 308 }; 309 required.then(|| req_arg.clone()) 310 }; 311 312 for aa in self.cmd.unroll_arg_requires(is_relevant, a) { 313 // if we don't check for duplicates here this causes duplicate error messages 314 // see https://github.com/clap-rs/clap/issues/2770 315 unrolled_reqs.push(aa); 316 } 317 // always include the required arg itself. it will not be enumerated 318 // by unroll_requirements_for_arg. 319 unrolled_reqs.push(a.clone()); 320 } 321 debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}"); 322 323 let mut required_groups_members = FlatSet::new(); 324 let mut required_groups = FlatSet::new(); 325 for req in unrolled_reqs.iter().chain(incls.iter()) { 326 if self.cmd.find_group(req).is_some() { 327 let group_members = self.cmd.unroll_args_in_group(req); 328 let elem = self.cmd.format_group(req); 329 required_groups.insert(elem); 330 required_groups_members.extend(group_members); 331 } else { 332 debug_assert!(self.cmd.find(req).is_some()); 333 } 334 } 335 336 let mut required_opts = FlatSet::new(); 337 let mut required_positionals = Vec::new(); 338 for req in unrolled_reqs.iter().chain(incls.iter()) { 339 if let Some(arg) = self.cmd.find(req) { 340 if required_groups_members.contains(arg.get_id()) { 341 continue; 342 } 343 344 let stylized = arg.stylized(self.styles, Some(!force_optional)); 345 if let Some(index) = arg.get_index() { 346 let new_len = index + 1; 347 if required_positionals.len() < new_len { 348 required_positionals.resize(new_len, None); 349 } 350 required_positionals[index] = Some(stylized); 351 } else { 352 required_opts.insert(stylized); 353 } 354 } else { 355 debug_assert!(self.cmd.find_group(req).is_some()); 356 } 357 } 358 359 for pos in self.cmd.get_positionals() { 360 if pos.is_hide_set() { 361 continue; 362 } 363 if required_groups_members.contains(pos.get_id()) { 364 continue; 365 } 366 367 let index = pos.get_index().unwrap(); 368 let new_len = index + 1; 369 if required_positionals.len() < new_len { 370 required_positionals.resize(new_len, None); 371 } 372 if required_positionals[index].is_some() { 373 if pos.is_last_set() { 374 let styled = required_positionals[index].take().unwrap(); 375 let mut new = StyledStr::new(); 376 let _ = write!(new, "{}--{} ", literal.render(), literal.render_reset()); 377 new.push_styled(&styled); 378 required_positionals[index] = Some(new); 379 } 380 } else { 381 let mut styled; 382 if pos.is_last_set() { 383 styled = StyledStr::new(); 384 let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); 385 styled.push_styled(&pos.stylized(self.styles, Some(true))); 386 let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); 387 } else { 388 styled = pos.stylized(self.styles, Some(false)); 389 } 390 required_positionals[index] = Some(styled); 391 } 392 if pos.is_last_set() && force_optional { 393 required_positionals[index] = None; 394 } 395 } 396 397 if !force_optional { 398 for arg in required_opts { 399 styled.push_styled(&arg); 400 styled.push_str(" "); 401 } 402 for arg in required_groups { 403 styled.push_styled(&arg); 404 styled.push_str(" "); 405 } 406 } 407 for arg in required_positionals.into_iter().flatten() { 408 styled.push_styled(&arg); 409 styled.push_str(" "); 410 } 411 } 412 get_required_usage_from( &self, incls: &[Id], matcher: Option<&ArgMatcher>, incl_last: bool, ) -> Vec<StyledStr>413 pub(crate) fn get_required_usage_from( 414 &self, 415 incls: &[Id], 416 matcher: Option<&ArgMatcher>, 417 incl_last: bool, 418 ) -> Vec<StyledStr> { 419 debug!( 420 "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", 421 incls, 422 matcher.is_some(), 423 incl_last 424 ); 425 426 let required_owned; 427 let required = if let Some(required) = self.required { 428 required 429 } else { 430 required_owned = self.cmd.required_graph(); 431 &required_owned 432 }; 433 434 let mut unrolled_reqs = Vec::new(); 435 for a in required.iter() { 436 let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { 437 let required = match val { 438 ArgPredicate::Equals(_) => { 439 if let Some(matcher) = matcher { 440 matcher.check_explicit(a, val) 441 } else { 442 false 443 } 444 } 445 ArgPredicate::IsPresent => true, 446 }; 447 required.then(|| req_arg.clone()) 448 }; 449 450 for aa in self.cmd.unroll_arg_requires(is_relevant, a) { 451 // if we don't check for duplicates here this causes duplicate error messages 452 // see https://github.com/clap-rs/clap/issues/2770 453 unrolled_reqs.push(aa); 454 } 455 // always include the required arg itself. it will not be enumerated 456 // by unroll_requirements_for_arg. 457 unrolled_reqs.push(a.clone()); 458 } 459 debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}"); 460 461 let mut required_groups_members = FlatSet::new(); 462 let mut required_groups = FlatSet::new(); 463 for req in unrolled_reqs.iter().chain(incls.iter()) { 464 if self.cmd.find_group(req).is_some() { 465 let group_members = self.cmd.unroll_args_in_group(req); 466 let is_present = matcher 467 .map(|m| { 468 group_members 469 .iter() 470 .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent)) 471 }) 472 .unwrap_or(false); 473 debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}"); 474 if is_present { 475 continue; 476 } 477 478 let elem = self.cmd.format_group(req); 479 required_groups.insert(elem); 480 required_groups_members.extend(group_members); 481 } else { 482 debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist"); 483 } 484 } 485 486 let mut required_opts = FlatSet::new(); 487 let mut required_positionals = Vec::new(); 488 for req in unrolled_reqs.iter().chain(incls.iter()) { 489 if let Some(arg) = self.cmd.find(req) { 490 if required_groups_members.contains(arg.get_id()) { 491 continue; 492 } 493 494 let is_present = matcher 495 .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent)) 496 .unwrap_or(false); 497 debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}"); 498 if is_present { 499 continue; 500 } 501 502 let stylized = arg.stylized(self.styles, Some(true)); 503 if let Some(index) = arg.get_index() { 504 if !arg.is_last_set() || incl_last { 505 let new_len = index + 1; 506 if required_positionals.len() < new_len { 507 required_positionals.resize(new_len, None); 508 } 509 required_positionals[index] = Some(stylized); 510 } 511 } else { 512 required_opts.insert(stylized); 513 } 514 } else { 515 debug_assert!(self.cmd.find_group(req).is_some()); 516 } 517 } 518 519 let mut ret_val = Vec::new(); 520 ret_val.extend(required_opts); 521 ret_val.extend(required_groups); 522 for pos in required_positionals.into_iter().flatten() { 523 ret_val.push(pos); 524 } 525 526 debug!("Usage::get_required_usage_from: ret_val={ret_val:?}"); 527 ret_val 528 } 529 } 530