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}