bpaf/
info.rs

1//! Help message generation and rendering
2
3use crate::{
4    args::{Args, State},
5    error::Message,
6    meta_help::render_help,
7    parsers::NamedArg,
8    short, Doc, Error, Meta, ParseFailure, Parser,
9};
10
11/// Information about the parser
12///
13/// No longer public, users are only interacting with it via [`OptionParser`]
14#[derive(Debug, Clone)]
15#[doc(hidden)]
16pub struct Info {
17    /// version field, see [`version`][Info::version]
18    pub version: Option<Doc>,
19    /// Custom description field, see [`descr`][Info::descr]
20    pub descr: Option<Doc>,
21    /// Custom header field, see [`header`][Info::header]
22    pub header: Option<Doc>,
23    /// Custom footer field, see [`footer`][Info::footer]
24    pub footer: Option<Doc>,
25    /// Custom usage field, see [`usage`][Info::usage]
26    pub usage: Option<Doc>,
27    pub help_arg: NamedArg,
28    pub version_arg: NamedArg,
29    pub help_if_no_args: bool,
30    pub max_width: usize,
31}
32
33impl Default for Info {
34    fn default() -> Self {
35        Self {
36            version: None,
37            descr: None,
38            header: None,
39            footer: None,
40            usage: None,
41            help_arg: short('h').long("help").help("Prints help information"),
42            version_arg: short('V')
43                .long("version")
44                .help("Prints version information"),
45            help_if_no_args: false,
46            max_width: 100,
47        }
48    }
49}
50
51/// Ready to run [`Parser`] with additional information attached
52///
53/// Created with [`to_options`](Parser::to_options)
54///
55/// In addition to the inner parser `OptionParser` contains documentation about a program or a
56/// subcommand as a whole, version, custom usage, if specified, and handles custom parsers for
57/// `--version` and `--help` flags.
58pub struct OptionParser<T> {
59    pub(crate) inner: Box<dyn Parser<T>>,
60    pub(crate) info: Info,
61}
62
63impl<T> OptionParser<T> {
64    /// Execute the [`OptionParser`], extract a parsed value or print some diagnostic and exit
65    ///
66    /// # Usage
67    /// ```no_run
68    /// # use bpaf::*;
69    /// /// Parses number of repetitions of `-v` on a command line
70    /// fn verbosity() -> OptionParser<usize> {
71    ///     let parser = short('v')
72    ///         .req_flag(())
73    ///         .many()
74    ///         .map(|xs|xs.len());
75    ///
76    ///     parser
77    ///         .to_options()
78    ///         .descr("Takes verbosity flag and does nothing else")
79    /// }
80    ///
81    /// fn main() {
82    ///     let verbosity: usize = verbosity().run();
83    /// }
84    /// ```
85    #[must_use]
86    pub fn run(self) -> T
87    where
88        Self: Sized,
89    {
90        match self.run_inner(Args::current_args()) {
91            Ok(t) => t,
92            Err(err) => {
93                err.print_message(self.info.max_width);
94                std::process::exit(err.exit_code())
95            }
96        }
97    }
98
99    /// Execute the [`OptionParser`], extract a parsed value or return a [`ParseFailure`]
100    ///
101    /// In most cases using [`run`](OptionParser::run) is sufficient, you can use `try_run` if you
102    /// want to control the exit code or you need to perform a custom cleanup.
103    ///
104    /// # Usage
105    /// ```no_run
106    /// # use bpaf::*;
107    /// /// Parses number of repetitions of `-v` on a command line
108    /// fn verbosity() -> OptionParser<usize> {
109    ///     let parser = short('v')
110    ///         .req_flag(())
111    ///         .many()
112    ///         .map(|xs|xs.len());
113    ///
114    ///     parser
115    ///         .to_options()
116    ///         .descr("Takes verbosity flag and does nothing else")
117    /// }
118    ///
119    /// fn main() {
120    ///     let verbosity: Option<usize> = match verbosity().try_run() {
121    ///         Ok(v) => Some(v),
122    ///         Err(ParseFailure::Stdout(buf, full)) => {
123    ///             print!("{}", buf.monochrome(full));
124    ///             None
125    ///         }
126    ///         Err(ParseFailure::Completion(msg)) => {
127    ///             print!("{}", msg);
128    ///             None
129    ///         }
130    ///         Err(ParseFailure::Stderr(buf)) => {
131    ///             eprintln!("{}", buf.monochrome(true));
132    ///             None
133    ///         }
134    ///     };
135    ///
136    ///     // Run cleanup tasks
137    /// }
138    /// ```
139    ///
140    /// # Errors
141    ///
142    /// [`ParseFailure`] represents parsing errors, autocomplete results and generated `--help`
143    /// output.
144    #[deprecated = "You should switch to equivalent parser.run_inner(Args::current_args())"]
145    pub fn try_run(self) -> Result<T, ParseFailure>
146    where
147        Self: Sized,
148    {
149        self.run_inner(Args::current_args())
150    }
151
152    /// Execute the [`OptionParser`] and produce a values for unit tests or manual processing
153    ///
154    /// ```rust
155    /// # use bpaf::*;
156    /// # /*
157    /// #[test]
158    /// fn positional_argument() {
159    /// # */
160    ///     let parser =
161    ///         positional::<String>("FILE")
162    ///             .help("File to process")
163    ///             .to_options();
164    ///
165    ///     let help = parser
166    ///         .run_inner(&["--help"])
167    ///         .unwrap_err()
168    ///         .unwrap_stdout();
169    ///     let expected_help = "\
170    /// Usage: FILE
171    ///
172    /// Available positional items:
173    ///     FILE        File to process
174    ///
175    /// Available options:
176    ///     -h, --help  Prints help information
177    /// ";
178    ///     assert_eq!(expected_help, help);
179    /// # /*
180    /// }
181    /// # */
182    /// ```
183    ///
184    /// See also [`Args`] and it's `From` impls to produce input and
185    /// [`ParseFailure::unwrap_stderr`] / [`ParseFailure::unwrap_stdout`] for processing results.
186    ///
187    /// # Errors
188    ///
189    /// If parser can't produce desired result `run_inner` returns [`ParseFailure`]
190    /// which represents runtime behavior: one branch to print something to stdout and exit with
191    /// success and the other branch to print something to stderr and exit with failure.
192    ///
193    /// `bpaf` generates contents of this `ParseFailure` using expected textual output from
194    /// [`parse`](Parser::parse), stdout/stderr isn't actually captured.
195    ///
196    /// Exact string reperentations may change between versions including minor releases.
197    pub fn run_inner<'a>(&self, args: impl Into<Args<'a>>) -> Result<T, ParseFailure>
198    where
199        Self: Sized,
200    {
201        // prepare available short flags and arguments for disambiguation
202        let mut short_flags = Vec::new();
203        let mut short_args = Vec::new();
204        self.inner
205            .meta()
206            .collect_shorts(&mut short_flags, &mut short_args);
207        short_flags.extend(&self.info.help_arg.short);
208        short_flags.extend(&self.info.version_arg.short);
209        let args = args.into();
210        let mut err = None;
211        let mut state = State::construct(args, &short_flags, &short_args, &mut err);
212
213        // this only handles disambiguation failure in construct
214        if let Some(msg) = err {
215            #[cfg(feature = "autocomplete")]
216            let check_disambiguation = state.comp_ref().is_none();
217
218            #[cfg(not(feature = "autocomplete"))]
219            let check_disambiguation = false;
220
221            if check_disambiguation {
222                return Err(msg.render(&state, &self.inner.meta()));
223            }
224        }
225
226        self.run_subparser(&mut state)
227    }
228
229    /// Run subparser, implementation detail
230    pub(crate) fn run_subparser(&self, args: &mut State) -> Result<T, ParseFailure> {
231        // process should work like this:
232        // - inner parser is evaluated, it returns Error
233        // - if error is finalized (ParseFailure) - it is simply propagated outwards,
234        //   otherwise we are making a few attempts at improving it after dealing with
235        //   autocomplete/help
236        //
237        // - generate autocomplete, if enabled
238        // - produce --help, --version
239        // - Try to improve error message and finalize it otherwise
240        //
241        // outer parser gets value in ParseFailure format
242
243        let no_args = args.is_empty();
244        let res = self.inner.eval(args);
245
246        // Don't override inner parser printing usage info
247        let parser_failed = match res {
248            Ok(_) | Err(Error(Message::ParseFailure(ParseFailure::Stdout(..)))) => false,
249            Err(_) => true,
250        };
251
252        if parser_failed && self.info.help_if_no_args && no_args {
253            let buffer = render_help(
254                &args.path,
255                &self.info,
256                &self.inner.meta(),
257                &self.info.meta(),
258                true,
259            );
260            return Err(ParseFailure::Stdout(buffer, false));
261        };
262
263        if let Err(Error(Message::ParseFailure(failure))) = res {
264            return Err(failure);
265        }
266        #[cfg(feature = "autocomplete")]
267        if let Some(comp) = args.check_complete() {
268            return Err(ParseFailure::Completion(comp));
269        }
270
271        let err = match res {
272            Ok(ok) => {
273                if let Some((ix, _)) = args.items_iter().next() {
274                    Message::Unconsumed(ix)
275                } else {
276                    return Ok(ok);
277                }
278            }
279            Err(Error(err)) => err,
280        };
281
282        // handle --help and --version messages
283        if let Ok(extra) = self.info.eval(args) {
284            let mut detailed = false;
285            let buffer = match extra {
286                ExtraParams::Help(d) => {
287                    detailed = d;
288                    render_help(
289                        &args.path,
290                        &self.info,
291                        &self.inner.meta(),
292                        &self.info.meta(),
293                        true,
294                    )
295                }
296                ExtraParams::Version(v) => {
297                    use crate::buffer::{Block, Token};
298                    let mut buffer = Doc::default();
299                    buffer.token(Token::BlockStart(Block::Block));
300                    buffer.text("Version: ");
301                    buffer.doc(&v);
302                    buffer.token(Token::BlockEnd(Block::Block));
303                    buffer
304                }
305            };
306            return Err(ParseFailure::Stdout(buffer, detailed));
307        }
308        Err(err.render(args, &self.inner.meta()))
309    }
310
311    /// Get first line of description if Available
312    ///
313    /// Used internally to avoid duplicating description for [`command`].
314    #[must_use]
315    pub(crate) fn short_descr(&self) -> Option<Doc> {
316        self.info.descr.as_ref().and_then(Doc::first_line)
317    }
318
319    /// Set the version field.
320    ///
321    /// By default `bpaf` won't include any version info and won't accept `--version` switch.
322    ///
323    /// # Combinatoric usage
324    ///
325    /// ```rust
326    /// use bpaf::*;
327    /// fn options() -> OptionParser<bool>  {
328    ///    short('s')
329    ///        .switch()
330    ///        .to_options()
331    ///        .version(env!("CARGO_PKG_VERSION"))
332    /// }
333    /// ```
334    ///
335    /// # Derive usage
336    ///
337    /// `version` annotation is available after `options` and `command` annotations, takes
338    /// an optional argument - version value to use, otherwise `bpaf_derive` would use value from cargo.
339    ///
340    /// ```rust
341    /// # use bpaf::*;
342    /// #[derive(Debug, Clone, Bpaf)]
343    /// #[bpaf(options, version)]
344    /// struct Options {
345    ///     #[bpaf(short)]
346    ///     switch: bool
347    /// }
348    /// ```
349    ///
350    /// # Example
351    /// ```console
352    /// $ app --version
353    /// Version: 0.5.0
354    /// ```
355    #[must_use]
356    pub fn version<B: Into<Doc>>(mut self, version: B) -> Self {
357        self.info.version = Some(version.into());
358        self
359    }
360    /// Set the description field
361    ///
362    /// Description field should be 1-2 lines long briefly explaining program purpose. If
363    /// description field is present `bpaf` would print it right before the usage line.
364    ///
365    /// # Combinatoric usage
366    /// ```rust
367    /// # use bpaf::*;
368    /// fn options() -> OptionParser<bool>  {
369    ///    short('s')
370    ///        .switch()
371    ///        .to_options()
372    ///        .descr("This is a description")
373    ///        .header("This is a header")
374    ///        .footer("This is a footer")
375    /// }
376    /// ```
377    ///
378    /// # Derive usage
379    ///
380    /// `bpaf_derive` uses doc comments on the `struct` / `enum` to derive description, it skips single empty
381    /// lines and uses double empty lines break it into blocks. `bpaf_derive` would use first block as the
382    /// description, second block - header, third block - footer.
383    ///
384    /// ```rust
385    /// # use bpaf::*;
386    /// #[derive(Debug, Clone, Bpaf)]
387    /// #[bpaf(options, version)]
388    /// /// This is a description
389    /// ///
390    /// ///
391    /// /// This is a header
392    /// ///
393    /// ///
394    /// /// This is a footer
395    /// ///
396    /// ///
397    /// /// This is just a comment
398    /// struct Options {
399    ///     #[bpaf(short)]
400    ///     switch: bool
401    /// }
402    /// ```
403    ///
404    /// # Example
405    ///
406    /// ```console
407    /// This is a description
408    ///
409    /// Usage: [-s]
410    ///
411    /// This is a header
412    ///
413    /// Available options:
414    ///     -s
415    ///     -h, --help     Prints help information
416    ///     -V, --version  Prints version information
417    ///
418    /// This is a footer
419    /// ```
420    #[must_use]
421    pub fn descr<B: Into<Doc>>(mut self, descr: B) -> Self {
422        self.info.descr = Some(descr.into());
423        self
424    }
425
426    /// Set the header field
427    ///
428    /// `bpaf` displays the header between the usage line and a list of the available options in `--help` output
429    ///
430    /// # Combinatoric usage
431    /// ```rust
432    /// # use bpaf::*;
433    /// fn options() -> OptionParser<bool>  {
434    ///    short('s')
435    ///        .switch()
436    ///        .to_options()
437    ///        .descr("This is a description")
438    ///        .header("This is a header")
439    ///        .footer("This is a footer")
440    /// }
441    /// ```
442    ///
443    /// # Derive usage
444    ///
445    /// `bpaf_derive` uses doc comments on the `struct` / `enum` to derive description, it skips single empty
446    /// lines and uses double empty lines break it into blocks. `bpaf_derive` would use first block as the
447    /// description, second block - header, third block - footer.
448    ///
449    /// ```rust
450    /// # use bpaf::*;
451    /// #[derive(Debug, Clone, Bpaf)]
452    /// #[bpaf(options, version)]
453    /// /// This is a description
454    /// ///
455    /// ///
456    /// /// This is a header
457    /// ///
458    /// ///
459    /// /// This is a footer
460    /// ///
461    /// ///
462    /// /// This is just a comment
463    /// struct Options {
464    ///     #[bpaf(short)]
465    ///     switch: bool
466    /// }
467    /// ```
468    ///
469    /// # Example
470    ///
471    /// ```console
472    /// This is a description
473    ///
474    /// Usage: [-s]
475    ///
476    /// This is a header
477    ///
478    /// Available options:
479    ///     -s
480    ///     -h, --help     Prints help information
481    ///     -V, --version  Prints version information
482    ///
483    /// This is a footer
484    /// ```
485    #[must_use]
486    pub fn header<B: Into<Doc>>(mut self, header: B) -> Self {
487        self.info.header = Some(header.into());
488        self
489    }
490
491    /// Set the footer field
492    ///
493    /// `bpaf` displays the footer after list of the available options in `--help` output
494    ///
495    /// # Combinatoric usage
496    /// ```rust
497    /// # use bpaf::*;
498    /// fn options() -> OptionParser<bool>  {
499    ///    short('s')
500    ///        .switch()
501    ///        .to_options()
502    ///        .descr("This is a description")
503    ///        .header("This is a header")
504    ///        .footer("This is a footer")
505    /// }
506    /// ```
507    ///
508    /// # Derive usage
509    ///
510    /// `bpaf_derive` uses doc comments on the `struct` / `enum` to derive description, it skips single empty
511    /// lines and uses double empty lines break it into blocks. `bpaf_derive` would use first block as the
512    /// description, second block - header, third block - footer.
513    ///
514    /// ```rust
515    /// # use bpaf::*;
516    /// #[derive(Debug, Clone, Bpaf)]
517    /// #[bpaf(options, version)]
518    /// /// This is a description
519    /// ///
520    /// ///
521    /// /// This is a header
522    /// ///
523    /// ///
524    /// /// This is a footer
525    /// ///
526    /// ///
527    /// /// This is just a comment
528    /// struct Options {
529    ///     #[bpaf(short)]
530    ///     switch: bool
531    /// }
532    /// ```
533    ///
534    /// # Example
535    ///
536    /// ```console
537    /// This is a description
538    ///
539    /// Usage: [-s]
540    ///
541    /// This is a header
542    ///
543    /// Available options:
544    ///     -s
545    ///     -h, --help     Prints help information
546    ///     -V, --version  Prints version information
547    ///
548    /// This is a footer
549    /// ```
550    #[must_use]
551    pub fn footer<M: Into<Doc>>(mut self, footer: M) -> Self {
552        self.info.footer = Some(footer.into());
553        self
554    }
555
556    /// Set custom usage field
557    ///
558    /// Custom usage field to use instead of one derived by `bpaf`.
559    #[cfg_attr(not(doctest), doc = include_str!("docs2/usage.md"))]
560    #[must_use]
561    pub fn usage<B>(mut self, usage: B) -> Self
562    where
563        B: Into<Doc>,
564    {
565        self.info.usage = Some(usage.into());
566        self
567    }
568
569    /// Generate new usage line using automatically derived usage
570    ///
571    /// You can customize the surroundings of the usage line while still
572    /// having part that frequently changes generated by bpaf
573    ///
574    #[cfg_attr(not(doctest), doc = include_str!("docs2/with_usage.md"))]
575    ///
576    /// At the moment this method is not directly supported by derive API,
577    /// but since it gives you an object of [`OptionParser<T>`](OptionParser)
578    /// type you can alter it using Combinatoric API:
579    /// ```text
580    /// #[derive(Debug, Clone, Bpaf)] {
581    /// pub struct Options {
582    ///     ...
583    /// }
584    ///
585    /// fn my_decor(usage: Doc) -> Doc {
586    ///     ...
587    /// }
588    ///
589    /// fn main() {
590    ///     let options = options().with_usage(my_decor).run();
591    ///     ...
592    /// }
593    /// ```
594    #[must_use]
595    pub fn with_usage<F>(mut self, f: F) -> Self
596    where
597        F: Fn(Doc) -> Doc,
598    {
599        let mut buf = Doc::default();
600        buf.write_meta(&self.inner.meta(), true);
601        self.info.usage = Some(f(buf));
602        self
603    }
604
605    /// Check the invariants `bpaf` relies on for normal operations
606    ///
607    /// Takes a parameter whether to check for cosmetic invariants or not
608    /// (max help width exceeding 120 symbols, etc), currently not in use
609    ///
610    /// Best used as part of your test suite:
611    /// ```no_run
612    /// # use bpaf::*;
613    /// #[test]
614    /// fn check_options() {
615    /// # let options = || short('p').switch().to_options();
616    ///     options().check_invariants(false)
617    /// }
618    /// ```
619    ///
620    /// # Panics
621    ///
622    /// `check_invariants` indicates problems with panic
623    pub fn check_invariants(&self, _cosmetic: bool) {
624        self.inner.meta().positional_invariant_check(true);
625    }
626
627    /// Customize parser for `--help`
628    ///
629    /// By default `bpaf` displays help when program is called with either `--help` or `-h`, you
630    /// can customize those names and description in the help message
631    ///
632    /// Note, `--help` is something user expects to work
633    #[cfg_attr(not(doctest), doc = include_str!("docs2/custom_help_version.md"))]
634    #[must_use]
635    pub fn help_parser(mut self, parser: NamedArg) -> Self {
636        self.info.help_arg = parser;
637        self
638    }
639
640    /// Customize parser for `--version`
641    ///
642    /// By default `bpaf` displays version information when program is called with either `--version`
643    /// or `-V` (and version is available), you can customize those names and description in the help message
644    ///
645    /// Note, `--version` is something user expects to work
646    #[cfg_attr(not(doctest), doc = include_str!("docs2/custom_help_version.md"))]
647    #[must_use]
648    pub fn version_parser(mut self, parser: NamedArg) -> Self {
649        self.info.version_arg = parser;
650        self
651    }
652
653    /// Print help if app was called with no parameters
654    ///
655    /// By default `bpaf` tries to parse command line options and displays the best possible
656    /// error it can come up with. If application requires a subcommand or some argument
657    /// and user specified none - it might be a better experience for user to print
658    /// the help message.
659    ///
660    /// ```rust
661    /// # use bpaf::*;
662    /// # fn options() -> OptionParser<bool> { short('a').switch().to_options() }
663    /// // create option parser in a usual way, derive or combinatoric API
664    /// let opts = options().fallback_to_usage().run();
665    /// ```
666    ///
667    /// For derive macro you can specify `fallback_to_usage` in top level annotations
668    /// for options and for individual commands if fallback to useage is the desired behavior:
669    ///
670    ///
671    /// ```ignore
672    /// #[derive(Debug, Clone, Bpaf)]
673    /// enum Commands {
674    ///     #[bpaf(command, fallback_to_usage)]
675    ///     Action {
676    ///         ...
677    ///     }
678    /// }
679    /// ```
680    ///
681    /// Or
682    ///
683    /// ```ignore
684    /// #[derive(Debug, Clone, Bpaf)]
685    /// #[bpaf(options, fallback_to_usage)]
686    /// struct Options {
687    ///     ...
688    /// }
689    ///
690    /// fn main() {
691    ///     let options = options().run(); // falls back to usage
692    /// }
693    /// ```
694    #[must_use]
695    pub fn fallback_to_usage(mut self) -> Self {
696        self.info.help_if_no_args = true;
697        self
698    }
699
700    /// Set the width of the help message printed to the terminal upon failure
701    ///
702    /// By default, the help message is printed with a width of 100 characters.
703    /// This method allows to change where the help message is wrapped.
704    ///
705    /// Setting the max width too low may negatively affect the readability of the help message.
706    /// Also, the alignment padding of broken lines is always applied.
707    #[must_use]
708    pub fn max_width(mut self, width: usize) -> Self {
709        self.info.max_width = width;
710        self
711    }
712}
713
714impl Info {
715    #[inline(never)]
716    fn mk_help_parser(&self) -> impl Parser<()> {
717        self.help_arg.clone().req_flag(())
718    }
719    #[inline(never)]
720    fn mk_version_parser(&self) -> impl Parser<()> {
721        self.version_arg.clone().req_flag(())
722    }
723}
724
725impl Parser<ExtraParams> for Info {
726    fn eval(&self, args: &mut State) -> Result<ExtraParams, Error> {
727        let help = self.mk_help_parser();
728        if help.eval(args).is_ok() {
729            return Ok(ExtraParams::Help(help.eval(args).is_ok()));
730        }
731
732        if let Some(version) = &self.version {
733            if self.mk_version_parser().eval(args).is_ok() {
734                return Ok(ExtraParams::Version(version.clone()));
735            }
736        }
737
738        // error message is not actually used anywhere
739        Err(Error(Message::ParseFail("not a version or help")))
740    }
741
742    fn meta(&self) -> Meta {
743        let help = self.mk_help_parser().meta();
744        match &self.version {
745            Some(_) => Meta::And(vec![help, self.mk_version_parser().meta()]),
746            None => help,
747        }
748    }
749}
750
751#[derive(Clone, Debug)]
752pub(crate) enum ExtraParams {
753    Help(bool),
754    Version(Doc),
755}