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