bpaf/
error.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
use std::ops::Range;

use crate::{
    args::{Arg, State},
    buffer::{Block, Color, Doc, Style, Token},
    item::{Item, ShortLong},
    meta_help::Metavar,
    meta_youmean::{Suggestion, Variant},
    Meta,
};

/// Unsuccessful command line parsing outcome, internal representation
#[derive(Debug)]
pub struct Error(pub(crate) Message);

impl Error {
    pub(crate) fn combine_with(self, other: Self) -> Self {
        Error(self.0.combine_with(other.0))
    }
}

#[derive(Debug)]
pub(crate) enum Message {
    // those can be caught ---------------------------------------------------------------
    /// Tried to consume an env variable with no fallback, variable was not set
    NoEnv(&'static str),

    /// User specified an error message on some
    ParseSome(&'static str),

    /// User asked for parser to fail explicitly
    ParseFail(&'static str),

    /// pure_with failed to parse a value
    PureFailed(String),

    /// Expected one of those values
    ///
    /// Used internally to generate better error messages
    Missing(Vec<MissingItem>),

    // those cannot be caught-------------------------------------------------------------
    /// Parsing failed and this is the final output
    ParseFailure(ParseFailure),

    /// Tried to consume a strict positional argument, value was present but was not strictly
    /// positional
    StrictPos(usize, Metavar),

    /// Tried to consume a non-strict positional argument, but the value was strict
    NonStrictPos(usize, Metavar),

    /// Parser provided by user failed to parse a value
    ParseFailed(Option<usize>, String),

    /// Parser provided by user failed to validate a value
    GuardFailed(Option<usize>, &'static str),

    /// Argument requres a value but something else was passed,
    /// required: --foo <BAR>
    /// given: --foo --bar
    ///        --foo -- bar
    ///        --foo
    NoArgument(usize, Metavar),

    /// Parser is expected to consume all the things from the command line
    /// this item will contain an index of the unconsumed value
    Unconsumed(/* TODO - unused? */ usize),

    /// argument is ambigoups - parser can accept it as both a set of flags and a short flag with no =
    Ambiguity(usize, String),

    /// Suggested fixes for typos or missing input
    Suggestion(usize, Suggestion),

    /// Two arguments are mutually exclusive
    /// --release --dev
    Conflict(/* winner */ usize, usize),

    /// Expected one or more items in the scope, got someting else if any
    Expected(Vec<Item>, Option<usize>),

    /// Parameter is accepted but only once
    OnlyOnce(/* winner */ usize, usize),
}

impl Message {
    pub(crate) fn can_catch(&self) -> bool {
        match self {
            Message::NoEnv(_)
            | Message::ParseSome(_)
            | Message::ParseFail(_)
            | Message::Missing(_)
            | Message::PureFailed(_)
            | Message::NonStrictPos(_, _) => true,
            Message::StrictPos(_, _)
            | Message::ParseFailed(_, _)
            | Message::GuardFailed(_, _)
            | Message::Unconsumed(_)
            | Message::Ambiguity(_, _)
            | Message::Suggestion(_, _)
            | Message::Conflict(_, _)
            | Message::ParseFailure(_)
            | Message::Expected(_, _)
            | Message::OnlyOnce(_, _)
            | Message::NoArgument(_, _) => false,
        }
    }
}

/// Missing item in a context
#[derive(Debug, Clone)]
pub struct MissingItem {
    /// Item that is missing
    pub(crate) item: Item,
    /// Position it is missing from - exact for positionals, earliest possible for flags
    pub(crate) position: usize,
    /// Range where search was performed, important for combinators that narrow the search scope
    /// such as adjacent
    pub(crate) scope: Range<usize>,
}

impl Message {
    #[must_use]
    pub(crate) fn combine_with(self, other: Self) -> Self {
        #[allow(clippy::match_same_arms)]
        match (self, other) {
            // help output takes priority
            (a @ Message::ParseFailure(_), _) => a,
            (_, b @ Message::ParseFailure(_)) => b,

            // combine missing elements
            (Message::Missing(mut a), Message::Missing(mut b)) => {
                a.append(&mut b);
                Message::Missing(a)
            }

            // otherwise earliest wins
            (a, b) => {
                if a.can_catch() {
                    b
                } else {
                    a
                }
            }
        }
    }
}

/// Unsuccessful command line parsing outcome, use it for unit tests
///
/// When [`OptionParser::run_inner`](crate::OptionParser::run_inner) produces `Err(ParseFailure)`
/// it means that the parser couldn't produce the value it supposed to produce and the program
/// should terminate.
///
/// If you are handling variants manually - `Stdout` contains formatted output and you can use any
/// logging framework to produce the output, `Completion` should be printed to stdout unchanged -
/// shell completion mechanism relies on that. In both cases application should exit with error
/// code of 0. `Stderr` variant indicates a genuinly parsing error which should be printed to
/// stderr or a logging framework of your choice as an error and the app should exit with error
/// code of 1. [`ParseFailure::exit_code`] is a helper method that performs printing and produces
/// the exit code to use.
///
/// For purposes of for unit testing for user parsers, you can consume it with
/// [`ParseFailure::unwrap_stdout`] and [`ParseFailure::unwrap_stdout`] - both of which produce a
/// an unformatted `String` that parser might produce if failure type is correct or panics
/// otherwise.
#[derive(Clone, Debug)]
pub enum ParseFailure {
    /// Print this to stdout and exit with success code
    Stdout(Doc, bool),
    /// This also goes to stdout with exit code of 0,
    /// this cannot be Doc because completion needs more control about rendering
    Completion(String),
    /// Print this to stderr and exit with failure code
    Stderr(Doc),
}

impl ParseFailure {
    /// Returns the contained `stderr` values - for unit tests
    ///
    /// # Panics
    ///
    /// Panics if failure contains `stdout`
    #[allow(clippy::must_use_candidate)]
    #[track_caller]
    pub fn unwrap_stderr(self) -> String {
        match self {
            Self::Stderr(err) => err.monochrome(true),
            Self::Completion(..) | Self::Stdout(..) => panic!("not an stderr: {:?}", self),
        }
    }

    /// Returns the contained `stdout` values - for unit tests
    ///
    /// # Panics
    ///
    /// Panics if failure contains `stderr`
    #[allow(clippy::must_use_candidate)]
    #[track_caller]
    pub fn unwrap_stdout(self) -> String {
        match self {
            Self::Stdout(err, full) => err.monochrome(full),
            Self::Completion(s) => s,
            Self::Stderr(..) => panic!("not an stdout: {:?}", self),
        }
    }

    /// Returns the exit code for the failure
    #[allow(clippy::must_use_candidate)]
    pub fn exit_code(self) -> i32 {
        match self {
            Self::Stdout(..) | Self::Completion(..) => 0,
            Self::Stderr(..) => 1,
        }
    }

    #[doc(hidden)]
    #[deprecated = "Please use ParseFailure::print_message, with two s"]
    pub fn print_mesage(&self, max_width: usize) {
        self.print_message(max_width)
    }

    /// Prints a message to `stdout` or `stderr` appropriate to the failure.
    pub fn print_message(&self, max_width: usize) {
        let color = Color::default();
        match self {
            ParseFailure::Stdout(msg, full) => {
                println!("{}", msg.render_console(*full, color, max_width));
            }
            ParseFailure::Completion(s) => {
                print!("{}", s);
            }
            ParseFailure::Stderr(msg) => {
                #[allow(unused_mut)]
                let mut error;
                #[cfg(not(feature = "color"))]
                {
                    error = "Error: ";
                }

                #[cfg(feature = "color")]
                {
                    error = String::new();
                    color.push_str(Style::Invalid, &mut error, "Error: ");
                }

                eprintln!("{}{}", error, msg.render_console(true, color, max_width));
            }
        }
    }
}

fn check_conflicts(args: &State) -> Option<Message> {
    let (loser, winner) = args.conflict()?;
    Some(Message::Conflict(winner, loser))
}

fn textual_part(args: &State, ix: Option<usize>) -> Option<std::borrow::Cow<str>> {
    match args.items.get(ix?)? {
        Arg::Short(_, _, _) | Arg::Long(_, _, _) => None,
        Arg::ArgWord(s) | Arg::Word(s) | Arg::PosWord(s) => Some(s.to_string_lossy()),
    }
}

fn only_once(args: &State, cur: usize) -> Option<usize> {
    if cur == 0 {
        return None;
    }
    let mut iter = args.items[..cur].iter().rev();
    let offset = match args.items.get(cur)? {
        Arg::Short(s, _, _) => iter.position(|a| a.match_short(*s)),
        Arg::Long(l, _, _) => iter.position(|a| a.match_long(l)),
        Arg::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_) => None,
    };
    Some(cur - offset? - 1)
}

impl Message {
    #[allow(clippy::too_many_lines)] // it's a huge match with lots of simple cases
    pub(crate) fn render(mut self, args: &State, meta: &Meta) -> ParseFailure {
        // try to come up with a better error message for a few cases
        match self {
            Message::Unconsumed(ix) => {
                if let Some(conflict) = check_conflicts(args) {
                    self = conflict;
                } else if let Some(prev_ix) = only_once(args, ix) {
                    self = Message::OnlyOnce(prev_ix, ix);
                } else if let Some((ix, suggestion)) = crate::meta_youmean::suggest(args, meta) {
                    self = Message::Suggestion(ix, suggestion);
                }
            }
            Message::Missing(xs) => {
                self = summarize_missing(&xs, meta, args);
            }
            _ => {}
        }

        let mut doc = Doc::default();
        match self {
            // already rendered
            Message::ParseFailure(f) => return f,

            // this case is handled above
            Message::Missing(_) => {
                // this one is unreachable
            }

            // Error: --foo is not expected in this context
            Message::Unconsumed(ix) => {
                let item = &args.items[ix];
                doc.token(Token::BlockStart(Block::TermRef));
                doc.write(item, Style::Invalid);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" is not expected in this context");
            }

            // Error: environment variable FOO is not set
            Message::NoEnv(name) => {
                doc.text("environment variable ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.invalid(name);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" is not set");
            }

            // Error: FOO expected to be  in the right side of --
            Message::StrictPos(_ix, metavar) => {
                doc.text("expected ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.metavar(metavar);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" to be on the right side of ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.literal("--");
                doc.token(Token::BlockEnd(Block::TermRef));
            }

            // Error: FOO expected to be on the left side of --
            Message::NonStrictPos(_ix, metavar) => {
                doc.text("expected ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.metavar(metavar);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" to be on the left side of ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.literal("--");
                doc.token(Token::BlockEnd(Block::TermRef));
            }

            // Error: <message from some or fail>
            Message::ParseSome(s) | Message::ParseFail(s) => {
                doc.text(s);
            }

            // Error: couldn't parse FIELD: <FromStr message>
            Message::ParseFailed(mix, s) => {
                doc.text("couldn't parse");
                if let Some(field) = textual_part(args, mix) {
                    doc.text(" ");
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.invalid(&field);
                    doc.token(Token::BlockEnd(Block::TermRef));
                }
                doc.text(": ");
                doc.text(&s);
            }

            // Error: ( FIELD:  | check failed: ) <message from guard>
            Message::GuardFailed(mix, s) => {
                if let Some(field) = textual_part(args, mix) {
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.invalid(&field);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(": ");
                } else {
                    doc.text("check failed: ");
                }
                doc.text(s);
            }

            // Error: --foo requires an argument FOO, got a flag --bar, try --foo=-bar to use it as an argument
            // Error: --foo requires an argument FOO
            Message::NoArgument(x, mv) => match args.get(x + 1) {
                Some(Arg::Short(_, _, os) | Arg::Long(_, _, os)) => {
                    let arg = &args.items[x];
                    let os = &os.to_string_lossy();

                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.write(arg, Style::Literal);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(" requires an argument ");
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.metavar(mv);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(", got a flag ");
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.write(os, Style::Invalid);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(", try ");
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.write(arg, Style::Literal);
                    doc.literal("=");
                    doc.write(os, Style::Literal);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(" to use it as an argument");
                }
                // "Some" part of this branch is actually unreachable
                Some(Arg::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_)) | None => {
                    let arg = &args.items[x];
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.write(arg, Style::Literal);
                    doc.token(Token::BlockEnd(Block::TermRef));
                    doc.text(" requires an argument ");
                    doc.token(Token::BlockStart(Block::TermRef));
                    doc.metavar(mv);
                    doc.token(Token::BlockEnd(Block::TermRef));
                }
            },
            // Error: <message from pure_with>
            Message::PureFailed(s) => {
                doc.text(&s);
            }
            // Error: app supports -f as both an option and an option-argument, try to split -foo
            // into invididual options (-f -o ..) or use -f=oo syntax to disambiguate
            Message::Ambiguity(ix, name) => {
                let mut chars = name.chars();
                let first = chars.next().unwrap();
                let rest = chars.as_str();
                let second = chars.next().unwrap();
                let s = args.items[ix].os_str().to_str().unwrap();

                if let Some(name) = args.path.first() {
                    doc.literal(name);
                    doc.text(" supports ");
                } else {
                    doc.text("app supports ");
                }

                doc.token(Token::BlockStart(Block::TermRef));
                doc.literal("-");
                doc.write_char(first, Style::Literal);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" as both an option and an option-argument, try to split ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.write(s, Style::Literal);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" into individual options (");
                doc.literal("-");
                doc.write_char(first, Style::Literal);
                doc.literal(" -");
                doc.write_char(second, Style::Literal);
                doc.literal(" ..");
                doc.text(") or use ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.literal("-");
                doc.write_char(first, Style::Literal);
                doc.literal("=");
                doc.literal(rest);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" syntax to disambiguate");
            }
            // Error: No such (flag|argument|command), did you mean  ...
            Message::Suggestion(ix, suggestion) => {
                let actual = &args.items[ix].to_string();
                match suggestion {
                    Suggestion::Variant(v) => {
                        let ty = match &args.items[ix] {
                            _ if actual.starts_with('-') => "flag",
                            Arg::Short(_, _, _) | Arg::Long(_, _, _) => "flag",
                            Arg::ArgWord(_) => "argument value",
                            Arg::Word(_) | Arg::PosWord(_) => "command or positional",
                        };

                        doc.text("no such ");
                        doc.text(ty);
                        doc.text(": ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.invalid(actual);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(", did you mean ");
                        doc.token(Token::BlockStart(Block::TermRef));

                        match v {
                            Variant::CommandLong(name) => doc.literal(name),
                            Variant::Flag(ShortLong::Long(l) | ShortLong::Both(_, l)) => {
                                doc.literal("--");
                                doc.literal(l);
                            }
                            Variant::Flag(ShortLong::Short(s)) => {
                                doc.literal("-");
                                doc.write_char(s, Style::Literal);
                            }
                        };

                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text("?");
                    }
                    Suggestion::MissingDash(name) => {
                        doc.text("no such flag: ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal("-");
                        doc.literal(name);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(" (with one dash), did you mean ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal("--");
                        doc.literal(name);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text("?");
                    }
                    Suggestion::ExtraDash(name) => {
                        doc.text("no such flag: ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal("--");
                        doc.write_char(name, Style::Literal);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(" (with two dashes), did you mean ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal("-");
                        doc.write_char(name, Style::Literal);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text("?");
                    }
                    Suggestion::Nested(x, v) => {
                        let ty = match v {
                            Variant::CommandLong(_) => "subcommand",
                            Variant::Flag(_) => "flag",
                        };
                        doc.text(ty);
                        doc.text(" ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal(actual);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(
                            " is not valid in this context, did you mean to pass it to command ",
                        );
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.literal(&x);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text("?");
                    }
                }
            }
            // Error: Expected (no arguments|--foo), got ..., pass --help
            Message::Expected(exp, actual) => {
                doc.text("expected ");
                match exp.len() {
                    0 => {
                        doc.text("no arguments");
                    }
                    1 => {
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write_item(&exp[0]);
                        doc.token(Token::BlockEnd(Block::TermRef));
                    }
                    2 => {
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write_item(&exp[0]);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(" or ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write_item(&exp[1]);
                        doc.token(Token::BlockEnd(Block::TermRef));
                    }
                    _ => {
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write_item(&exp[0]);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(", ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write_item(&exp[1]);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(", or more");
                    }
                }
                match actual {
                    Some(actual) => {
                        doc.text(", got ");
                        doc.token(Token::BlockStart(Block::TermRef));
                        doc.write(&args.items[actual], Style::Invalid);
                        doc.token(Token::BlockEnd(Block::TermRef));
                        doc.text(". Pass ");
                    }
                    None => {
                        doc.text(", pass ");
                    }
                }
                doc.token(Token::BlockStart(Block::TermRef));
                doc.literal("--help");
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" for usage information");
            }

            // Error: --intel cannot be used at the same time as --att
            Message::Conflict(winner, loser) => {
                doc.token(Token::BlockStart(Block::TermRef));
                doc.write(&args.items[loser], Style::Literal);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" cannot be used at the same time as ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.write(&args.items[winner], Style::Literal);
                doc.token(Token::BlockEnd(Block::TermRef));
            }

            // Error: argument FOO cannot be used multiple times in this context
            Message::OnlyOnce(_winner, loser) => {
                doc.text("argument ");
                doc.token(Token::BlockStart(Block::TermRef));
                doc.write(&args.items[loser], Style::Literal);
                doc.token(Token::BlockEnd(Block::TermRef));
                doc.text(" cannot be used multiple times in this context");
            }
        };

        ParseFailure::Stderr(doc)
    }
}

/// go over all the missing items, pick the left most scope
pub(crate) fn summarize_missing(items: &[MissingItem], inner: &Meta, args: &State) -> Message {
    // missing items can belong to different scopes, pick the best scope to work with
    let best_item = match items
        .iter()
        .max_by_key(|item| (item.position, item.scope.start))
    {
        Some(x) => x,
        None => return Message::ParseSome("parser requires an extra flag, argument or parameter, but its name is hidden by the author"),
    };

    let mut best_scope = best_item.scope.clone();

    let mut saw_command = false;
    let expected = items
        .iter()
        .filter_map(|i| {
            let cmd = matches!(i.item, Item::Command { .. });
            if i.scope == best_scope && !(saw_command && cmd) {
                saw_command |= cmd;
                Some(i.item.clone())
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    best_scope.start = best_scope.start.max(best_item.position);
    let mut args = args.clone();
    args.set_scope(best_scope);
    if let Some((ix, _arg)) = args.items_iter().next() {
        if let Some((ix, sugg)) = crate::meta_youmean::suggest(&args, inner) {
            Message::Suggestion(ix, sugg)
        } else {
            Message::Expected(expected, Some(ix))
        }
    } else {
        Message::Expected(expected, None)
    }
}

/*
#[inline(never)]
/// the idea is to post some context for the error
fn snip(buffer: &mut Buffer, args: &State, items: &[usize]) {
    for ix in args.scope() {
        buffer.write(ix, Style::Text);
    }
}
*/