start working on guide-level docs

This commit is contained in:
Terts Diepraam
2023-12-13 18:53:30 +01:00
parent 645f700765
commit d62daf9155
13 changed files with 374 additions and 18 deletions
+15
View File
@@ -18,6 +18,21 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data::Enum, DeriveInput};
/// Derive `Arguments`
///
/// ## Argument specifications
///
/// | specification | kind | value |
/// | -------------- | ---------- | -------- |
/// | `VAL` | positional | n.a. |
/// | `-s` | short | none |
/// | `-s VAL` | short | required |
/// | `-s[VAL]` | short | optional |
/// | `--long` | long | none |
/// | `--long=VAL` | long | required |
/// | `--long[=VAL]` | long | optional |
/// | `long=VAL` | dd | required |
///
#[proc_macro_derive(Arguments, attributes(arg, arguments))]
pub fn arguments(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
@@ -109,12 +109,25 @@ Some utils take positional arguments, which might be required.
### Deprecated syntax `+N` and `-N`
Some utils (e.g. `head`, `tail`, `kill`, `fold` and `uniq`) support an old deprecated syntax where numbers can be directly passed as arguments as a shorthand. For example, `uniq +5` is a shorthand for `uniq -s 5` and `uniq -5` is short for `uniq -f 5`.
Some utils (e.g. `head`, `tail`, `kill`, `fold` and `uniq`) support an old
deprecated syntax where numbers can be directly passed as arguments as a
shorthand. For example, `uniq +5` is a shorthand for `uniq -s 5` and `uniq -5`
is short for `uniq -f 5`.
These all behave slightly differently.
1. `head` and `tail` only accept this if it is the first argument and either 1 or 2 arguments are given.
2. In `fold` the `-N` must be standalone (e.g. `-10b` is rejected), but can appear at any position.
3. In `kill`, the same rules as `fold` apply, but it can also be a name instead of a number.
4. In `uniq`, the syntax does not need to stand alone and is additive in a weird way, because they hack `-22` as `-2 -2` so each flag `-1...-9` multiplies the previous by 10 and adds itself. I'm not sure that we need to support this. Doing something like what `fold` and `kill` do is probably fine. Also note that to make it extra confusing, the `+` variant works like `fold`.
1. `head` and `tail` only accept this if it is the first argument and either 1
or 2 arguments are given.
2. In `fold` the `-N` must be standalone (e.g. `-10b` is rejected), but can
appear at any position.
3. In `kill`, the same rules as `fold` apply, but it can also be a name instead
of a number.
4. In `uniq`, the syntax does not need to stand alone and is additive in a weird
way, because they hack `-22` as `-2 -2` so each flag `-1...-9` multiplies the
previous by 10 and adds itself. I'm not sure that we need to support this.
Doing something like what `fold` and `kill` do is probably fine. Also note
that to make it extra confusing, the `+` variant works like `fold`.
5. `pr` the behaviour is similar to `uniq`.
6. `split` seems to be somewhere between `uniq` and `fold`. It accepts things like `-x10x` correctly, but it doesn't do the additive thing from `uniq` across multiple occurrences. Basically, it's very clever and cursed.
6. `split` seems to be somewhere between `uniq` and `fold`. It accepts things
like `-x10x` correctly, but it doesn't do the additive thing from `uniq`
across multiple occurrences. Basically, it's very clever and cursed.
+1
View File
@@ -0,0 +1 @@
# Design
+12 -8
View File
@@ -64,10 +64,10 @@ struct Settings {
> is always obvious where an argument is defined.
As part of the `Options` derive, we get a `Settings::parse` method that returns
a `Settings` from a `OsString` iterator. The implementation of
this is defined by the `set` and `map` attributes. `map` just says: "if we
encounter this value in the iterator set this value", using a match-like syntax
(it expands to a match). And the `#[set(Arg::Name)]` is just short for
a `Settings` from a `OsString` iterator. The implementation of this is defined
by the `set` and `map` attributes. `map` just says: "if we encounter this value
in the iterator set this value", using a match-like syntax (it expands to a
match). And the `#[set(Arg::Name)]` is just short for
`#[map(Arg::Name(name) => name)]`, because that is a commonly appearing pattern.
Importantly, arguments can appear in the attributes for multiple fields. We
@@ -147,7 +147,9 @@ enum Arg {
## Options struct
The options struct has just one fundamental attribute: `map`. It works much like a `match` expression (in fact, that's what it expands to). Furthermore, it's possible to define defaults on fields.
The options struct has just one fundamental attribute: `map`. It works much like
a `match` expression (in fact, that's what it expands to). Furthermore, it's
possible to define defaults on fields.
```rust
#[derive(Options, Default)]
@@ -180,7 +182,8 @@ struct Settings {
}
```
As a shorthand, there is also a `set` attribute. These fields behave identically:
As a shorthand, there is also a `set` attribute. These fields behave
identically:
```rust
#[derive(Options, Default)]
@@ -195,7 +198,8 @@ struct Settings {
## `FromValue` enums
We often want to map values to some enum, we can define this mapping by deriving `FromValue`:
We often want to map values to some enum, we can define this mapping by deriving
`FromValue`:
```rust
#[derive(Default, FromValue)]
@@ -210,4 +214,4 @@ enum Color {
#[value("never", "no", "none")]
Never,
}
```
```
@@ -111,7 +111,7 @@ uutils diverge.
`clap`'s arguments are identified by strings. This leads to code like this:
```rust
```rust,ignore
const OPT_NAME: &'static str = "name";
// -- snip --
@@ -183,9 +183,10 @@ libraries.
- Does not support a many-to-many relationship.
- [`bpaf`](https://github.com/pacak/bpaf)
- Extremely flexible, even supports `dd`-style.
- A different configuration between short and long options requires a workaround.
- A different configuration between short and long options requires a
workaround.
- A many-to-many relation ship is possible, though not very ergonomic.
- For more information, see: https://github.com/uutils/uutils-args/issues/17
- For more information, see: <https://github.com/uutils/uutils-args/issues/17>
- [`gumdrop`](https://github.com/murarth/gumdrop)
- Does not handle invalid UTF-8.
- Not configurable enough.
+1
View File
@@ -0,0 +1 @@
# Completions
+12
View File
@@ -0,0 +1,12 @@
# Guide
This module provides guide-level documentation for [`uutils-args`](crate). If
you're unfamiliar with this library you probably want to start with the first
chapter below and work your way through.
## Chapters
1. [Quick Start](guide::quick)
2. [Porting a Parser from `clap`](guide::port)
3. [The `Value` trait](`guide::value`)
4. [Generating completions](`guide::completions`)
+1
View File
@@ -0,0 +1 @@
# Porting from Clap
+278
View File
@@ -0,0 +1,278 @@
# Quick Start
A parser consists of two parts:
- an `enum` implementing [`Arguments`](crate::Arguments)
- an `struct` implementing [`Options`](crate::Options)
The `enum` defines all the arguments that your application accepts. The `struct` represents all configuration options for the application. In other words, the `struct` is the internal representation of the options, while the `enum` is the external representation.
## A single flag
We can create arguments by annotating a variant of an `enum` deriving [`Arguments`](crate::Arguments) with the `arg` attribute. This attribute takes strings that define the arguments. A short flag, for instance, looks like `"-f"` and a long flag looks like `"--flag"`. The full syntax for the arguments specifications can be found in the documentation for the [`Arguments` derive macro](derive@crate::Arguments)
To represent the program configuration we create a struct called `Settings`, which implements `Options<Arg>`. When an argument is encountered, we _apply_ it to the `Settings` struct. In this case, we set the `force` field of `Settings` to `true` if `Arg::Force` is parsed.
```rust
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("-f", "--force")]
Force,
}
// Note: Debug, PartialEq and Eq are only necessary for assert_eq! below.
#[derive(Default, Debug, PartialEq, Eq)]
struct Settings {
force: bool
}
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Force => self.force = true,
}
}
}
assert_eq!(Settings::default().parse(["test"]), Settings { force: false });
assert_eq!(Settings::default().parse(["test", "-f"]), Settings { force: true });
```
## Two overriding flags
Of course, we can define multiple flags. If these arguments change the same fields of `Settings`, then they will override. This is important: by default none of the arguments will "conflict", they will always simply be processed in order.
```rust
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("-f", "--force")]
Force,
#[arg("-F", "--no-force")]
NoForce,
}
// Note: Debug, PartialEq and Eq are only necessary for assert_eq! below.
#[derive(Default, Debug, PartialEq, Eq)]
struct Settings {
force: bool
}
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Force => self.force = true,
Arg::NoForce => self.force = false,
}
}
}
assert_eq!(Settings::default().parse(["test"]), Settings { force: false });
assert_eq!(Settings::default().parse(["test", "-f"]), Settings { force: true });
assert_eq!(Settings::default().parse(["test", "-F"]), Settings { force: false });
```
## Help strings
We can document our flags in two ways: by giving them a docstring or by giving the `arg` attribute a `help` argument. Note that the `help` argument will take precedence over the docstring.
```rust
use uutils_args::Arguments;
#[derive(Arguments)]
enum Arg {
/// Force!
#[arg("-f", "--force")]
Force,
#[arg("-F", "--no-force", help = "No! Don't force!")]
NoForce,
}
```
## Arguments with required values
So far, our arguments have been simple flags that do not take any arguments, but `uutils-args` supports much more! If we want an argument for our option, the corresponding variant on our `enum` needs to take an argument too.
> **Note**: In the example below, we use `OsString`. A regular `String` works too, but is generally discouraged in `coreutils`, because we often have to support text with invalid UTF-8.
```rust
# use uutils_args::{Arguments, Options};
# use std::ffi::OsString;
#
#[derive(Arguments)]
enum Arg {
#[arg("-n NAME", "--name=NAME")]
Name(OsString),
}
#
# // Note: Debug, PartialEq and Eq are only necessary for assert_eq! below.
# #[derive(Default, Debug, PartialEq, Eq)]
# struct Settings {
# name: OsString
# }
#
# impl Options<Arg> for Settings {
# fn apply(&mut self, arg: Arg) {
# match arg {
# Arg::Name(name) => self.name = name,
# }
# }
# }
#
# assert_eq!(
# Settings::default().parse(["test"]),
# Settings { name: OsString::new() }
# );
# assert_eq!(
# Settings::default().parse(["test", "--name=John"]),
# Settings { name: OsString::from("John")}
# );
```
## Arguments with optional values
Arguments with optional values are possible, too. However, we have to give a value to be used if the value is not given. Below, we set that value to `OsString::from("anonymous")`, with the `value` argument of `arg`.
```rust
# use uutils_args::{Arguments, Options};
# use std::ffi::OsString;
#
#[derive(Arguments)]
enum Arg {
#[arg("-n[NAME]", "--name[=NAME]", value = OsString::from("anonymous"))]
Name(OsString),
}
#
# #[derive(Default, Debug, PartialEq, Eq)]
# struct Settings {
# name: OsString
# }
#
# impl Options<Arg> for Settings {
# fn apply(&mut self, arg: Arg) {
# match arg {
# Arg::Name(name) => self.name = name,
# }
# }
# }
#
# assert_eq!(
# Settings::default().parse(["test", "--name"]),
# Settings { name: OsString::from("anonymous")}
# );
# assert_eq!(
# Settings::default().parse(["test", "--name=John"]),
# Settings { name: OsString::from("John")}
# );
```
## Multiple arguments per variant
Here's a neat trick: you can use multiple `arg` attributes per variant. Recall the `--force/--no-force` example above. We could have written that as follows:
```rust
# use uutils_args::{Arguments, Options};
#
#[derive(Arguments)]
enum Arg {
#[arg("-f", "--force", value = true, help = "enable force")]
#[arg("-F", "--no-force", value = false, help = "disable force")]
Force(bool),
}
#
# #[derive(Default, Debug, PartialEq, Eq)]
# struct Settings {
# force: bool
# }
#
# impl Options<Arg> for Settings {
# fn apply(&mut self, arg: Arg) {
# match arg {
# Arg::Force(b) => self.force = b,
# }
# }
# }
#
# assert_eq!(Settings::default().parse(["test"]), Settings { force: false });
# assert_eq!(Settings::default().parse(["test", "-f"]), Settings { force: true });
# assert_eq!(Settings::default().parse(["test", "-F"]), Settings { force: false });
```
This is particularly interesting for defining "shortcut" arguments. For example, `ls` takes a `--sort=WORD` argument, that defines how the files should be sorted. But it also has shorthands like `-t`, which is the same as `--sort=time`. All of these can be implemented on one variant:
> **Note**: The `--sort` argument should not take a `String` as value. We've done that here for illustrative purposes. It should actually use an `enum` with the `Value` trait.
```rust
# use uutils_args::{Arguments, Options};
#
#[derive(Arguments)]
enum Arg {
#[arg("--sort=WORD", help = "Sort by WORD")]
#[arg("-t", value = String::from("time"), help = "Sort by time")]
#[arg("-U", value = String::from("none"), help = "Do not sort")]
#[arg("-v", value = String::from("version"), help = "Sort by version")]
#[arg("-X", value = String::from("extension"), help = "Sort by extension")]
Sort(String),
}
#
# #[derive(Default, Debug, PartialEq, Eq)]
# struct Settings {
# sort: String
# }
#
# impl Options<Arg> for Settings {
# fn apply(&mut self, arg: Arg) {
# match arg {
# Arg::Sort(s) => self.sort = s,
# }
# }
# }
#
# assert_eq!(Settings::default().parse(["test"]), Settings { sort: String::new() });
# assert_eq!(Settings::default().parse(["test", "--sort=time"]), Settings { sort: String::from("time") });
# assert_eq!(Settings::default().parse(["test", "-t"]), Settings { sort: String::from("time") });
```
## Positional arguments
Finally, at the end of this whirlwind tour, we get to positional arguments. Here's a simple positional argument:
```rust
use uutils_args::{Arguments, Options};
use std::path::PathBuf;
#[derive(Arguments)]
enum Arg {
#[arg("FILES", ..)]
File(PathBuf)
}
#[derive(Default, Debug, PartialEq, Eq)]
struct Settings {
files: Vec<PathBuf>,
}
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::File(f) => self.files.push(f),
}
}
}
#
# assert_eq!(
# Settings::default().parse(["test"]),
# Settings { files: Vec::new() }
# );
# assert_eq!(
# Settings::default().parse(["test", "foo"]),
# Settings { files: vec![PathBuf::from("foo")] }
# );
# assert_eq!(
# Settings::default().parse(["test", "foo", "bar"]),
# Settings { files: vec!["foo".into(), "bar".into()] }
# );
```
+1
View File
@@ -0,0 +1 @@
# Value trait
+2 -1
View File
@@ -14,7 +14,8 @@ decisions. Before diving in, let's lay out the design goals of this project.
fewer features to support.
- Use outside uutils is possible but not prioritized. Hence, configurability
beyond the coreutils is not necessary.
- Errors must be at least as good as GNU's, but may be different (hopefully improved).
- Errors must be at least as good as GNU's, but may be different (hopefully
improved).
## Pages
+24
View File
@@ -0,0 +1,24 @@
//! This module contains only documentation to be rendered by rustdoc.
//!
//! - [Guide](guide): the guide for using this library
//! - [Design](design): documents about the design of this library
#[doc = include_str!("../docs/guide/guide.md")]
pub mod guide {
#[doc = include_str!("../docs/guide/quick.md")]
pub mod quick {}
#[doc = include_str!("../docs/guide/port.md")]
pub mod port {}
#[doc = include_str!("../docs/guide/completions.md")]
pub mod completions {}
#[doc = include_str!("../docs/guide/value.md")]
pub mod value {}
}
#[doc = include_str!("../docs/design/design.md")]
pub mod design {
#[doc = include_str!("../docs/design/arguments_in_coreutils.md")]
pub mod coreutils {}
#[doc = include_str!("../docs/design/problems_with_clap.md")]
pub mod problems {}
}
+4
View File
@@ -1,12 +1,16 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! [Click here to check out the guide-level documentation](docs::guide)
#![doc = include_str!("../README.md")]
mod error;
pub mod internal;
mod value;
#[cfg(doc)]
pub mod docs;
pub use lexopt;
pub use uutils_args_derive::*;