move Arg from associated type to type parameter

This allows a struct to implement Options for multiple types. See the tail test for rationale
This commit is contained in:
Terts Diepraam
2023-02-15 14:53:29 +01:00
parent 8616b87f58
commit 08c0383cb9
14 changed files with 154 additions and 163 deletions
+1 -2
View File
@@ -27,8 +27,7 @@ struct Settings {
count: u8,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Name(n) => self.name = n,
+7 -10
View File
@@ -74,8 +74,7 @@
//!
//! // To implement `Options`, we only need to provide the `apply` method.
//! // The `parse` method will be automatically generated.
//! impl Options for Settings {
//! type Arg = Arg;
//! impl Options<Arg> for Settings {
//! fn apply(&mut self, arg: Arg) {
//! match arg {
//! Arg::NoCaps => self.caps = false,
@@ -316,7 +315,7 @@ pub trait Initial: Sized {
/// Defines the app settings by consuming [`Arguments`].
///
/// When implementing this trait, only two things need to be provided:
/// - the [`Arg`](Options::Arg) type, which defines the type to use for
/// - the `Arg` type parameter, which defines the type to use for
/// argument parsing,
/// - the [`apply`](Options::apply) method, which defines to how map that
/// type onto the options.
@@ -326,11 +325,9 @@ pub trait Initial: Sized {
/// 2. repeatedly call [`ArgumentIter::next_arg`] and call [`Options::apply`]
/// on the result until the arguments are exhausted,
/// 3. and finally call [`Arguments::check_missing`].
pub trait Options: Sized + Initial {
type Arg: Arguments;
pub trait Options<Arg: Arguments>: Sized + Initial {
/// Apply a single argument to the options.
fn apply(&mut self, arg: Self::Arg);
fn apply(&mut self, arg: Arg);
/// Parse an iterator of arguments into
fn parse<I>(args: I) -> Self
@@ -338,7 +335,7 @@ pub trait Options: Sized + Initial {
I: IntoIterator + 'static,
I::Item: Into<OsString>,
{
exit_if_err(Self::try_parse(args), Self::Arg::EXIT_CODE)
exit_if_err(Self::try_parse(args), Arg::EXIT_CODE)
}
fn try_parse<I>(args: I) -> Result<Self, Error>
@@ -347,11 +344,11 @@ pub trait Options: Sized + Initial {
I::Item: Into<OsString>,
{
let mut _self = Self::initial();
let mut iter = Self::Arg::parse(args);
let mut iter = Arg::parse(args);
while let Some(arg) = iter.next_arg()? {
_self.apply(arg);
}
Self::Arg::check_missing(iter.positional_idx)?;
Arg::check_missing(iter.positional_idx)?;
Ok(_self)
}
}
+1 -2
View File
@@ -49,8 +49,7 @@ struct Settings {
files: Vec<PathBuf>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Binary => self.binary = true,
+1 -2
View File
@@ -26,8 +26,7 @@ struct Settings {
file: Option<PathBuf>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Decode => self.decode = true,
+1 -2
View File
@@ -23,8 +23,7 @@ struct Settings {
names: Vec<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Multiple => self.multiple = true,
+1 -2
View File
@@ -53,8 +53,7 @@ struct Settings {
files: Vec<PathBuf>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::ShowAll => {
+1 -2
View File
@@ -27,8 +27,7 @@ struct Settings {
strings: Vec<OsString>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::NoNewline => self.trailing_newline = false,
+1 -2
View File
@@ -336,8 +336,7 @@ struct Settings {
hide_control_chars: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::All => self.which_files = Files::All,
+1 -2
View File
@@ -37,8 +37,7 @@ struct Settings {
template: String,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Directory => self.directory = true,
+99 -65
View File
@@ -1,7 +1,93 @@
use std::path::PathBuf;
use std::{ffi::OsString, path::PathBuf};
use uutils_args::{Arguments, Initial, Options, Value};
#[derive(Arguments)]
enum DeprecatedArg {
#[option("{N}")]
Shorthand(Shorthand),
#[positional]
File(PathBuf),
}
impl Options<DeprecatedArg> for Settings {
fn apply(&mut self, arg: DeprecatedArg) {
match arg {
DeprecatedArg::Shorthand(Shorthand { num, mode, follow }) => {
self.number = num;
self.mode = mode;
self.follow = follow.then_some(FollowMode::Descriptor);
}
DeprecatedArg::File(file) => {
self.inputs.push(file);
}
}
}
}
struct Shorthand {
num: SigNum,
mode: Mode,
follow: bool,
}
// This is not technically 100% compatible with GNU, because the shorthand can
// appear as any argument, not just the first.
impl Value for Shorthand {
fn from_value(value: &std::ffi::OsStr) -> uutils_args::ValueResult<Self> {
let s = String::from_value(value)?;
let mut rest: &str = &s;
let sig = if let Some(r) = rest.strip_prefix('-') {
rest = r;
SigNum::Negative
} else if let Some(r) = rest.strip_prefix('+') {
rest = r;
SigNum::Positive
} else {
return Err("Invalid shorthand".into());
};
// Find and parse the number part of the string
let end_num = rest
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(rest.len());
let num = rest[..end_num].parse().unwrap_or(10);
rest = &rest[end_num..];
let mode = if let Some(r) = rest.strip_prefix('l') {
rest = r;
Mode::Lines
} else if let Some(r) = rest.strip_prefix('c') {
rest = r;
Mode::Bytes
} else if let Some(r) = rest.strip_prefix('b') {
rest = r;
Mode::Blocks
} else {
Mode::Lines
};
let follow = if let Some(r) = rest.strip_prefix('f') {
rest = r;
true
} else {
false
};
if !rest.is_empty() {
return Err("Invalid shorthand!".into());
}
Ok(Self {
num: sig(num),
mode,
follow,
})
}
}
#[derive(Arguments)]
enum Arg {
// TODO: Bytes and Lines should take a `SigNum`
@@ -38,12 +124,6 @@ enum Arg {
#[option("-z", "--zero-terminated")]
Zero,
#[option("-{N}")]
NegativeShorthand(Shorthand),
#[option("+{N}")]
PositiveShorthand(Shorthand),
#[positional(..)]
File(PathBuf),
@@ -51,49 +131,6 @@ enum Arg {
PresumeInputPipe,
}
struct Shorthand {
num: u64,
mode: Mode,
follow: bool,
}
impl Value for Shorthand {
fn from_value(value: &std::ffi::OsStr) -> uutils_args::ValueResult<Self> {
let s = String::from_value(value)?;
let mut rest: &str = &s;
let end_num = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
let num = rest[..end_num].parse().unwrap_or(10);
rest = &rest[end_num..];
let mode = if let Some(r) = rest.strip_prefix('l') {
rest = r;
Mode::Lines
} else if let Some(r) = rest.strip_prefix('c') {
rest = r;
Mode::Bytes
} else if let Some(r) = rest.strip_prefix('b') {
rest = r;
Mode::Blocks
} else {
Mode::Lines
};
let follow = if let Some(r) = rest.strip_prefix('f') {
rest = r;
true
} else {
false
};
if !rest.is_empty() {
return Err("Invalid shorthand!".into());
}
Ok(Self { num, mode, follow })
}
}
// We need both negative and positive 0
#[derive(Debug, PartialEq, Eq)]
enum SigNum {
@@ -139,8 +176,7 @@ struct Settings {
zero: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Bytes(n) => {
@@ -163,35 +199,33 @@ impl Options for Settings {
Arg::SleepInterval(n) => self.sleep_sec = n,
Arg::Verbose => self.verbose = true,
Arg::Zero => self.zero = true,
Arg::NegativeShorthand(Shorthand { num, mode, follow }) => {
self.number = SigNum::Negative(num);
self.mode = mode;
self.follow = follow.then_some(FollowMode::Descriptor);
}
Arg::PositiveShorthand(Shorthand { num, mode, follow }) => {
self.number = SigNum::Positive(num);
self.mode = mode;
self.follow = follow.then_some(FollowMode::Descriptor);
}
Arg::File(input) => self.inputs.push(input),
Arg::PresumeInputPipe => self.presume_input_pipe = true,
}
}
}
fn parse_tail<I>(iter: I) -> Result<Settings, uutils_args::Error>
where
I: IntoIterator + Clone + 'static,
I::Item: Into<OsString>,
{
<Settings as Options<DeprecatedArg>>::try_parse(iter.clone())
.or_else(|_| <Settings as Options<Arg>>::try_parse(iter))
}
#[test]
fn shorthand() {
let s = Settings::try_parse(["tail", "-20"]).unwrap();
let s = parse_tail(["tail", "-20", "somefile"]).unwrap();
assert_eq!(s.number, SigNum::Negative(20));
assert_eq!(s.mode, Mode::Lines);
assert_eq!(s.follow, None);
let s = Settings::try_parse(["tail", "+20"]).unwrap();
let s = parse_tail(["tail", "+20", "somefile"]).unwrap();
assert_eq!(s.number, SigNum::Positive(20));
assert_eq!(s.mode, Mode::Lines);
assert_eq!(s.follow, None);
let s = Settings::try_parse(["tail", "-100cf"]).unwrap();
let s = parse_tail(["tail", "-100cf", "somefile"]).unwrap();
assert_eq!(s.number, SigNum::Negative(100));
assert_eq!(s.mode, Mode::Bytes);
assert_eq!(s.follow, Some(FollowMode::Descriptor));
+2 -4
View File
@@ -14,8 +14,7 @@ fn true_default() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo: Arg) {
self.foo = false;
}
@@ -39,8 +38,7 @@ fn env_var_string() {
foo: String,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo(x): Arg) {
self.foo = x;
}
+21 -34
View File
@@ -13,9 +13,8 @@ fn one_flag() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, arg: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Foo => self.foo = true,
}
@@ -42,9 +41,8 @@ fn two_flags() {
b: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, arg: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::A => self.a = true,
Arg::B => self.b = true,
@@ -80,9 +78,8 @@ fn long_and_short_flag() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, Arg::Foo: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo: Arg) {
self.foo = true;
}
}
@@ -105,9 +102,8 @@ fn short_alias() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, Arg::Foo: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo: Arg) {
self.foo = true;
}
}
@@ -128,9 +124,8 @@ fn long_alias() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, Arg::Foo: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo: Arg) {
self.foo = true;
}
}
@@ -154,9 +149,8 @@ fn short_and_long_alias() {
bar: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, arg: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Foo => self.foo = true,
Arg::Bar => self.bar = true,
@@ -199,9 +193,8 @@ fn xyz_map_to_abc() {
c: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, arg: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::X => {
self.a = true;
@@ -273,9 +266,8 @@ fn non_rust_ident() {
b: bool,
}
impl Options for Settings {
type Arg = Arg;
fn apply(&mut self, arg: Self::Arg) {
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::FooBar => self.a = true,
Arg::Super => self.b = true,
@@ -301,8 +293,7 @@ fn number_flag() {
one: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::One: Arg) {
self.one = true;
}
@@ -326,8 +317,7 @@ fn false_bool() {
foo: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
self.foo = match arg {
Arg::A => true,
@@ -357,8 +347,7 @@ fn verbosity() {
verbosity: u8,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Verbosity: Arg) {
self.verbosity += 1;
}
@@ -388,8 +377,7 @@ fn infer_long_args() {
author: bool,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::All => self.all = true,
@@ -430,8 +418,7 @@ fn enum_flag() {
foo: SomeEnum,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
self.foo = match arg {
Arg::Foo => SomeEnum::Foo,
+11 -22
View File
@@ -15,8 +15,7 @@ fn string_option() {
message: String,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Message(s): Arg) {
self.message = s
}
@@ -52,8 +51,7 @@ fn enum_option() {
format: Format,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Format(f): Arg) {
self.format = f;
}
@@ -92,8 +90,7 @@ fn enum_option_with_fields() {
indent: Indent,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Indent(i): Arg) {
self.indent = i;
}
@@ -142,8 +139,7 @@ fn enum_with_complex_from_value() {
indent: Indent,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Indent(i): Arg) {
self.indent = i;
}
@@ -178,8 +174,7 @@ fn color() {
color: Color,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Color(c): Arg) {
self.color = c.unwrap_or(Color::Always);
}
@@ -221,8 +216,7 @@ fn actions() {
messages: Vec<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Message(m) => {
@@ -254,8 +248,7 @@ fn width() {
width: Option<u64>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Width(w): Arg) {
self.width = match w {
0 => None,
@@ -299,8 +292,7 @@ fn integers() {
n: i128,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
self.n = match arg {
Arg::U8(x) => x as i128,
@@ -357,8 +349,7 @@ fn ls_classify() {
classify: When,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Classify(c): Arg) {
self.classify = c;
}
@@ -393,8 +384,7 @@ fn mktemp_tmpdir() {
tmpdir: Option<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::TmpDir(dir): Arg) {
self.tmpdir = Some(dir);
}
@@ -450,8 +440,7 @@ fn deprecated() {
n2: isize,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Min(n) => self.n1 = n,
+6 -12
View File
@@ -13,8 +13,7 @@ fn one_positional() {
file1: String,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::File1(f): Arg) {
self.file1 = f;
}
@@ -42,8 +41,7 @@ fn two_positionals() {
bar: String,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Foo(x) => self.foo = x,
@@ -72,8 +70,7 @@ fn optional_positional() {
foo: Option<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo(x): Arg) {
self.foo = Some(x);
}
@@ -98,8 +95,7 @@ fn collect_positional() {
foo: Vec<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo(x): Arg) {
self.foo.push(x);
}
@@ -124,8 +120,7 @@ fn last1() {
foo: Vec<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, Arg::Foo(x): Arg) {
self.foo = x;
}
@@ -151,8 +146,7 @@ fn last2() {
foo: Vec<String>,
}
impl Options for Settings {
type Arg = Arg;
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::A => {}