diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f8e8b05..539ff4b 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -27,8 +27,7 @@ struct Settings { count: u8, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Name(n) => self.name = n, diff --git a/src/lib.rs b/src/lib.rs index ea06a3b..f3a1036 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 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: 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(args: I) -> Self @@ -338,7 +335,7 @@ pub trait Options: Sized + Initial { I: IntoIterator + 'static, I::Item: Into, { - exit_if_err(Self::try_parse(args), Self::Arg::EXIT_CODE) + exit_if_err(Self::try_parse(args), Arg::EXIT_CODE) } fn try_parse(args: I) -> Result @@ -347,11 +344,11 @@ pub trait Options: Sized + Initial { I::Item: Into, { 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) } } diff --git a/tests/coreutils/b2sum.rs b/tests/coreutils/b2sum.rs index 4ed82bd..52c8036 100644 --- a/tests/coreutils/b2sum.rs +++ b/tests/coreutils/b2sum.rs @@ -49,8 +49,7 @@ struct Settings { files: Vec, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Binary => self.binary = true, diff --git a/tests/coreutils/base32.rs b/tests/coreutils/base32.rs index 47a635d..03120b5 100644 --- a/tests/coreutils/base32.rs +++ b/tests/coreutils/base32.rs @@ -26,8 +26,7 @@ struct Settings { file: Option, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Decode => self.decode = true, diff --git a/tests/coreutils/basename.rs b/tests/coreutils/basename.rs index 50d25d9..231a9fc 100644 --- a/tests/coreutils/basename.rs +++ b/tests/coreutils/basename.rs @@ -23,8 +23,7 @@ struct Settings { names: Vec, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Multiple => self.multiple = true, diff --git a/tests/coreutils/cat.rs b/tests/coreutils/cat.rs index 0f52694..d4c9928 100644 --- a/tests/coreutils/cat.rs +++ b/tests/coreutils/cat.rs @@ -53,8 +53,7 @@ struct Settings { files: Vec, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::ShowAll => { diff --git a/tests/coreutils/echo.rs b/tests/coreutils/echo.rs index 1295ee8..d42fe69 100644 --- a/tests/coreutils/echo.rs +++ b/tests/coreutils/echo.rs @@ -27,8 +27,7 @@ struct Settings { strings: Vec, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::NoNewline => self.trailing_newline = false, diff --git a/tests/coreutils/ls.rs b/tests/coreutils/ls.rs index c42e74a..46273bd 100644 --- a/tests/coreutils/ls.rs +++ b/tests/coreutils/ls.rs @@ -336,8 +336,7 @@ struct Settings { hide_control_chars: bool, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::All => self.which_files = Files::All, diff --git a/tests/coreutils/mktemp.rs b/tests/coreutils/mktemp.rs index b2494e6..b80b352 100644 --- a/tests/coreutils/mktemp.rs +++ b/tests/coreutils/mktemp.rs @@ -37,8 +37,7 @@ struct Settings { template: String, } -impl Options for Settings { - type Arg = Arg; +impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Directory => self.directory = true, diff --git a/tests/coreutils/tail.rs b/tests/coreutils/tail.rs index bb138b7..b8797f8 100644 --- a/tests/coreutils/tail.rs +++ b/tests/coreutils/tail.rs @@ -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 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 { + 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 { - 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 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(iter: I) -> Result +where + I: IntoIterator + Clone + 'static, + I::Item: Into, +{ + >::try_parse(iter.clone()) + .or_else(|_| >::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)); diff --git a/tests/defaults.rs b/tests/defaults.rs index 90fabe8..44a31ef 100644 --- a/tests/defaults.rs +++ b/tests/defaults.rs @@ -14,8 +14,7 @@ fn true_default() { foo: bool, } - impl Options for Settings { - type Arg = Arg; + impl Options 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 for Settings { fn apply(&mut self, Arg::Foo(x): Arg) { self.foo = x; } diff --git a/tests/flags.rs b/tests/flags.rs index ddcab7a..0d4b1fe 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 for Settings { fn apply(&mut self, arg: Arg) { self.foo = match arg { Arg::Foo => SomeEnum::Foo, diff --git a/tests/options.rs b/tests/options.rs index 11f286e..03c1133 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -15,8 +15,7 @@ fn string_option() { message: String, } - impl Options for Settings { - type Arg = Arg; + impl Options 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 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 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 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 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, } - impl Options for Settings { - type Arg = Arg; + impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Message(m) => { @@ -254,8 +248,7 @@ fn width() { width: Option, } - impl Options for Settings { - type Arg = Arg; + impl Options 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 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 for Settings { fn apply(&mut self, Arg::Classify(c): Arg) { self.classify = c; } @@ -393,8 +384,7 @@ fn mktemp_tmpdir() { tmpdir: Option, } - impl Options for Settings { - type Arg = Arg; + impl Options 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 for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::Min(n) => self.n1 = n, diff --git a/tests/positionals.rs b/tests/positionals.rs index 2decccf..5e0bd3b 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -13,8 +13,7 @@ fn one_positional() { file1: String, } - impl Options for Settings { - type Arg = Arg; + impl Options 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 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, } - impl Options for Settings { - type Arg = Arg; + impl Options for Settings { fn apply(&mut self, Arg::Foo(x): Arg) { self.foo = Some(x); } @@ -98,8 +95,7 @@ fn collect_positional() { foo: Vec, } - impl Options for Settings { - type Arg = Arg; + impl Options for Settings { fn apply(&mut self, Arg::Foo(x): Arg) { self.foo.push(x); } @@ -124,8 +120,7 @@ fn last1() { foo: Vec, } - impl Options for Settings { - type Arg = Arg; + impl Options for Settings { fn apply(&mut self, Arg::Foo(x): Arg) { self.foo = x; } @@ -151,8 +146,7 @@ fn last2() { foo: Vec, } - impl Options for Settings { - type Arg = Arg; + impl Options for Settings { fn apply(&mut self, arg: Arg) { match arg { Arg::A => {}