xref: /third_party/rust/crates/clap/src/error/format.rs (revision 19625d8c)
1#![allow(missing_copy_implementations)]
2#![allow(missing_debug_implementations)]
3#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
4#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
5
6use crate::builder::Command;
7use crate::builder::StyledStr;
8#[cfg(feature = "error-context")]
9use crate::error::ContextKind;
10#[cfg(feature = "error-context")]
11use crate::error::ContextValue;
12use crate::error::ErrorKind;
13use crate::output::TAB;
14
15/// Defines how to format an error for displaying to the user
16pub trait ErrorFormatter: Sized {
17    /// Stylize the error for the terminal
18    fn format_error(error: &crate::error::Error<Self>) -> StyledStr;
19}
20
21/// Report [`ErrorKind`]
22///
23/// No context is included.
24///
25/// **NOTE:** Consider removing the [`error-context`][crate::_features] default feature if using this to remove all
26/// overhead for [`RichFormatter`].
27#[non_exhaustive]
28pub struct KindFormatter;
29
30impl ErrorFormatter for KindFormatter {
31    fn format_error(error: &crate::error::Error<Self>) -> StyledStr {
32        let mut styled = StyledStr::new();
33        start_error(&mut styled);
34        if let Some(msg) = error.kind().as_str() {
35            styled.none(msg.to_owned());
36        } else if let Some(source) = error.inner.source.as_ref() {
37            styled.none(source.to_string());
38        } else {
39            styled.none("unknown cause");
40        }
41        styled.none("\n");
42        styled
43    }
44}
45
46/// Richly formatted error context
47///
48/// This follows the [rustc diagnostic style guide](https://rustc-dev-guide.rust-lang.org/diagnostics.html#suggestion-style-guide).
49#[non_exhaustive]
50#[cfg(feature = "error-context")]
51pub struct RichFormatter;
52
53#[cfg(feature = "error-context")]
54impl ErrorFormatter for RichFormatter {
55    fn format_error(error: &crate::error::Error<Self>) -> StyledStr {
56        let mut styled = StyledStr::new();
57        start_error(&mut styled);
58
59        if !write_dynamic_context(error, &mut styled) {
60            if let Some(msg) = error.kind().as_str() {
61                styled.none(msg.to_owned());
62            } else if let Some(source) = error.inner.source.as_ref() {
63                styled.none(source.to_string());
64            } else {
65                styled.none("unknown cause");
66            }
67        }
68
69        let mut suggested = false;
70        if let Some(valid) = error.get(ContextKind::SuggestedSubcommand) {
71            styled.none("\n");
72            if !suggested {
73                styled.none("\n");
74                suggested = true;
75            }
76            did_you_mean(&mut styled, "subcommand", valid);
77        }
78        if let Some(valid) = error.get(ContextKind::SuggestedArg) {
79            styled.none("\n");
80            if !suggested {
81                styled.none("\n");
82                suggested = true;
83            }
84            did_you_mean(&mut styled, "argument", valid);
85        }
86        if let Some(valid) = error.get(ContextKind::SuggestedValue) {
87            styled.none("\n");
88            if !suggested {
89                styled.none("\n");
90                suggested = true;
91            }
92            did_you_mean(&mut styled, "value", valid);
93        }
94        let suggestions = error.get(ContextKind::Suggested);
95        if let Some(ContextValue::StyledStrs(suggestions)) = suggestions {
96            if !suggested {
97                styled.none("\n");
98            }
99            for suggestion in suggestions {
100                styled.none("\n");
101                styled.none(TAB);
102                styled.good("note: ");
103                styled.extend(suggestion.iter());
104            }
105        }
106
107        let usage = error.get(ContextKind::Usage);
108        if let Some(ContextValue::StyledStr(usage)) = usage {
109            put_usage(&mut styled, usage.clone());
110        }
111
112        try_help(&mut styled, error.inner.help_flag);
113
114        styled
115    }
116}
117
118fn start_error(styled: &mut StyledStr) {
119    styled.error("error:");
120    styled.none(" ");
121}
122
123#[must_use]
124#[cfg(feature = "error-context")]
125fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> bool {
126    match error.kind() {
127        ErrorKind::ArgumentConflict => {
128            let invalid_arg = error.get(ContextKind::InvalidArg);
129            let prior_arg = error.get(ContextKind::PriorArg);
130            if let (Some(ContextValue::String(invalid_arg)), Some(prior_arg)) =
131                (invalid_arg, prior_arg)
132            {
133                if ContextValue::String(invalid_arg.clone()) == *prior_arg {
134                    styled.none("the argument '");
135                    styled.warning(invalid_arg);
136                    styled.none("' cannot be used multiple times");
137                } else {
138                    styled.none("the argument '");
139                    styled.warning(invalid_arg);
140                    styled.none("' cannot be used with");
141
142                    match prior_arg {
143                        ContextValue::Strings(values) => {
144                            styled.none(":");
145                            for v in values {
146                                styled.none("\n");
147                                styled.none(TAB);
148                                styled.warning(&**v);
149                            }
150                        }
151                        ContextValue::String(value) => {
152                            styled.none(" '");
153                            styled.warning(value);
154                            styled.none("'");
155                        }
156                        _ => {
157                            styled.none(" one or more of the other specified arguments");
158                        }
159                    }
160                }
161                true
162            } else {
163                false
164            }
165        }
166        ErrorKind::NoEquals => {
167            let invalid_arg = error.get(ContextKind::InvalidArg);
168            if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
169                styled.none("equal sign is needed when assigning values to '");
170                styled.warning(invalid_arg);
171                styled.none("'");
172                true
173            } else {
174                false
175            }
176        }
177        ErrorKind::InvalidValue => {
178            let invalid_arg = error.get(ContextKind::InvalidArg);
179            let invalid_value = error.get(ContextKind::InvalidValue);
180            if let (
181                Some(ContextValue::String(invalid_arg)),
182                Some(ContextValue::String(invalid_value)),
183            ) = (invalid_arg, invalid_value)
184            {
185                if invalid_value.is_empty() {
186                    styled.none("a value is required for '");
187                    styled.warning(invalid_arg);
188                    styled.none("' but none was supplied");
189                } else {
190                    styled.none("invalid value '");
191                    styled.none(invalid_value);
192                    styled.none("' for '");
193                    styled.warning(invalid_arg);
194                    styled.none("'");
195                }
196
197                let possible_values = error.get(ContextKind::ValidValue);
198                if let Some(ContextValue::Strings(possible_values)) = possible_values {
199                    if !possible_values.is_empty() {
200                        styled.none("\n");
201                        styled.none(TAB);
202                        styled.none("[possible values: ");
203                        if let Some((last, elements)) = possible_values.split_last() {
204                            for v in elements {
205                                styled.good(escape(v));
206                                styled.none(", ");
207                            }
208                            styled.good(escape(last));
209                        }
210                        styled.none("]");
211                    }
212                }
213                true
214            } else {
215                false
216            }
217        }
218        ErrorKind::InvalidSubcommand => {
219            let invalid_sub = error.get(ContextKind::InvalidSubcommand);
220            if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
221                styled.none("unrecognized subcommand '");
222                styled.warning(invalid_sub);
223                styled.none("'");
224                true
225            } else {
226                false
227            }
228        }
229        ErrorKind::MissingRequiredArgument => {
230            let invalid_arg = error.get(ContextKind::InvalidArg);
231            if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg {
232                styled.none("the following required arguments were not provided:");
233                for v in invalid_arg {
234                    styled.none("\n");
235                    styled.none(TAB);
236                    styled.good(&**v);
237                }
238                true
239            } else {
240                false
241            }
242        }
243        ErrorKind::MissingSubcommand => {
244            let invalid_sub = error.get(ContextKind::InvalidSubcommand);
245            if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
246                styled.none("'");
247                styled.warning(invalid_sub);
248                styled.none("' requires a subcommand but one was not provided");
249
250                let possible_values = error.get(ContextKind::ValidSubcommand);
251                if let Some(ContextValue::Strings(possible_values)) = possible_values {
252                    if !possible_values.is_empty() {
253                        styled.none("\n");
254                        styled.none(TAB);
255                        styled.none("[subcommands: ");
256                        if let Some((last, elements)) = possible_values.split_last() {
257                            for v in elements {
258                                styled.good(escape(v));
259                                styled.none(", ");
260                            }
261                            styled.good(escape(last));
262                        }
263                        styled.none("]");
264                    }
265                }
266
267                true
268            } else {
269                false
270            }
271        }
272        ErrorKind::InvalidUtf8 => false,
273        ErrorKind::TooManyValues => {
274            let invalid_arg = error.get(ContextKind::InvalidArg);
275            let invalid_value = error.get(ContextKind::InvalidValue);
276            if let (
277                Some(ContextValue::String(invalid_arg)),
278                Some(ContextValue::String(invalid_value)),
279            ) = (invalid_arg, invalid_value)
280            {
281                styled.none("unexpected value '");
282                styled.warning(invalid_value);
283                styled.none("' for '");
284                styled.warning(invalid_arg);
285                styled.none("' found; no more were expected");
286                true
287            } else {
288                false
289            }
290        }
291        ErrorKind::TooFewValues => {
292            let invalid_arg = error.get(ContextKind::InvalidArg);
293            let actual_num_values = error.get(ContextKind::ActualNumValues);
294            let min_values = error.get(ContextKind::MinValues);
295            if let (
296                Some(ContextValue::String(invalid_arg)),
297                Some(ContextValue::Number(actual_num_values)),
298                Some(ContextValue::Number(min_values)),
299            ) = (invalid_arg, actual_num_values, min_values)
300            {
301                let were_provided = singular_or_plural(*actual_num_values as usize);
302                styled.warning(min_values.to_string());
303                styled.none(" more values required by '");
304                styled.warning(invalid_arg);
305                styled.none("'; only ");
306                styled.warning(actual_num_values.to_string());
307                styled.none(were_provided);
308                true
309            } else {
310                false
311            }
312        }
313        ErrorKind::ValueValidation => {
314            let invalid_arg = error.get(ContextKind::InvalidArg);
315            let invalid_value = error.get(ContextKind::InvalidValue);
316            if let (
317                Some(ContextValue::String(invalid_arg)),
318                Some(ContextValue::String(invalid_value)),
319            ) = (invalid_arg, invalid_value)
320            {
321                styled.none("invalid value '");
322                styled.warning(invalid_value);
323                styled.none("' for '");
324                styled.warning(invalid_arg);
325                if let Some(source) = error.inner.source.as_deref() {
326                    styled.none("': ");
327                    styled.none(source.to_string());
328                } else {
329                    styled.none("'");
330                }
331                true
332            } else {
333                false
334            }
335        }
336        ErrorKind::WrongNumberOfValues => {
337            let invalid_arg = error.get(ContextKind::InvalidArg);
338            let actual_num_values = error.get(ContextKind::ActualNumValues);
339            let num_values = error.get(ContextKind::ExpectedNumValues);
340            if let (
341                Some(ContextValue::String(invalid_arg)),
342                Some(ContextValue::Number(actual_num_values)),
343                Some(ContextValue::Number(num_values)),
344            ) = (invalid_arg, actual_num_values, num_values)
345            {
346                let were_provided = singular_or_plural(*actual_num_values as usize);
347                styled.warning(num_values.to_string());
348                styled.none(" values required for '");
349                styled.warning(invalid_arg);
350                styled.none("' but ");
351                styled.warning(actual_num_values.to_string());
352                styled.none(were_provided);
353                true
354            } else {
355                false
356            }
357        }
358        ErrorKind::UnknownArgument => {
359            let invalid_arg = error.get(ContextKind::InvalidArg);
360            if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
361                styled.none("unexpected argument '");
362                styled.warning(invalid_arg.to_string());
363                styled.none("' found");
364                true
365            } else {
366                false
367            }
368        }
369        ErrorKind::DisplayHelp
370        | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
371        | ErrorKind::DisplayVersion
372        | ErrorKind::Io
373        | ErrorKind::Format => false,
374    }
375}
376
377pub(crate) fn format_error_message(
378    message: &str,
379    cmd: Option<&Command>,
380    usage: Option<StyledStr>,
381) -> StyledStr {
382    let mut styled = StyledStr::new();
383    start_error(&mut styled);
384    styled.none(message);
385    if let Some(usage) = usage {
386        put_usage(&mut styled, usage);
387    }
388    if let Some(cmd) = cmd {
389        try_help(&mut styled, get_help_flag(cmd));
390    }
391    styled
392}
393
394/// Returns the singular or plural form on the verb to be based on the argument's value.
395fn singular_or_plural(n: usize) -> &'static str {
396    if n > 1 {
397        " were provided"
398    } else {
399        " was provided"
400    }
401}
402
403fn put_usage(styled: &mut StyledStr, usage: StyledStr) {
404    styled.none("\n\n");
405    styled.extend(usage.into_iter());
406}
407
408pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> {
409    if !cmd.is_disable_help_flag_set() {
410        Some("--help")
411    } else if cmd.has_subcommands() && !cmd.is_disable_help_subcommand_set() {
412        Some("help")
413    } else {
414        None
415    }
416}
417
418fn try_help(styled: &mut StyledStr, help: Option<&str>) {
419    if let Some(help) = help {
420        styled.none("\n\nFor more information, try '");
421        styled.literal(help.to_owned());
422        styled.none("'.\n");
423    } else {
424        styled.none("\n");
425    }
426}
427
428#[cfg(feature = "error-context")]
429fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) {
430    if let ContextValue::String(valid) = valid {
431        styled.none(TAB);
432        styled.good("note: ");
433        styled.none(context);
434        styled.none(" '");
435        styled.good(valid);
436        styled.none("' exists");
437    } else if let ContextValue::Strings(valid) = valid {
438        styled.none(TAB);
439        styled.good("note: ");
440        styled.none(context);
441        if valid.len() > 1 {
442            styled.none("s");
443        }
444        styled.none(" ");
445        for (i, valid) in valid.iter().enumerate() {
446            if i != 0 {
447                styled.none(", ");
448            }
449            styled.none("'");
450            styled.good(valid);
451            styled.none("'");
452        }
453        if valid.len() == 1 {
454            styled.none(" exists");
455        } else {
456            styled.none(" exist");
457        }
458    }
459}
460
461fn escape(s: impl AsRef<str>) -> String {
462    let s = s.as_ref();
463    if s.contains(char::is_whitespace) {
464        format!("{s:?}")
465    } else {
466        s.to_owned()
467    }
468}
469