You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
start working on guide-level docs
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
# Design
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
# Completions
|
||||
@@ -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`)
|
||||
@@ -0,0 +1 @@
|
||||
# Porting from Clap
|
||||
@@ -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()] }
|
||||
# );
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
# Value trait
|
||||
@@ -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
@@ -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 {}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user