Skip to content

Commit

Permalink
Merge pull request #264 from pacak/rc-0.9.4
Browse files Browse the repository at this point in the history
Grammar and documentation wording
  • Loading branch information
pacak authored Aug 8, 2023
2 parents fb2a94c + 000cb33 commit d6c37ac
Show file tree
Hide file tree
Showing 29 changed files with 293 additions and 288 deletions.
32 changes: 16 additions & 16 deletions documentation/_documentation/_0_intro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,47 @@ from the IDE, derive API uses proc macro to save on typing but your IDE will be
help you. Picking one API style does not lock you out from using the other style, you can mix
and match both in a single parser

# Examples for both styles
# Examples of both styles

#![cfg_attr(not(doctest), doc = include_str!("docs2/intro.md"))]

# Design goals

## Parse, don't validate

`bpaf` tries hard to let you to move as much invariants about the user input you are
`bpaf` tries hard to let you move as many invariants about the user input you are
trying to parse into rust types: for mutually exclusive options you can get `enum` with
exclusive items going into separate branches, you can collect results into types like
exclusive items going into separate branches, and you can collect results into types like
[`BTreeSet`](std::collections::BTreeSet), or whatever custom type you might have with
custom parsing. Ideas for
[making invalid states unrepresentable](https://geeklaunch.io/blog/make-invalid-states-unrepresentable/)
and [using parsing over validation](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)
are not new.

That said you can also validate your inputs if this fits your situation better. If you want to
ensure that sum of every numeric fields must be divisible by both 3 and 5, but only when it's
ensure that the sum of every numeric field must be divisible by both 3 and 5, but only when it's
Thursday - you can do that too.

## Flexibility

While aiming to be a general purpose command line parser `bpaf` offers a few backdoors that
allow you to parse pretty much anything you want: chained commands, custom blocks of options, DOS
style options (`/ofile.pas`), `dd` style options (`if=file of=out`), etc. Similar idea applies
for what the parser can produce - your app operates with boxed string slices internally? `bpaf`
While aiming to be a general-purpose command line parser `bpaf` offers a few backdoors that
allow you to parse pretty much anything you want: chained commands, custom blocks of options,
DOS-style options (`/ofile.pas`), `dd` style options (`if=file of=out`), etc. A similar idea applies
to what the parser can produce - if your app operates with boxed string slices internally - `bpaf`
will give you `Box<str>` instead of `String` if you ask it to.

The only restriction being that you cannot use information from items parsed earlier (but not
The only restriction is that you cannot use information from items parsed earlier (but not
the fact that something was parsed successfully or not) to decide to how to parse further
options, and even then you can side step this restrictions by passing some shared state as a
options, and even then you can side step this restriction by passing some shared state as a
parameter to the parsers.

## Reusability

Parsers in `bpaf` are not monolithic and you can share their parts across multiple binaries,
workspace members or even independent projects. Say you have a multiple binaries in a workspace
workspace members or even independent projects. Say you have multiple binaries in a workspace
that perform different operations on some input. You can declare a parser for the input
specifically, along with all the validations, help messages or shell dynamic completion
functions you need and use it across all the binaries alongside with the arguments specific to
functions you need and use it across all the binaries alongside the arguments specific to
those binaries.

## Composition, transformation
Expand All @@ -61,18 +61,18 @@ Or to make it so parser runs multiple times and collects results into a `Vec`.

## Performance

While performance is an explicit non goal - `bpaf` does nothing that would pessimize it either,
While performance is an explicit non-goal - `bpaf` does nothing that would pessimize it either,
so performance is on par or better compared to other fully featured parsers.

## Correctness

`bpaf` would parse only items it can represent and will reject anything it cannot represent
in the output. Say your parser accepts both `--intel` and `--att` flags, but encodes the result
into `enum Style { Intel, Att }`, `bpaf` will accept those flags separately, but not if they
are used both at once. If parser later collects multipe styles into a `Vec<Style>` then it
are used both at once. If the parser later collects multiple styles into a `Vec<Style>` then it
will accept any combinationof those flags.

## User friendly

`bpaf` tries to provide user friendly error messages, suggestions for typos but also scripts
for shell completion, `man` pages and markdown documentation for web.
`bpaf` tries to provide user-friendly error messages, and suggestions for typos but also scripts
for shell completion, `man` pages and markdown documentation for the web.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#### Options, switches or flags

Options or flags usually starts with a dash, single dash for short options and double dash for
Options or flags usually starts with a dash, a single dash for short options and a double dash for
long one. Several short options can usually be squashed together with a single dash in front of
them to save on typing: `-vvv` can be parsed the same as `-v -v -v`. Options don't have any
other information apart from being there or not. Relative position usually does not matter and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Option arguments are similar to regular options but they come with an extra value attached.
Value can be separated by a space, `=` or directly adjacent to a short name. Same as with
options - relative position usually doesn't matter.
options - their relative position usually doesn't matter.

<div class="code-wrap">
<pre>
Expand All @@ -13,7 +13,7 @@ $ cargo check <span style="font-weight: bold">--bin=megapotato</span>
</div>

In the generated help message or documentation they come with a placeholder metavariable,
usually a short, all caps word describing what the value means: `NAME`, `AGE`, `SPEC`, `CODE`
usually a short, all-caps word describing what the value means: `NAME`, `AGE`, `SPEC`, and `CODE`
are all valid examples.

#![cfg_attr(not(doctest), doc = include_str!("docs2/argument.md"))]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#### Commands or subcommands

Commands are similar to positional items, but instead of representing an item they start
a whole new parser, usually with its own help and other arguments. Commands allow a single
applications to perform multiple different functions. Command parser will be able to parse all
a whole new parser, usually with its help and other arguments. Commands allow a single
application to perform multiple different functions. The command parser will be able to parse all
the command line options to the right of the command name

<div class="code-wrap">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#### Exotic schemas

While modern software tends to use just the options listed above you can still encounter
programs created before those options became norm and they use something complitely different,
programs created before those options became the norm and they use something completely different,
let me give a few examples, see [the parsing cookbook](crate::_documentation::_2_howto)
about actually parsing them

Expand All @@ -18,19 +18,19 @@ this example calls `ls -l` on every file `find` finds.
$ find /etc --exec ls -l '{}' \;
</pre></div>

`Xorg` and related tools use flag like items that start with a single `+` to enable a
`Xorg` and related tools use flag-like items that start with a single `+` to enable a
feature and with `-` to disable it.

<div class="code-wrap"><pre>
$ xorg -backing +xinerama
</pre></div>

`dd` takes several key value pairs, this would create a 100M file
`dd` takes several key-value pairs, this would create a 100M file
<div class="code-wrap"><pre>
$ dd if=/dev/zero of=dummy.bin bs=1M count=100
</pre></div>

Most of the command line arguments in Turbo C++ 3.0 start with `/`. For example option
Most of the command line arguments in Turbo C++ 3.0 start with `/`. For example, option
`/x` tells it to use all available extended memory, while `/x[=n]` limits it to n kilobytes
<div class="code-wrap"><pre>
C:\PROJECT>TC /x=200
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
common types of line options and conventions

This chapter serves as an introduction to available command line options and tries to set the
terminology. If you are familiar with command line argument parsers in general - feel free top
terminology. If you are familiar with command line argument parsers in general - feel free to
skip it.

If you ever used any software from a command line (say `cargo`) you used command line options.
Expand All @@ -17,5 +17,5 @@ $ cargo test -p my_project --verbose
`cargo` here is an executable name, everything to the right of it separated by spaces are the
options.

Nowdays programs share mostly similar conventions about what a command line argument is, it
Nowadays programs share mostly similar conventions about what a command line argument is, it
wasn't the case before though. Let's cover the basic types.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#### Switch parser

Let's start with the simpliest possible one - a simple switch that gets parsed into a `bool`.
Let's start with the simplest possible one - a simple switch that gets parsed into a `bool`.

First of all - switch needs a name - you can start with [`short`] or [`long`] and add more
First of all - the switch needs a name - you can start with [`short`] or [`long`] and add more
names if you want: `long("simple")` or `short('s').long("simple")`. This gives something with
type [`NamedArg`]:
the type [`NamedArg`]:

```rust
# use bpaf::*;
Expand All @@ -14,7 +14,7 @@ fn simple_switch() -> NamedArg {
}
```

From `NamedArg` you make a switch parser by calling [`NamedArg::switch`]. Usually you do it
From `NamedArg` you make a switch parser by calling [`NamedArg::switch`]. Usually, you do it
right away without assigning `NamedArg` to a variable.

```rust
Expand All @@ -24,7 +24,7 @@ fn simple_switch() -> impl Parser<bool> {
}
```

Switch parser we just implements trait [`Parser`] and to run it you convert it to [`OptionParser`] with
The switch parser we just made implements trait [`Parser`] and to run it you convert it to [`OptionParser`] with
[`Parser::to_options`] and run it with [`OptionParser::run`]

Full example with some sample inputs and outputs:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#### Argument parser

Next in complexity would be a parser to consume a named argument, such as `-p my_crate`. Same
as with switch parser it starts from a `NamedArg` but next method is [`NamedArg::argument`].
Method takes a metavariable name - a short description that will be used in the `--help`
output. `rustc` also needs to know the type of a variable you are trying to parse, there's
as with the switch parser it starts from a `NamedArg` but the next method is [`NamedArg::argument`].
This method takes a metavariable name - a short description that will be used in the `--help`
output. `rustc` also needs to know the parameter type you are trying to parse, there are
several ways to do it:

```rust
Expand All @@ -28,7 +28,7 @@ fn file_parser() -> OptionParser<PathBuf> {

You can use any type for as long as it implements [`FromStr`]. To parse items that don't
implement it you can first parse a `String` or `OsString` and then use [`Parser::parse`], see
[the next chapter](super::super::_1_chaining) how to do that.
[the next chapter](super::super::_1_chaining) on how to do that.

Full example with some sample inputs and outputs:
#![cfg_attr(not(doctest), doc = include_str!("docs2/compose_basic_argument.md"))]
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#### Positional item parser

And the last simple option type is parser for positional items. Since there's no name you use
[`positional`] method directly. Similar to [`NamedArg::argument`] this method takes
metavariable name and a type parameter in some form. You can also attach the help message
And the last simple option type is a parser for positional items. Since there's no name you use
the [`positional`] function directly. Similar to [`NamedArg::argument`] this method takes
a metavariable name and a type parameter in some form. You can also attach the help message
thanks to [`ParsePositional::help`]

Full example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Once you have your primitive parsers done you might want to improve them a bit -
values, or change them to consume multiple items, etc. Every primitive (or composite) parser
implements [`Parser`] so most of the transformations are coming from this trait.

Say you a parser that takes a crate name as a required argument you want to use in you own
Say you have a parser that takes a crate name as a required argument you want to use in your own
`cargo test` replacement

```rust
Expand All @@ -14,8 +14,8 @@ fn krate() -> impl Parser<String> {
}
```

You can turn it into, for example, optional argument - something that returns
`Some("my_crate")` if specified or `None` if it wasn't. Or to let user to pass multiple
You can turn it into, for example, an optional argument - something that returns
`Some("my_crate")` if specified or `None` if it wasn't. Or to let the user to pass a multiple
of them and collect them all into a `Vec`


Expand All @@ -39,16 +39,16 @@ fn krates() -> impl Parser<Vec<String>> {
A complete example:
#![cfg_attr(not(doctest), doc = include_str!("docs2/compose_basic_many.md"))]

Transforming a parser with a method from `Parser` trait usually gives you a new parser back and
Transforming a parser with a method from the `Parser` trait usually gives you a new parser back and
you can chain as many transformations as you need.

Transformations available in the `Parser` trait things like adding fallback values, making
parser optional, making it so it consumes many but at least one value, changing how it is
the parser optional, making it so it consumes many but at least one value, changing how it is
being shown in `--help` output, adding additional validation and parsing on top and so on.

Order of those chained transformations matters and for some operations using the right order
makes code cleaner. For example suppose you are trying to write a parser that takes an even
number and this parser should be optional. There's two ways to write it:
The order of those chained transformations matters and for some operations using the right order
makes code cleaner. For example, suppose you are trying to write a parser that takes an even
number and this parser should be optional. There are two ways to write it:

Validation first:

Expand All @@ -74,12 +74,12 @@ fn even() -> impl Parser<Option<usize>> {
}
```

In later case validation function must deal with a possibility where number is absent, for this
In later case validation function must deal with a possibility where a number is absent, for this
specific example it makes code less readable.

One of the important types of transformations you can apply is a set of failing
transformations. Suppose your application operates with numbers and uses `newtype` pattern to
keep track what numbers are odd or even. Parser that consumes an even number can use
keep track of what numbers are odd or even. A parser that consumes an even number can use
[`Parser::parse`] and may look like this:

```rust
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#### Combining multiple simple parsers

A single item option parser can only get you so far. Fortunately you can combine multiple
parsers together with [`construct!`] macro.
A single-item option parser can only get you so far. Fortunately, you can combine multiple
parsers with [`construct!`] macro.

For sequential composition (all the fields must be present) you write your code as if you are
constructing a structure, enum variant or a tuple and wrap it with `construct!`. Both
constructor and parsers must be present in scope. If instead of a parser you have a function
a constructor and parsers must be present in the scope. If instead of a parser you have a function
that creates one - just add `()` after the name:

```rust
Expand All @@ -30,25 +30,25 @@ fn both() -> impl Parser<Options> {
Full example:
#![cfg_attr(not(doctest), doc = include_str!("docs2/compose_basic_construct.md"))]

If you are using positional parsers - they must go to the right most side and will run in
order you specify them. For named parsers order affects only the `--help` message.
If you are using positional parsers - they must go to the right-most side and will run in
the order you specify them. For named parsers order affects only the `--help` message.

Second type of composition `construct!` offers is a parallel composition. You pass multiple
The second type of composition `construct!` offers is a parallel composition. You pass multiple
parsers that produce the same result type in `[]` and `bpaf` selects one that fits best with
the data user gave.


#![cfg_attr(not(doctest), doc = include_str!("docs2/compose_basic_choice.md"))]

If parsers inside parallel composition can parse the same object - longest possible match
should go first since `bpaf` picks earlier parser if everything else is equal, otherwise it
If parsers inside parallel composition can parse the same object - the longest possible match
should go first since `bpaf` picks an earlier parser if everything else is equal, otherwise it
does not matter. In this example `construct!([miles, km])` produces the same results as
`construct!([km, miles])` and only `--help` message is going to be different.

Parsers created with [`construct!`] still implement [`Parser`] trait so you can apply more
Parsers created with [`construct!`] still implement the [`Parser`] trait so you can apply more
transformation on top. For example same as you can make a simple parser optional - you can make
composite parser optional. Such parser will succeed if both `--alpha` and `--beta` are
present or neither of them:
a composite parser optional. Parser transformed this way will succeed if both `--alpha` and
`--beta` are present or neither of them:

```rust
# use bpaf::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#### Improving the user experience

Once you have the final parser done there's still a few ways you can improve user experience.
Once you have the final parser done there are still a few ways you can improve user experience.
[`OptionParser`] comes equipped with a few methods that let you set version number,
description, help message header and footer and so on.

#![cfg_attr(not(doctest), doc = include_str!("docs2/compose_basic_to_options.md"))]

There's a few other things you can do:
There are a few other things you can do:

- group some of the primitive parsers into logical blocks for `--help` message with
[`Parser::group_help`]
- add tests to make sure important combinations are handled the way they supposed to
- add tests to make sure important combinations are handled the way they are supposed to
after any future refactors with [`OptionParser::run_inner`]
- add a test to make sure that bpaf internal invariants are satisfied with
[`OptionParser::check_invariants`]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#### Combinatoric API
Parse arguments without using proc macros

When making parser in the Combinatoric style API you usually go though those steps
When making a parser in the Combinatoric style API you usually go through those steps

1. Design data type your application will receive
2. Design command line options user will have to pass
3. Create a set of simple parsers
4. Combine and transform simple parsers to create the final data type
5. Transform resulting [`Parser`] into [`OptionParser`] and run it
5. Transform the resulting [`Parser`] into [`OptionParser`] and run it

Let's go though some of them in more details:
Let's go through some of them in more detail:
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Let's take a look at a simple example
#![cfg_attr(not(doctest), doc = include_str!("docs2/derive_basic_intro.md"))]

`bpaf` is trying hard to guess what you are trying to achieve just from the types so it will
pick up types, doc comment, presence or absence of names, but it is possible to customize all
pick up types, doc comments, presence or absence of names, but it is possible to customize all
of it, add custom transformations, validations and more.
Loading

0 comments on commit d6c37ac

Please sign in to comment.