| 1 | // Internal |
| 2 | use crate::builder::StyledStr; |
| 3 | use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue}; |
| 4 | use crate::error::{Error, Result as ClapResult}; |
| 5 | use crate::output::Usage; |
| 6 | use crate::parser::ArgMatcher; |
| 7 | use crate::util::ChildGraph; |
| 8 | use crate::util::FlatMap; |
| 9 | use crate::util::FlatSet; |
| 10 | use crate::util::Id; |
| 11 | use crate::INTERNAL_ERROR_MSG; |
| 12 | |
| 13 | pub(crate) struct Validator<'cmd> { |
| 14 | cmd: &'cmd Command, |
| 15 | required: ChildGraph<Id>, |
| 16 | } |
| 17 | |
| 18 | impl<'cmd> Validator<'cmd> { |
| 19 | pub(crate) fn new(cmd: &'cmd Command) -> Self { |
| 20 | let required = cmd.required_graph(); |
| 21 | Validator { cmd, required } |
| 22 | } |
| 23 | |
| 24 | pub(crate) fn validate(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> { |
| 25 | debug!("Validator::validate" ); |
| 26 | let conflicts = Conflicts::with_args(self.cmd, matcher); |
| 27 | let has_subcmd = matcher.subcommand_name().is_some(); |
| 28 | |
| 29 | if !has_subcmd && self.cmd.is_arg_required_else_help_set() { |
| 30 | let num_user_values = matcher |
| 31 | .args() |
| 32 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 33 | .count(); |
| 34 | if num_user_values == 0 { |
| 35 | let message = self.cmd.write_help_err(false); |
| 36 | return Err(Error::display_help_error(self.cmd, message)); |
| 37 | } |
| 38 | } |
| 39 | if !has_subcmd && self.cmd.is_subcommand_required_set() { |
| 40 | let bn = self.cmd.get_bin_name_fallback(); |
| 41 | return Err(Error::missing_subcommand( |
| 42 | self.cmd, |
| 43 | bn.to_string(), |
| 44 | self.cmd |
| 45 | .all_subcommand_names() |
| 46 | .map(|s| s.to_owned()) |
| 47 | .collect::<Vec<_>>(), |
| 48 | Usage::new(self.cmd) |
| 49 | .required(&self.required) |
| 50 | .create_usage_with_title(&[]), |
| 51 | )); |
| 52 | } |
| 53 | |
| 54 | ok!(self.validate_conflicts(matcher, &conflicts)); |
| 55 | if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) { |
| 56 | ok!(self.validate_required(matcher, &conflicts)); |
| 57 | } |
| 58 | |
| 59 | Ok(()) |
| 60 | } |
| 61 | |
| 62 | fn validate_conflicts( |
| 63 | &mut self, |
| 64 | matcher: &ArgMatcher, |
| 65 | conflicts: &Conflicts, |
| 66 | ) -> ClapResult<()> { |
| 67 | debug!("Validator::validate_conflicts" ); |
| 68 | |
| 69 | ok!(self.validate_exclusive(matcher)); |
| 70 | |
| 71 | for (arg_id, _) in matcher |
| 72 | .args() |
| 73 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 74 | .filter(|(arg_id, _)| self.cmd.find(arg_id).is_some()) |
| 75 | { |
| 76 | debug!("Validator::validate_conflicts::iter: id={arg_id:?}" ); |
| 77 | let conflicts = conflicts.gather_conflicts(self.cmd, arg_id); |
| 78 | ok!(self.build_conflict_err(arg_id, &conflicts, matcher)); |
| 79 | } |
| 80 | |
| 81 | Ok(()) |
| 82 | } |
| 83 | |
| 84 | fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> { |
| 85 | debug!("Validator::validate_exclusive" ); |
| 86 | let args_count = matcher |
| 87 | .args() |
| 88 | .filter(|(arg_id, matched)| { |
| 89 | matched.check_explicit(&ArgPredicate::IsPresent) |
| 90 | // Avoid including our own groups by checking none of them. If a group is present, the |
| 91 | // args for the group will be. |
| 92 | && self.cmd.find(arg_id).is_some() |
| 93 | }) |
| 94 | .count(); |
| 95 | if args_count <= 1 { |
| 96 | // Nothing present to conflict with |
| 97 | return Ok(()); |
| 98 | } |
| 99 | |
| 100 | matcher |
| 101 | .args() |
| 102 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 103 | .find_map(|(id, _)| { |
| 104 | debug!("Validator::validate_exclusive:iter:{id:?}" ); |
| 105 | self.cmd |
| 106 | .find(id) |
| 107 | // Find `arg`s which are exclusive but also appear with other args. |
| 108 | .filter(|&arg| arg.is_exclusive_set() && args_count > 1) |
| 109 | }) |
| 110 | .map(|arg| { |
| 111 | // Throw an error for the first conflict found. |
| 112 | Err(Error::argument_conflict( |
| 113 | self.cmd, |
| 114 | arg.to_string(), |
| 115 | Vec::new(), |
| 116 | Usage::new(self.cmd) |
| 117 | .required(&self.required) |
| 118 | .create_usage_with_title(&[]), |
| 119 | )) |
| 120 | }) |
| 121 | .unwrap_or(Ok(())) |
| 122 | } |
| 123 | |
| 124 | fn build_conflict_err( |
| 125 | &self, |
| 126 | name: &Id, |
| 127 | conflict_ids: &[Id], |
| 128 | matcher: &ArgMatcher, |
| 129 | ) -> ClapResult<()> { |
| 130 | if conflict_ids.is_empty() { |
| 131 | return Ok(()); |
| 132 | } |
| 133 | |
| 134 | debug!("Validator::build_conflict_err: name={name:?}" ); |
| 135 | let mut seen = FlatSet::new(); |
| 136 | let conflicts = conflict_ids |
| 137 | .iter() |
| 138 | .flat_map(|c_id| { |
| 139 | if self.cmd.find_group(c_id).is_some() { |
| 140 | self.cmd.unroll_args_in_group(c_id) |
| 141 | } else { |
| 142 | vec![c_id.clone()] |
| 143 | } |
| 144 | }) |
| 145 | .filter_map(|c_id| { |
| 146 | seen.insert(c_id.clone()).then(|| { |
| 147 | let c_arg = self.cmd.find(&c_id).expect(INTERNAL_ERROR_MSG); |
| 148 | c_arg.to_string() |
| 149 | }) |
| 150 | }) |
| 151 | .collect(); |
| 152 | |
| 153 | let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG); |
| 154 | let usg = self.build_conflict_err_usage(matcher, conflict_ids); |
| 155 | Err(Error::argument_conflict( |
| 156 | self.cmd, |
| 157 | former_arg.to_string(), |
| 158 | conflicts, |
| 159 | usg, |
| 160 | )) |
| 161 | } |
| 162 | |
| 163 | fn build_conflict_err_usage( |
| 164 | &self, |
| 165 | matcher: &ArgMatcher, |
| 166 | conflicting_keys: &[Id], |
| 167 | ) -> Option<StyledStr> { |
| 168 | let used_filtered: Vec<Id> = matcher |
| 169 | .args() |
| 170 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 171 | .map(|(n, _)| n) |
| 172 | .filter(|n| { |
| 173 | // Filter out the args we don't want to specify. |
| 174 | self.cmd |
| 175 | .find(n) |
| 176 | .map(|a| !a.is_hide_set()) |
| 177 | .unwrap_or_default() |
| 178 | }) |
| 179 | .filter(|key| !conflicting_keys.contains(key)) |
| 180 | .cloned() |
| 181 | .collect(); |
| 182 | let required: Vec<Id> = used_filtered |
| 183 | .iter() |
| 184 | .filter_map(|key| self.cmd.find(key)) |
| 185 | .flat_map(|arg| arg.requires.iter().map(|item| &item.1)) |
| 186 | .filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key)) |
| 187 | .chain(used_filtered.iter()) |
| 188 | .cloned() |
| 189 | .collect(); |
| 190 | Usage::new(self.cmd) |
| 191 | .required(&self.required) |
| 192 | .create_usage_with_title(&required) |
| 193 | } |
| 194 | |
| 195 | fn gather_requires(&mut self, matcher: &ArgMatcher) { |
| 196 | debug!("Validator::gather_requires" ); |
| 197 | for (name, matched) in matcher |
| 198 | .args() |
| 199 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 200 | { |
| 201 | debug!("Validator::gather_requires:iter:{name:?}" ); |
| 202 | if let Some(arg) = self.cmd.find(name) { |
| 203 | let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { |
| 204 | let required = matched.check_explicit(val); |
| 205 | required.then(|| req_arg.clone()) |
| 206 | }; |
| 207 | |
| 208 | for req in self.cmd.unroll_arg_requires(is_relevant, arg.get_id()) { |
| 209 | self.required.insert(req); |
| 210 | } |
| 211 | } else if let Some(g) = self.cmd.find_group(name) { |
| 212 | debug!("Validator::gather_requires:iter:{name:?}:group" ); |
| 213 | for r in &g.requires { |
| 214 | self.required.insert(r.clone()); |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> { |
| 221 | debug!("Validator::validate_required: required={:?}" , self.required); |
| 222 | self.gather_requires(matcher); |
| 223 | |
| 224 | let mut missing_required = Vec::new(); |
| 225 | let mut highest_index = 0; |
| 226 | |
| 227 | let is_exclusive_present = matcher |
| 228 | .args() |
| 229 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 230 | .any(|(id, _)| { |
| 231 | self.cmd |
| 232 | .find(id) |
| 233 | .map(|arg| arg.is_exclusive_set()) |
| 234 | .unwrap_or_default() |
| 235 | }); |
| 236 | debug!("Validator::validate_required: is_exclusive_present={is_exclusive_present}" ); |
| 237 | |
| 238 | for arg_or_group in self |
| 239 | .required |
| 240 | .iter() |
| 241 | .filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent)) |
| 242 | { |
| 243 | debug!("Validator::validate_required:iter:aog={arg_or_group:?}" ); |
| 244 | if let Some(arg) = self.cmd.find(arg_or_group) { |
| 245 | debug!("Validator::validate_required:iter: This is an arg" ); |
| 246 | if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) { |
| 247 | debug!( |
| 248 | "Validator::validate_required:iter: Missing {:?}" , |
| 249 | arg.get_id() |
| 250 | ); |
| 251 | missing_required.push(arg.get_id().clone()); |
| 252 | if !arg.is_last_set() { |
| 253 | highest_index = highest_index.max(arg.get_index().unwrap_or(0)); |
| 254 | } |
| 255 | } |
| 256 | } else if let Some(group) = self.cmd.find_group(arg_or_group) { |
| 257 | debug!("Validator::validate_required:iter: This is a group" ); |
| 258 | if !self |
| 259 | .cmd |
| 260 | .unroll_args_in_group(&group.id) |
| 261 | .iter() |
| 262 | .any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent)) |
| 263 | { |
| 264 | debug!( |
| 265 | "Validator::validate_required:iter: Missing {:?}" , |
| 266 | group.get_id() |
| 267 | ); |
| 268 | missing_required.push(group.get_id().clone()); |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | // Validate the conditionally required args |
| 274 | for a in self |
| 275 | .cmd |
| 276 | .get_arguments() |
| 277 | .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent)) |
| 278 | { |
| 279 | let mut required = false; |
| 280 | |
| 281 | for (other, val) in &a.r_ifs { |
| 282 | if matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) { |
| 283 | debug!( |
| 284 | "Validator::validate_required:iter: Missing {:?}" , |
| 285 | a.get_id() |
| 286 | ); |
| 287 | required = true; |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | let match_all = a.r_ifs_all.iter().all(|(other, val)| { |
| 292 | matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) |
| 293 | }); |
| 294 | if match_all && !a.r_ifs_all.is_empty() { |
| 295 | debug!( |
| 296 | "Validator::validate_required:iter: Missing {:?}" , |
| 297 | a.get_id() |
| 298 | ); |
| 299 | required = true; |
| 300 | } |
| 301 | |
| 302 | if (!a.r_unless.is_empty() || !a.r_unless_all.is_empty()) |
| 303 | && self.fails_arg_required_unless(a, matcher) |
| 304 | { |
| 305 | debug!( |
| 306 | "Validator::validate_required:iter: Missing {:?}" , |
| 307 | a.get_id() |
| 308 | ); |
| 309 | required = true; |
| 310 | } |
| 311 | |
| 312 | if !is_exclusive_present && required { |
| 313 | missing_required.push(a.get_id().clone()); |
| 314 | if !a.is_last_set() { |
| 315 | highest_index = highest_index.max(a.get_index().unwrap_or(0)); |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // For display purposes, include all of the preceding positional arguments |
| 321 | if !self.cmd.is_allow_missing_positional_set() { |
| 322 | for pos in self |
| 323 | .cmd |
| 324 | .get_positionals() |
| 325 | .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent)) |
| 326 | { |
| 327 | if pos.get_index() < Some(highest_index) { |
| 328 | debug!( |
| 329 | "Validator::validate_required:iter: Missing {:?}" , |
| 330 | pos.get_id() |
| 331 | ); |
| 332 | missing_required.push(pos.get_id().clone()); |
| 333 | } |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | if !missing_required.is_empty() { |
| 338 | ok!(self.missing_required_error(matcher, missing_required)); |
| 339 | } |
| 340 | |
| 341 | Ok(()) |
| 342 | } |
| 343 | |
| 344 | fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool { |
| 345 | debug!("Validator::is_missing_required_ok: {}" , a.get_id()); |
| 346 | if !conflicts.gather_conflicts(self.cmd, a.get_id()).is_empty() { |
| 347 | debug!("Validator::is_missing_required_ok: true (self)" ); |
| 348 | return true; |
| 349 | } |
| 350 | for group_id in self.cmd.groups_for_arg(a.get_id()) { |
| 351 | if !conflicts.gather_conflicts(self.cmd, &group_id).is_empty() { |
| 352 | debug!("Validator::is_missing_required_ok: true ({group_id})" ); |
| 353 | return true; |
| 354 | } |
| 355 | } |
| 356 | false |
| 357 | } |
| 358 | |
| 359 | // Failing a required unless means, the arg's "unless" wasn't present, and neither were they |
| 360 | fn fails_arg_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> bool { |
| 361 | debug!("Validator::fails_arg_required_unless: a={:?}" , a.get_id()); |
| 362 | let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent); |
| 363 | |
| 364 | (a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists)) |
| 365 | && !a.r_unless.iter().any(exists) |
| 366 | } |
| 367 | |
| 368 | // `req_args`: an arg to include in the error even if not used |
| 369 | fn missing_required_error( |
| 370 | &self, |
| 371 | matcher: &ArgMatcher, |
| 372 | raw_req_args: Vec<Id>, |
| 373 | ) -> ClapResult<()> { |
| 374 | debug!("Validator::missing_required_error; incl={raw_req_args:?}" ); |
| 375 | debug!( |
| 376 | "Validator::missing_required_error: reqs={:?}" , |
| 377 | self.required |
| 378 | ); |
| 379 | |
| 380 | let usg = Usage::new(self.cmd).required(&self.required); |
| 381 | |
| 382 | let req_args = { |
| 383 | #[cfg (feature = "usage" )] |
| 384 | { |
| 385 | usg.get_required_usage_from(&raw_req_args, Some(matcher), true) |
| 386 | .into_iter() |
| 387 | .map(|s| s.to_string()) |
| 388 | .collect::<Vec<_>>() |
| 389 | } |
| 390 | |
| 391 | #[cfg (not(feature = "usage" ))] |
| 392 | { |
| 393 | raw_req_args |
| 394 | .iter() |
| 395 | .map(|id| { |
| 396 | if let Some(arg) = self.cmd.find(id) { |
| 397 | arg.to_string() |
| 398 | } else if let Some(_group) = self.cmd.find_group(id) { |
| 399 | self.cmd.format_group(id).to_string() |
| 400 | } else { |
| 401 | debug_assert!(false, "id={id:?} is unknown" ); |
| 402 | "" .to_owned() |
| 403 | } |
| 404 | }) |
| 405 | .collect::<FlatSet<_>>() |
| 406 | .into_iter() |
| 407 | .collect::<Vec<_>>() |
| 408 | } |
| 409 | }; |
| 410 | |
| 411 | debug!("Validator::missing_required_error: req_args={req_args:#?}" ); |
| 412 | |
| 413 | let used: Vec<Id> = matcher |
| 414 | .args() |
| 415 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 416 | .map(|(n, _)| n) |
| 417 | .filter(|n| { |
| 418 | // Filter out the args we don't want to specify. |
| 419 | self.cmd |
| 420 | .find(n) |
| 421 | .map(|a| !a.is_hide_set()) |
| 422 | .unwrap_or_default() |
| 423 | }) |
| 424 | .cloned() |
| 425 | .chain(raw_req_args) |
| 426 | .collect(); |
| 427 | |
| 428 | Err(Error::missing_required_argument( |
| 429 | self.cmd, |
| 430 | req_args, |
| 431 | usg.create_usage_with_title(&used), |
| 432 | )) |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | #[derive (Default, Clone, Debug)] |
| 437 | struct Conflicts { |
| 438 | potential: FlatMap<Id, Vec<Id>>, |
| 439 | } |
| 440 | |
| 441 | impl Conflicts { |
| 442 | fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self { |
| 443 | let mut potential = FlatMap::new(); |
| 444 | potential.extend_unchecked( |
| 445 | matcher |
| 446 | .args() |
| 447 | .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) |
| 448 | .map(|(id, _)| { |
| 449 | let conf = gather_direct_conflicts(cmd, id); |
| 450 | (id.clone(), conf) |
| 451 | }), |
| 452 | ); |
| 453 | Self { potential } |
| 454 | } |
| 455 | |
| 456 | fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec<Id> { |
| 457 | debug!("Conflicts::gather_conflicts: arg={arg_id:?}" ); |
| 458 | let mut conflicts = Vec::new(); |
| 459 | |
| 460 | let arg_id_conflicts_storage; |
| 461 | let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) { |
| 462 | arg_id_conflicts |
| 463 | } else { |
| 464 | // `is_missing_required_ok` is a case where we check not-present args for conflicts |
| 465 | arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id); |
| 466 | &arg_id_conflicts_storage |
| 467 | }; |
| 468 | for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() { |
| 469 | if arg_id == other_arg_id { |
| 470 | continue; |
| 471 | } |
| 472 | |
| 473 | if arg_id_conflicts.contains(other_arg_id) { |
| 474 | conflicts.push(other_arg_id.clone()); |
| 475 | } |
| 476 | if other_arg_id_conflicts.contains(arg_id) { |
| 477 | conflicts.push(other_arg_id.clone()); |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | debug!("Conflicts::gather_conflicts: conflicts={conflicts:?}" ); |
| 482 | conflicts |
| 483 | } |
| 484 | |
| 485 | fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> { |
| 486 | self.potential.get(arg_id).map(Vec::as_slice) |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec<Id> { |
| 491 | let conf: Vec = if let Some(arg: &Arg) = cmd.find(id) { |
| 492 | gather_arg_direct_conflicts(cmd, arg) |
| 493 | } else if let Some(group: &ArgGroup) = cmd.find_group(id) { |
| 494 | gather_group_direct_conflicts(group) |
| 495 | } else { |
| 496 | debug_assert!(false, "id= {id:?} is unknown" ); |
| 497 | Vec::new() |
| 498 | }; |
| 499 | debug!("Conflicts::gather_direct_conflicts id={id:?}, conflicts={conf:?}" ,); |
| 500 | conf |
| 501 | } |
| 502 | |
| 503 | fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec<Id> { |
| 504 | let mut conf: Vec = arg.blacklist.clone(); |
| 505 | for group_id: Id in cmd.groups_for_arg(arg.get_id()) { |
| 506 | let group: &ArgGroup = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG); |
| 507 | conf.extend(iter:group.conflicts.iter().cloned()); |
| 508 | if !group.multiple { |
| 509 | for member_id: &Id in &group.args { |
| 510 | if member_id != arg.get_id() { |
| 511 | conf.push(member_id.clone()); |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | // Overrides are implicitly conflicts |
| 518 | conf.extend(iter:arg.overrides.iter().cloned()); |
| 519 | |
| 520 | conf |
| 521 | } |
| 522 | |
| 523 | fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec<Id> { |
| 524 | group.conflicts.clone() |
| 525 | } |
| 526 | |
| 527 | pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec<PossibleValue> { |
| 528 | if !a.is_takes_value_set() { |
| 529 | vec![] |
| 530 | } else { |
| 531 | aOption>.get_value_parser() |
| 532 | .possible_values() |
| 533 | .map(|pvs: Box>| pvs.collect()) |
| 534 | .unwrap_or_default() |
| 535 | } |
| 536 | } |
| 537 | |