diff --git a/Cargo.toml b/Cargo.toml index ee4818a..9b4ebfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" [dependencies] derive = { version = "0.1.0", path = "derive" } -lexopt = "0.2.1" +lexopt = "0.3.0" term_md = { version = "0.1.0", path = "term_md" } [workspace] diff --git a/derive/src/action.rs b/derive/src/action.rs deleted file mode 100644 index 4fea590..0000000 --- a/derive/src/action.rs +++ /dev/null @@ -1,70 +0,0 @@ -use syn::{ - parenthesized, - parse::{Nothing, Parse, ParseStream}, - punctuated::Punctuated, - Attribute, Token, -}; - -pub(crate) struct ActionAttr { - pub(crate) action_type: ActionType, - pub(crate) collect: bool, -} - -pub(crate) enum ActionType { - Set(Vec), - Map(Vec), -} - -fn parse_paths(attr: &Attribute) -> Vec { - attr.parse_args_with(Punctuated::::parse_terminated) - .into_iter() - .flatten() - .collect() -} - -pub(crate) fn parse_action_attr(attr: &Attribute) -> Option { - if attr.path.is_ident("collect") { - let inner: ActionType = attr.parse_args().unwrap(); - Some(ActionAttr { - action_type: inner, - collect: true, - }) - } else if attr.path.is_ident("map") { - Some(ActionAttr { - action_type: ActionType::Map( - attr.parse_args_with(Punctuated::::parse_terminated) - .into_iter() - .flatten() - .collect(), - ), - collect: false, - }) - } else if attr.path.is_ident("set") { - Some(ActionAttr { - action_type: ActionType::Set(parse_paths(attr)), - collect: false, - }) - } else { - None - } -} - -impl Parse for ActionType { - fn parse(input: ParseStream) -> syn::Result { - let action: syn::Ident = input.parse()?; - let action = action.to_string(); - let content; - parenthesized!(content in input); - if action == "map" { - let arms = content.call(Punctuated::::parse_terminated)?; - Ok(ActionType::Map(arms.into_iter().collect())) - } else { - let pat = content.call(Punctuated::::parse_terminated)?; - let pat = pat.into_iter().collect(); - match &action[..] { - "set" => Ok(ActionType::Set(pat)), - _ => panic!("Unexpected action type in collect {}", action), - } - } - } -} diff --git a/derive/src/field.rs b/derive/src/field.rs index 6a33489..1282bf1 100644 --- a/derive/src/field.rs +++ b/derive/src/field.rs @@ -2,15 +2,11 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{Attribute, Field, Ident}; -use crate::{ - action::{parse_action_attr, ActionAttr, ActionType}, - attributes::FieldAttr, -}; +use crate::attributes::FieldAttr; pub(crate) struct FieldData { pub(crate) ident: Ident, pub(crate) default_value: TokenStream, - pub(crate) match_stmt: TokenStream, } pub(crate) fn parse_field(field: &Field) -> FieldData { @@ -32,21 +28,9 @@ pub(crate) fn parse_field(field: &Field) -> FieldData { ) } - let match_arms = field - .attrs - .iter() - .filter_map(parse_action_attr) - .flat_map(|attr| action_attr_to_match_arms(&field_ident, attr)); - - let match_stmt = quote!(match arg.clone() { - #(#match_arms)*, - _ => {} - }); - FieldData { ident: field_ident, default_value, - match_stmt, } } @@ -58,47 +42,3 @@ pub(crate) fn parse_field_attr(attrs: &[Attribute]) -> FieldAttr { } FieldAttr::default() } - -fn action_attr_to_match_arms(field_ident: &Ident, attr: ActionAttr) -> Vec { - let mut match_arms = Vec::new(); - match attr.action_type { - ActionType::Map(arms) => { - for arm in arms { - match_arms.push(field_expression( - arm.pat.to_token_stream(), - arm.body.to_token_stream(), - field_ident, - attr.collect, - )); - } - } - - ActionType::Set(pats) => { - let pats: Vec<_> = pats.iter().map(|p| quote!(#p(x))).collect(); - match_arms.push(field_expression( - quote!(#(#pats)|*), - quote!(x), - field_ident, - attr.collect, - )); - } - }; - match_arms -} - -fn field_expression( - pat: TokenStream, - expr: TokenStream, - field_ident: &Ident, - collect: bool, -) -> TokenStream { - if collect { - quote!( - #pat => { self.#field_ident.push(#expr) } - ) - } else { - quote!( - #pat => { self.#field_ident = #expr } - ) - } -} diff --git a/derive/src/lib.rs b/derive/src/lib.rs index ff4d26f..816b255 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,4 +1,3 @@ -mod action; mod argument; mod attributes; mod field; @@ -16,24 +15,18 @@ use help::{help_handling, help_string, version_handling}; use proc_macro::TokenStream; use quote::quote; use syn::{ - parse::Parse, + // parse::Parse, parse_macro_input, Data::{Enum, Struct}, - DeriveInput, Fields, + DeriveInput, + Fields, }; -#[proc_macro_derive(Options, attributes(arg_type, map, set, field, collect))] +#[proc_macro_derive(Initial, attributes(field))] pub fn options(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; - let arg_type = input - .attrs - .iter() - .find(|a| a.path.is_ident("arg_type")) - .expect("An Options struct must have a `arg_type` attribute") - .parse_args_with(syn::Ident::parse) - .expect("The `arg_type` attribute must contain a valid identifier."); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let Struct(data) = input.data else { @@ -46,53 +39,23 @@ pub fn options(input: TokenStream) -> TokenStream { // The key of this map is a literal pattern and the value // is whatever code needs to be run when that pattern is encountered. - let mut stmts = Vec::new(); let mut defaults = Vec::new(); for field in fields.named { let FieldData { ident, default_value, - match_stmt, } = parse_field(&field); defaults.push(quote!(#ident: #default_value)); - stmts.push(match_stmt); } let expanded = quote!( - impl #impl_generics Options for #name #ty_generics #where_clause { - type Arg = #arg_type; - + impl #impl_generics Initial for #name #ty_generics #where_clause { fn initial() -> Result { Ok(Self { #(#defaults),* }) } - - fn apply_args(&mut self, args: I) -> Result<(), uutils_args::Error> - where - I: IntoIterator + 'static, - I::Item: Into, - { - use uutils_args::{lexopt, FromValue, Argument}; - let mut iter = ::Arg::parse(args); - while let Some(arg) = iter.next_arg()? { - match arg { - Argument::Help => { - print!("{}", iter.help()); - std::process::exit(0); - }, - Argument::Version => { - println!("{}", iter.version()); - }, - Argument::Custom(arg) => { - #(#stmts)* - } - } - } - ::Arg::check_missing(iter.positional_idx)?; - Ok(()) - } } ); diff --git a/examples/hello_world.rs b/examples/hello_world.rs index ba2e8f6..f8e8b05 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,11 +1,7 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; -#[derive(Clone, Arguments)] -#[arguments( - help = ["--help"], - version = ["--version"], - file = "examples/hello_world_help.md" -)] +#[derive(Arguments)] +#[arguments(file = "examples/hello_world_help.md")] enum Arg { /// The *name* to **greet** /// @@ -24,15 +20,24 @@ enum Arg { Hidden, } -#[derive(Default, Options)] -#[arg_type(Arg)] +#[derive(Initial)] struct Settings { - #[set(Arg::Name)] name: String, - #[set(Arg::Count)] + #[field(default = 1)] count: u8, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Name(n) => self.name = n, + Arg::Count(c) => self.count = c, + Arg::Hidden => {} + } + } +} + fn main() -> Result<(), uutils_args::Error> { let settings = Settings::parse(std::env::args_os()); for _ in 0..settings.count { diff --git a/src/lib.rs b/src/lib.rs index dfcfb34..d4c341a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,17 @@ pub enum Argument { Custom(T), } -pub trait Arguments: Sized + Clone { +fn exit_if_err(res: Result, exit_code: i32) -> T { + match res { + Ok(v) => v, + Err(err) => { + eprintln!("{err}"); + std::process::exit(exit_code); + } + } +} + +pub trait Arguments: Sized { const EXIT_CODE: i32; fn parse(args: I) -> ArgumentIter @@ -36,6 +46,24 @@ pub trait Arguments: Sized + Clone { fn help(bin_name: &str) -> String; fn version() -> String; + + fn check(args: I) + where + I: IntoIterator + 'static, + I::Item: Into, + { + exit_if_err(Self::try_check(args), Self::EXIT_CODE) + } + + fn try_check(args: I) -> Result<(), Error> + where + I: IntoIterator + 'static, + I::Item: Into, + { + let mut iter = Self::parse(args); + while iter.next_arg()?.is_some() {} + Ok(()) + } } pub struct ArgumentIter { @@ -57,34 +85,48 @@ impl ArgumentIter { } } - pub fn next_arg(&mut self) -> Result>, Error> { - T::next_arg(&mut self.parser, &mut self.positional_idx) + pub fn next_arg(&mut self) -> Result, Error> { + if let Some(arg) = T::next_arg(&mut self.parser, &mut self.positional_idx)? { + match arg { + Argument::Help => { + print!("{}", self.help()); + std::process::exit(0); + } + Argument::Version => { + print!("{}", self.version()); + std::process::exit(0); + } + Argument::Custom(arg) => Ok(Some(arg)), + } + } else { + Ok(None) + } } - pub fn help(&self) -> String { + fn help(&self) -> String { T::help(self.parser.bin_name().unwrap()) } - pub fn version(&self) -> String { + fn version(&self) -> String { T::version() } } -pub trait Options: Sized + Default { +pub trait Initial: Sized { + fn initial() -> Result; +} + +pub trait Options: Sized + Initial { type Arg: Arguments; + fn apply(&mut self, arg: Self::Arg); + fn parse(args: I) -> Self where I: IntoIterator + 'static, I::Item: Into, { - match Self::try_parse(args) { - Ok(v) => v, - Err(err) => { - eprintln!("{err}"); - std::process::exit(::Arg::EXIT_CODE); - } - } + exit_if_err(Self::try_parse(args), Self::Arg::EXIT_CODE) } fn try_parse(args: I) -> Result @@ -93,16 +135,13 @@ pub trait Options: Sized + Default { I::Item: Into, { let mut _self = Self::initial()?; - _self.apply_args(args)?; + let mut iter = Self::Arg::parse(args); + while let Some(arg) = iter.next_arg()? { + _self.apply(arg); + } + Self::Arg::check_missing(iter.positional_idx)?; Ok(_self) } - - fn initial() -> Result; - - fn apply_args(&mut self, args: I) -> Result<(), Error> - where - I: IntoIterator + 'static, - I::Item: Into; } pub trait FromValue: Sized { diff --git a/tests/coreutils/arch.rs b/tests/coreutils/arch.rs index 5bf497d..d3198d3 100644 --- a/tests/coreutils/arch.rs +++ b/tests/coreutils/arch.rs @@ -1,20 +1,16 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::Arguments; -#[derive(Clone, Arguments)] +#[derive(Arguments)] enum Arg {} -#[derive(Default, Options)] -#[arg_type(Arg)] -struct Settings {} - #[test] fn no_args() { - assert!(Settings::try_parse(["arch"]).is_ok()); + assert!(Arg::try_check(["arch"]).is_ok()); } #[test] fn one_arg_fails() { - assert!(Settings::try_parse(["arch", "-f"]).is_err()); - assert!(Settings::try_parse(["arch", "--foo"]).is_err()); - assert!(Settings::try_parse(["arch", "foo"]).is_err()); + assert!(Arg::try_check(["arch", "-f"]).is_err()); + assert!(Arg::try_check(["arch", "--foo"]).is_err()); + assert!(Arg::try_check(["arch", "foo"]).is_err()); } diff --git a/tests/coreutils/b2sum.rs b/tests/coreutils/b2sum.rs index 2cb39a6..1514ba4 100644 --- a/tests/coreutils/b2sum.rs +++ b/tests/coreutils/b2sum.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[derive(Clone, Arguments)] enum Arg { @@ -40,35 +40,33 @@ enum CheckOutput { Status, } -#[derive(Default, Options)] -#[arg_type(Arg)] +#[derive(Initial)] struct Settings { - #[map( - Arg::Binary => true, - Arg::Text => false, - )] binary: bool, - - #[map(Arg::Check => true)] check: bool, - - #[map(Arg::Tag => true)] tag: bool, - - #[map( - Arg::Warn => CheckOutput::Warn, - Arg::Quiet => CheckOutput::Quiet, - Arg::Status => CheckOutput::Status, - )] check_output: CheckOutput, - - #[map(Arg::Strict => true)] strict: bool, - - #[collect(set(Arg::File))] files: Vec, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Binary => self.binary = true, + Arg::Check => self.check = true, + Arg::Tag => self.tag = true, + Arg::Text => self.binary = false, + Arg::Quiet => self.check_output = CheckOutput::Quiet, + Arg::Status => self.check_output = CheckOutput::Status, + Arg::Strict => self.strict = true, + Arg::Warn => self.check_output = CheckOutput::Warn, + Arg::File(f) => self.files.push(f), + } + } +} + #[test] fn binary() { assert!(!Settings::parse(["b2sum"]).binary); diff --git a/tests/coreutils/base32.rs b/tests/coreutils/base32.rs index 3656e0e..47a635d 100644 --- a/tests/coreutils/base32.rs +++ b/tests/coreutils/base32.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[derive(Clone, Arguments)] enum Arg { @@ -17,26 +17,28 @@ enum Arg { File(PathBuf), } -#[derive(Options, Default)] -#[arg_type(Arg)] +#[derive(Initial)] struct Settings { - #[map(Arg::Decode => true)] decode: bool, - - #[map(Arg::IgnoreGarbage => true)] ignore_garbage: bool, - - #[map( - Arg::Wrap(0) => None, - Arg::Wrap(n) => Some(n), - )] #[field(default = Some(76))] wrap: Option, - - #[map(Arg::File(f) => Some(f))] file: Option, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Decode => self.decode = true, + Arg::IgnoreGarbage => self.ignore_garbage = true, + Arg::Wrap(0) => self.wrap = None, + Arg::Wrap(x) => self.wrap = Some(x), + Arg::File(f) => self.file = Some(f), + } + } +} + #[test] fn wrap() { assert_eq!(Settings::parse(["base32"]).wrap, Some(76)); diff --git a/tests/coreutils/basename.rs b/tests/coreutils/basename.rs index 984dab4..50d25d9 100644 --- a/tests/coreutils/basename.rs +++ b/tests/coreutils/basename.rs @@ -1,4 +1,4 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[derive(Clone, Arguments)] enum Arg { @@ -15,22 +15,29 @@ enum Arg { Names(Vec), } -#[derive(Default, Options)] -#[arg_type(Arg)] +#[derive(Initial)] struct Settings { - #[map(Arg::Multiple | Arg::Suffix(_) => true)] multiple: bool, - - #[set(Arg::Suffix)] suffix: String, - - #[map(Arg::Zero => true)] zero: bool, - - #[set(Arg::Names)] names: Vec, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Multiple => self.multiple = true, + Arg::Suffix(s) => { + self.multiple = true; + self.suffix = s + } + Arg::Zero => self.zero = true, + Arg::Names(names) => self.names = names, + } + } +} + fn parse(args: &'static [&'static str]) -> Settings { let mut settings = Settings::parse(args); if !settings.multiple { diff --git a/tests/coreutils/cat.rs b/tests/coreutils/cat.rs index 1063066..0f52694 100644 --- a/tests/coreutils/cat.rs +++ b/tests/coreutils/cat.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[derive(Default)] enum NumberingMode { @@ -43,37 +43,44 @@ enum Arg { File(PathBuf), } -#[derive(Default, Options)] -#[arg_type(Arg)] +#[derive(Initial)] struct Settings { - #[map(Arg::ShowAll | Arg::ShowTabs | Arg::ShowNonPrintingTabs => true)] show_tabs: bool, - - #[map(Arg::ShowAll | Arg::ShowEnds | Arg::ShowNonPrintingEnds => true)] show_ends: bool, - - #[map( - Arg::ShowAll - | Arg::ShowNonPrintingEnds - | Arg::ShowNonPrintingTabs - | Arg::ShowNonPrinting - => true - )] show_nonprinting: bool, - - #[map( - Arg::Number => NumberingMode::All, - Arg::NumberNonblank => NumberingMode::NonEmpty, - )] number: NumberingMode, - - #[map(Arg::SqueezeBlank => true)] squeeze_blank: bool, - - #[collect(set(Arg::File))] files: Vec, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::ShowAll => { + self.show_tabs = true; + self.show_ends = true; + self.show_nonprinting = true; + } + Arg::ShowNonPrintingEnds => { + self.show_nonprinting = true; + self.show_ends = true; + } + Arg::ShowNonPrintingTabs => { + self.show_tabs = true; + self.show_nonprinting = true; + } + Arg::ShowEnds => self.show_ends = true, + Arg::ShowTabs => self.show_tabs = true, + Arg::ShowNonPrinting => self.show_nonprinting = true, + Arg::Number => self.number = NumberingMode::All, + Arg::NumberNonblank => self.number = NumberingMode::NonEmpty, + Arg::SqueezeBlank => self.squeeze_blank = true, + Arg::File(f) => self.files.push(f), + } + } +} + #[test] fn show() { let s = Settings::parse(["cat", "-v"]); diff --git a/tests/coreutils/ls.rs b/tests/coreutils/ls.rs index ed17c23..7494ad0 100644 --- a/tests/coreutils/ls.rs +++ b/tests/coreutils/ls.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; -use uutils_args::{Arguments, FromValue, Options}; +use uutils_args::{Arguments, FromValue, Initial, Options}; -#[derive(Clone, Default, Debug, PartialEq, Eq, FromValue)] +#[derive(Default, Debug, PartialEq, Eq, FromValue)] enum Format { #[value("long")] Long, @@ -20,7 +20,7 @@ enum Format { Commas, } -#[derive(Clone, Default, Debug, PartialEq, Eq, FromValue)] +#[derive(Default, Debug, PartialEq, Eq, FromValue)] enum When { #[value("yes", "always", "force")] Always, @@ -60,7 +60,7 @@ enum Dereference { All, } -#[derive(Clone, Default, Debug, PartialEq, Eq, FromValue)] +#[derive(Default, Debug, PartialEq, Eq, FromValue)] enum QuotingStyle { #[value("literal")] Literal, @@ -85,7 +85,7 @@ enum QuotingStyle { Escape, } -#[derive(Clone, Default, Debug, PartialEq, Eq, FromValue)] +#[derive(Default, Debug, PartialEq, Eq, FromValue)] enum Sort { #[default] Name, @@ -103,7 +103,7 @@ enum Sort { Width, } -#[derive(Clone, Default, Debug, PartialEq, Eq, FromValue)] +#[derive(Default, Debug, PartialEq, Eq, FromValue)] enum Time { #[default] Modification, @@ -115,7 +115,7 @@ enum Time { Birth, } -#[derive(Clone, Default, Debug, FromValue, PartialEq, Eq)] +#[derive(Default, Debug, FromValue, PartialEq, Eq)] enum IndicatorStyle { #[default] #[value("none")] @@ -128,7 +128,7 @@ enum IndicatorStyle { Classify, } -#[derive(Clone, Arguments)] +#[derive(Arguments)] enum Arg { // === Files === /// Do not ignore entries starting with . @@ -333,135 +333,118 @@ fn default_terminal_size() -> u16 { 80 } -#[derive(Default, Options, Debug, PartialEq, Eq)] -#[arg_type(Arg)] +#[derive(Initial, Debug, PartialEq, Eq)] struct Settings { - #[map( - Arg::Long | Arg::LongNoGroup | Arg::LongNoOwner | Arg::LongNumericUidGid => Format::Long, - Arg::Columns => Format::Columns, - Arg::Across => Format::Across, - Arg::Commas => Format::Commas, - Arg::SingleColumn => Format::SingleColumn, - Arg::Format(f) => f, - )] format: Format, - - #[collect(set(Arg::File))] files: Vec, - - #[map( - Arg::Sort(s) => s, - Arg::SortTime => Sort::Time, - Arg::SortNone => Sort::None, - Arg::SortVersion => Sort::Version, - Arg::SortExtension => Sort::Extension, - )] sort: Sort, - - #[map(Arg::Recursive => true)] recursive: bool, - - #[map(Arg::Reverse => true)] reverse: bool, - - #[map( - Arg::DerefAll => Dereference::All, - Arg::DerefDirArgs => Dereference::DirArgs, - Arg::DerefArgs => Dereference::Args, - )] dereference: Dereference, - - #[collect(set(Arg::Ignore))] ignore_patterns: Vec, - // // size_format: SizeFormat, - // - #[map(Arg::Directory => true)] directory: bool, - - #[map( - Arg::ChangeTime => Time::Change, - Arg::AccessTime => Time::Access, - Arg::Time(t) => t, - )] time: Time, - - #[map(Arg::Inode => true)] inode: bool, - - #[map(Arg::Color(when) => when.to_bool())] color: bool, - - #[map(Arg::Author => true)] long_author: bool, - - #[map(Arg::LongNoGroup => true)] long_no_group: bool, - - #[map(Arg::LongNoOwner => true)] long_no_owner: bool, - - #[map(Arg::LongNumericUidGid => true)] long_numeric_uid_gid: bool, - // alloc_size: bool, - // block_size: Option, - #[set(Arg::Width)] #[field(default = default_terminal_size())] width: u16, - - #[map( - Arg::QuotingStyle(q) => q, - Arg::Literal => QuotingStyle::Literal, - Arg::Escape => QuotingStyle::Escape, - )] quoting_style: QuotingStyle, - - #[map( - Arg::IndicatorStyleClassify(when) => { - if when.to_bool() { - IndicatorStyle::Classify - } else { - IndicatorStyle::None - } - } - Arg::IndicatorStyle(style) => style, - Arg::IndicatorStyleSlash => IndicatorStyle::Slash, - Arg::IndicatorStyleFileType => IndicatorStyle::FileType, - )] indicator_style: IndicatorStyle, - - // TODO for the full implementation, to complicated - // to do here. // time_style: TimeStyle, - // - #[map(Arg::SecurityContext => true)] context: bool, - - #[map(Arg::GroupDirectoriesFirst => true)] group_directories_first: bool, - - #[map(Arg::Zero => '\0')] #[field(default = '\n')] eol: char, - - #[map( - Arg::AlmostAll => Files::AlmostAll, - Arg::All => Files::All, - )] which_files: Files, - - #[map(Arg::IgnoreBackups => true)] ignore_backups: bool, - - #[map( - Arg::HideControlChars => true, - Arg::ShowControlChars => false, - )] hide_control_chars: bool, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::All => self.which_files = Files::All, + Arg::AlmostAll => self.which_files = Files::AlmostAll, + Arg::Author => self.long_author = true, + Arg::ChangeTime => self.time = Time::Change, + Arg::AccessTime => self.time = Time::Access, + Arg::Time(t) => self.time = t, + Arg::Sort(s) => self.sort = s, + Arg::SortTime => self.sort = Sort::Time, + Arg::SortNone => self.sort = Sort::None, + Arg::SortVersion => self.sort = Sort::Version, + Arg::SortExtension => self.sort = Sort::Extension, + Arg::SecurityContext => self.context = true, + Arg::IgnoreBackups => self.ignore_backups = true, + Arg::Directory => self.directory = true, + Arg::Dired => todo!(), + Arg::Hyperlink(_when) => todo!(), + Arg::Inode => self.inode = true, + Arg::Ignore(pattern) => self.ignore_patterns.push(pattern), + Arg::Reverse => self.reverse = true, + Arg::Recursive => self.recursive = true, + Arg::Width(w) => self.width = w, + Arg::AllocationSize => todo!(), + Arg::NoGroup => self.long_no_group = true, + Arg::Long => self.format = Format::Long, + Arg::Columns => self.format = Format::Columns, + Arg::Across => self.format = Format::Across, + Arg::Commas => self.format = Format::Commas, + Arg::SingleColumn => self.format = Format::SingleColumn, + Arg::LongNoGroup => { + self.format = Format::Long; + self.long_no_group = true; + } + Arg::LongNoOwner => { + self.format = Format::Long; + self.long_no_owner = true; + } + Arg::LongNumericUidGid => { + self.format = Format::Long; + self.long_numeric_uid_gid = true; + } + Arg::Format(f) => self.format = f, + Arg::IndicatorStyle(style) => self.indicator_style = style, + Arg::IndicatorStyleSlash => self.indicator_style = IndicatorStyle::Slash, + Arg::IndicatorStyleFileType => self.indicator_style = IndicatorStyle::FileType, + Arg::IndicatorStyleClassify(when) => { + self.indicator_style = if when.to_bool() { + IndicatorStyle::Classify + } else { + IndicatorStyle::None + } + } + Arg::DerefAll => self.dereference = Dereference::All, + Arg::DerefDirArgs => self.dereference = Dereference::DirArgs, + Arg::DerefArgs => self.dereference = Dereference::Args, + Arg::HumanReadable => todo!(), + Arg::Kibibytes => todo!(), + Arg::Si => todo!(), + Arg::QuotingStyle(style) => self.quoting_style = style, + Arg::Literal => self.quoting_style = QuotingStyle::Literal, + Arg::Escape => self.quoting_style = QuotingStyle::Escape, + Arg::QuoteName => todo!(), + Arg::Color(when) => self.color = when.to_bool(), + Arg::HideControlChars => self.hide_control_chars = true, + Arg::ShowControlChars => self.hide_control_chars = false, + Arg::Zero => { + self.eol = '\0'; + // TODO: Zero changes more than just this + } + Arg::GroupDirectoriesFirst => self.group_directories_first = true, + Arg::File(f) => self.files.push(f), + } + } +} + #[test] fn default() { assert_eq!( diff --git a/tests/coreutils/mktemp.rs b/tests/coreutils/mktemp.rs index 765150a..b2494e6 100644 --- a/tests/coreutils/mktemp.rs +++ b/tests/coreutils/mktemp.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[derive(Clone, Arguments)] enum Arg { @@ -26,31 +26,32 @@ enum Arg { Template(String), } -#[derive(Default, Options)] -#[arg_type(Arg)] +#[derive(Default, Initial)] struct Settings { - #[map(Arg::Directory => true)] directory: bool, - - #[map(Arg::DryRun => true)] dry_run: bool, - - #[map(Arg::Quiet => true)] quiet: bool, - - #[map(Arg::TmpDir(p) => Some(p))] tmp_dir: Option, - - #[map(Arg::Suffix(s) => Some(s))] suffix: Option, - - #[map(Arg::TreatAsTemplate => true)] treat_as_template: bool, - - #[set(Arg::Template)] template: String, } +impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Directory => self.directory = true, + Arg::DryRun => self.dry_run = true, + Arg::Quiet => self.quiet = true, + Arg::Suffix(s) => self.suffix = Some(s), + Arg::TreatAsTemplate => self.treat_as_template = true, + Arg::TmpDir(dir) => self.tmp_dir = Some(dir), + Arg::Template(s) => self.template = s, + } + } +} + #[test] fn suffix() { let s = Settings::parse(["mktemp", "--suffix=hello"]); diff --git a/tests/defaults.rs b/tests/defaults.rs index 90dd677..90fabe8 100644 --- a/tests/defaults.rs +++ b/tests/defaults.rs @@ -1,41 +1,51 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[test] fn true_default() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--foo")] Foo, } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo => false)] #[field(default = true)] foo: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo: Arg) { + self.foo = false; + } + } + assert!(Settings::parse(["test"]).foo); assert!(!Settings::parse(["test", "--foo"]).foo); } #[test] fn env_var_string() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--foo=MSG")] Foo(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Foo)] #[field(env = "FOO")] foo: String, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo(x): Arg) { + self.foo = x; + } + } + std::env::set_var("FOO", "one"); assert_eq!(Settings::parse(["test"]).foo, "one"); diff --git a/tests/flags.rs b/tests/flags.rs index 7994836..ddcab7a 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -1,20 +1,27 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[test] fn one_flag() { - #[derive(Arguments, Clone, Debug, PartialEq, Eq)] + #[derive(Arguments)] enum Arg { #[option("-f", "--foo")] Foo, } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo => true)] foo: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Self::Arg) { + match arg { + Arg::Foo => self.foo = true, + } + } + } + let settings = Settings::parse(["test", "-f"]); assert!(settings.foo); } @@ -29,15 +36,22 @@ fn two_flags() { B, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial, PartialEq, Eq, Debug)] struct Settings { - #[map(Arg::A => true)] a: bool, - #[map(Arg::B => true)] b: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Self::Arg) { + match arg { + Arg::A => self.a = true, + Arg::B => self.b = true, + } + } + } + assert_eq!( Settings::parse(["test", "-a"]), Settings { a: true, b: false } @@ -55,63 +69,78 @@ fn two_flags() { #[test] fn long_and_short_flag() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-f", "--foo")] Foo, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo => true)] foo: bool, } - assert_eq!(Settings::parse(["test"]), Settings { foo: false },); - assert_eq!(Settings::parse(["test", "--foo"]), Settings { foo: true },); - assert_eq!(Settings::parse(["test", "-f"]), Settings { foo: true },); + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo: Self::Arg) { + self.foo = true; + } + } + + assert!(!Settings::parse(["test"]).foo); + assert!(Settings::parse(["test", "--foo"]).foo); + assert!(Settings::parse(["test", "-f"]).foo); } #[test] fn short_alias() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-b")] Foo, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo => true)] foo: bool, } - assert_eq!(Settings::parse(["test", "-b"]), Settings { foo: true },); + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo: Self::Arg) { + self.foo = true; + } + } + + assert!(Settings::parse(["test", "-b"]).foo); } #[test] fn long_alias() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--bar")] Foo, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo => true)] foo: bool, } - assert_eq!(Settings::parse(["test", "--bar"]), Settings { foo: true },); + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo: Self::Arg) { + self.foo = true; + } + } + + assert!(Settings::parse(["test", "--bar"]).foo); } #[test] fn short_and_long_alias() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-b", "--bar")] Foo, @@ -119,15 +148,22 @@ fn short_and_long_alias() { Bar, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial, PartialEq, Eq, Debug)] struct Settings { - #[map(Arg::Foo => true)] foo: bool, - #[map(Arg::Bar => true)] bar: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Self::Arg) { + match arg { + Arg::Foo => self.foo = true, + Arg::Bar => self.bar = true, + } + } + } + let foo_true = Settings { foo: true, bar: false, @@ -146,7 +182,7 @@ fn short_and_long_alias() { #[test] fn xyz_map_to_abc() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-x")] X, @@ -156,17 +192,34 @@ fn xyz_map_to_abc() { Z, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial, PartialEq, Eq, Debug)] struct Settings { - #[map(Arg::X | Arg::Z => true)] a: bool, - #[map(Arg::X | Arg::Y | Arg::Z => true)] b: bool, - #[map(Arg::Y | Arg::Z => true)] c: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Self::Arg) { + match arg { + Arg::X => { + self.a = true; + self.b = true; + } + Arg::Y => { + self.b = true; + self.c = true; + } + Arg::Z => { + self.a = true; + self.b = true; + self.c = true; + } + } + } + } + assert_eq!( Settings::parse(["test", "-x"]), Settings { @@ -206,7 +259,7 @@ fn xyz_map_to_abc() { #[test] fn non_rust_ident() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--foo-bar")] FooBar, @@ -214,15 +267,22 @@ fn non_rust_ident() { Super, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial, PartialEq, Eq, Debug)] struct Settings { - #[map(Arg::FooBar => true)] a: bool, - #[map(Arg::Super => true)] b: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Self::Arg) { + match arg { + Arg::FooBar => self.a = true, + Arg::Super => self.b = true, + } + } + } + assert_eq!( Settings::parse(["test", "--foo-bar", "--super"]), Settings { a: true, b: true } @@ -236,19 +296,24 @@ fn number_flag() { #[option("-1")] One, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial, PartialEq, Eq, Debug)] struct Settings { - #[map(Arg::One => true)] one: bool, } - assert_eq!(Settings::parse(["test", "-1"]), Settings { one: true }) + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::One: Arg) { + self.one = true; + } + } + + assert!(Settings::parse(["test", "-1"]).one) } #[test] fn false_bool() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-a")] A, @@ -256,106 +321,49 @@ fn false_bool() { B, } - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map( - Arg::A => true, - Arg::B => false, - )] foo: bool, } - assert_eq!(Settings::parse(["test", "-a"]), Settings { foo: true }); - assert_eq!(Settings::parse(["test", "-b"]), Settings { foo: false }); - assert_eq!(Settings::parse(["test", "-ab"]), Settings { foo: false }); - assert_eq!(Settings::parse(["test", "-ba"]), Settings { foo: true }); - assert_eq!( - Settings::parse(["test", "-a", "-b"]), - Settings { foo: false } - ); - assert_eq!( - Settings::parse(["test", "-b", "-a"]), - Settings { foo: true } - ); - - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] - struct Settings2 { - #[map( - Arg::A => true, - Arg::B => false, - )] - foo: bool, + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + self.foo = match arg { + Arg::A => true, + Arg::B => false, + } + } } - assert_eq!(Settings2::parse(["test", "-a"]), Settings2 { foo: true }); - assert_eq!(Settings2::parse(["test", "-b"]), Settings2 { foo: false }); - assert_eq!(Settings2::parse(["test", "-ab"]), Settings2 { foo: false }); - assert_eq!(Settings2::parse(["test", "-ba"]), Settings2 { foo: true }); - assert_eq!( - Settings2::parse(["test", "-a", "-b"]), - Settings2 { foo: false } - ); - assert_eq!( - Settings2::parse(["test", "-b", "-a"]), - Settings2 { foo: true } - ); + assert!(Settings::parse(["test", "-a"]).foo); + assert!(!Settings::parse(["test", "-b"]).foo); + assert!(!Settings::parse(["test", "-ab"]).foo); + assert!(Settings::parse(["test", "-ba"]).foo); + assert!(!Settings::parse(["test", "-a", "-b"]).foo); + assert!(Settings::parse(["test", "-b", "-a"]).foo); } #[test] -fn enum_flag() { - #[derive(Default, PartialEq, Eq, Debug, Clone)] - enum SomeEnum { - #[default] - Foo, - Bar, - Baz, - } - - #[derive(Arguments, Clone)] - enum Arg { - #[option("--foo")] - Foo, - #[option("--bar")] - Bar, - #[option("--baz")] - Baz, - } - - #[derive(Default, Options, PartialEq, Eq, Debug)] - #[arg_type(Arg)] - struct Settings { - #[map( - Arg::Foo => SomeEnum::Foo, - Arg::Bar => SomeEnum::Bar, - Arg::Baz => SomeEnum::Baz, - )] - foo: SomeEnum, - } - - assert_eq!(Settings::parse(&[] as &[&str]).foo, SomeEnum::Foo); - - assert_eq!(Settings::parse(["test", "--bar"]).foo, SomeEnum::Bar); - - assert_eq!(Settings::parse(["test", "--baz"]).foo, SomeEnum::Baz,); -} - -#[test] -fn count() { - #[derive(Arguments, Clone)] +fn verbosity() { + #[derive(Arguments)] enum Arg { #[option("-v")] Verbosity, } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Verbosity => self.verbosity + 1)] verbosity: u8, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Verbosity: Arg) { + self.verbosity += 1; + } + } + assert_eq!(Settings::parse(["test", "-v"]).verbosity, 1); assert_eq!(Settings::parse(["test", "-vv"]).verbosity, 2); assert_eq!(Settings::parse(["test", "-vvv"]).verbosity, 3); @@ -363,7 +371,7 @@ fn count() { #[test] fn infer_long_args() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--all")] All, @@ -373,21 +381,67 @@ fn infer_long_args() { Author, } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::All => true)] all: bool, - - #[map(Arg::AlmostAll => true)] almost_all: bool, - - #[map(Arg::Author => true)] author: bool, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::All => self.all = true, + Arg::AlmostAll => self.almost_all = true, + Arg::Author => self.author = true, + } + } + } + assert!(Settings::parse(["test", "--all"]).all); assert!(Settings::parse(["test", "--alm"]).almost_all); assert!(Settings::parse(["test", "--au"]).author); assert!(Settings::try_parse(["test", "--a"]).is_err()); } + +#[test] +fn enum_flag() { + #[derive(Default, PartialEq, Eq, Debug)] + enum SomeEnum { + #[default] + Foo, + Bar, + Baz, + } + + #[derive(Arguments)] + enum Arg { + #[option("--foo")] + Foo, + #[option("--bar")] + Bar, + #[option("--baz")] + Baz, + } + + #[derive(Initial)] + struct Settings { + foo: SomeEnum, + } + + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + self.foo = match arg { + Arg::Foo => SomeEnum::Foo, + Arg::Bar => SomeEnum::Bar, + Arg::Baz => SomeEnum::Baz, + }; + } + } + + assert_eq!(Settings::parse(["test"]).foo, SomeEnum::Foo); + assert_eq!(Settings::parse(["test", "--bar"]).foo, SomeEnum::Bar); + assert_eq!(Settings::parse(["test", "--baz"]).foo, SomeEnum::Baz,); +} diff --git a/tests/options.rs b/tests/options.rs index 9c505a0..7c1e726 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -1,22 +1,27 @@ use std::ffi::OsString; -use uutils_args::{Arguments, FromValue, Options}; +use uutils_args::{Arguments, FromValue, Initial, Options}; #[test] fn string_option() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--message=MSG")] Message(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Message)] message: String, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Message(s): Arg) { + self.message = s + } + } + assert_eq!( Settings::parse(["test", "--message=hello"]).message, "hello" @@ -36,19 +41,24 @@ fn enum_option() { Baz, } - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--format=FORMAT")] Format(Format), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Format)] format: Format, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Format(f): Arg) { + self.format = f; + } + } + assert_eq!( Settings::parse(["test", "--format=bar"]).format, Format::Bar @@ -62,7 +72,7 @@ fn enum_option() { #[test] fn enum_option_with_fields() { - #[derive(FromValue, Default, Debug, PartialEq, Eq, Clone)] + #[derive(FromValue, Default, Debug, PartialEq, Eq)] enum Indent { #[default] Tabs, @@ -71,19 +81,24 @@ fn enum_option_with_fields() { Spaces(u8), } - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-i INDENT")] Indent(Indent), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Indent)] indent: Indent, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Indent(i): Arg) { + self.indent = i; + } + } + assert_eq!( Settings::parse(["test", "-i=thin"]).indent, Indent::Spaces(4) @@ -120,26 +135,31 @@ fn enum_with_complex_from_value() { } } - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-i INDENT")] Indent(Indent), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Indent(i) => i.clone())] indent: Indent, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Indent(i): Arg) { + self.indent = i; + } + } + assert_eq!(Settings::parse(["test", "-i=tabs"]).indent, Indent::Tabs); assert_eq!(Settings::parse(["test", "-i=4"]).indent, Indent::Spaces(4)); } #[test] fn color() { - #[derive(Default, FromValue, Debug, PartialEq, Eq, Clone)] + #[derive(Default, FromValue, Debug, PartialEq, Eq)] enum Color { #[value("yes", "always")] Always, @@ -150,22 +170,25 @@ fn color() { Never, } - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--color[=WHEN]")] Color(Option), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map( - Arg::Color(Some(c)) => c.clone(), - Arg::Color(None) => Color::Always, - )] + #[field(default = Color::Auto)] color: Color, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Color(c): Arg) { + self.color = c.unwrap_or(Color::Always); + } + } + assert_eq!( Settings::parse(["test", "--color=yes"]).color, Color::Always @@ -185,7 +208,7 @@ fn color() { #[test] fn actions() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-m MESSAGE")] Message(String), @@ -195,58 +218,63 @@ fn actions() { Receive, } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Message(m) => m.clone())] - message1: String, - - #[set(Arg::Message)] - message2: String, - - #[map( - Arg::Send => true, - Arg::Receive => false, - )] + last_message: String, send: bool, - - // Or map, true or false inside the collect - #[collect(set(Arg::Message))] messages: Vec, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Message(m) => { + self.last_message = m.clone(); + self.messages.push(m); + } + Arg::Send => self.send = true, + Arg::Receive => self.send = false, + } + } + } + let settings = Settings::parse(["test", "-m=Hello", "-m=World", "--send"]); assert_eq!(settings.messages, vec!["Hello", "World"]); - assert_eq!(settings.message1, "World"); - assert_eq!(settings.message2, "World"); + assert_eq!(settings.last_message, "World"); assert!(settings.send); } #[test] fn width() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("-w WIDTH")] Width(u64), } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map( - Arg::Width(0) => None, - Arg::Width(x) => Some(x), - )] width: Option, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Width(w): Arg) { + self.width = match w { + 0 => None, + x => Some(x), + } + } + } + assert_eq!(Settings::parse(["test", "-w=0"]).width, None); assert_eq!(Settings::parse(["test", "-w=1"]).width, Some(1)); } #[test] fn integers() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[option("--u8=N")] U8(u8), @@ -270,24 +298,29 @@ fn integers() { I128(i128), } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map( - Arg::U8(x) => x as i128, - Arg::U16(x) => x as i128, - Arg::U32(x) => x as i128, - Arg::U64(x) => x as i128, - Arg::U128(x) => x as i128, - Arg::I8(x) => x as i128, - Arg::I16(x) => x as i128, - Arg::I32(x) => x as i128, - Arg::I64(x) => x as i128, - Arg::I128(x) => x, - )] n: i128, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + self.n = match arg { + Arg::U8(x) => x as i128, + Arg::U16(x) => x as i128, + Arg::U32(x) => x as i128, + Arg::U64(x) => x as i128, + Arg::U128(x) => x as i128, + Arg::I8(x) => x as i128, + Arg::I16(x) => x as i128, + Arg::I32(x) => x as i128, + Arg::I64(x) => x as i128, + Arg::I128(x) => x, + } + } + } + assert_eq!(Settings::parse(["test", "--u8=5"]).n, 5); assert_eq!(Settings::parse(["test", "--u16=5"]).n, 5); assert_eq!(Settings::parse(["test", "--u32=5"]).n, 5); @@ -303,7 +336,7 @@ fn integers() { #[test] fn ls_classify() { - #[derive(FromValue, Default, Clone, PartialEq, Eq, Debug)] + #[derive(FromValue, Default, PartialEq, Eq, Debug)] enum When { #[value] Never, @@ -314,7 +347,7 @@ fn ls_classify() { Always, } - #[derive(Clone, Arguments)] + #[derive(Arguments)] enum Arg { #[option( "-F", "--classify[=WHEN]", @@ -323,13 +356,18 @@ fn ls_classify() { Classify(When), } - #[derive(Options, Default)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Classify)] classify: When, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Classify(c): Arg) { + self.classify = c; + } + } + assert_eq!(Settings::parse(["test"]).classify, When::Auto); assert_eq!( Settings::parse(["test", "--classify=never"]).classify, @@ -354,13 +392,18 @@ fn mktemp_tmpdir() { TmpDir(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::TmpDir(dir) => Some(dir))] tmpdir: Option, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::TmpDir(dir): Arg) { + self.tmpdir = Some(dir); + } + } + let settings = Settings::parse(["test", "-p", "X"]); assert_eq!(settings.tmpdir.unwrap(), "X"); diff --git a/tests/positionals.rs b/tests/positionals.rs index 63ae3f4..2decccf 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -1,4 +1,4 @@ -use uutils_args::{Arguments, Options}; +use uutils_args::{Arguments, Initial, Options}; #[test] fn one_positional() { @@ -8,13 +8,18 @@ fn one_positional() { File1(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::File1)] file1: String, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::File1(f): Arg) { + self.file1 = f; + } + } + let settings = Settings::parse(["test", "foo"]); assert_eq!(settings.file1, "foo"); @@ -23,7 +28,7 @@ fn one_positional() { #[test] fn two_positionals() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[positional(1)] Foo(String), @@ -31,15 +36,22 @@ fn two_positionals() { Bar(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Foo)] foo: String, - #[set(Arg::Bar)] bar: String, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Foo(x) => self.foo = x, + Arg::Bar(x) => self.bar = x, + } + } + } + let settings = Settings::parse(["test", "a", "b"]); assert_eq!(settings.foo, "a"); assert_eq!(settings.bar, "b"); @@ -49,19 +61,24 @@ fn two_positionals() { #[test] fn optional_positional() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[positional(0..=1)] Foo(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[map(Arg::Foo(s) => Some(s))] foo: Option, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo(x): Arg) { + self.foo = Some(x); + } + } + let settings = Settings::parse(["test"]); assert_eq!(settings.foo, None); let settings = Settings::parse(["test", "bar"]); @@ -76,13 +93,18 @@ fn collect_positional() { Foo(String), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[collect(set(Arg::Foo))] foo: Vec, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo(x): Arg) { + self.foo.push(x); + } + } + let settings = Settings::parse(["test", "a", "b", "c"]); assert_eq!(settings.foo, vec!["a", "b", "c"]); let settings = Settings::parse(["test"]); @@ -91,19 +113,24 @@ fn collect_positional() { #[test] fn last1() { - #[derive(Arguments, Clone)] + #[derive(Arguments)] enum Arg { #[positional(last, ..)] Foo(Vec), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Foo)] foo: Vec, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, Arg::Foo(x): Arg) { + self.foo = x; + } + } + let settings = Settings::parse(["test", "a", "-b", "c"]); assert_eq!(settings.foo, vec!["a", "-b", "c"]); } @@ -119,13 +146,21 @@ fn last2() { Foo(Vec), } - #[derive(Default, Options)] - #[arg_type(Arg)] + #[derive(Initial)] struct Settings { - #[set(Arg::Foo)] foo: Vec, } + impl Options for Settings { + type Arg = Arg; + fn apply(&mut self, arg: Arg) { + match arg { + Arg::A => {} + Arg::Foo(x) => self.foo = x, + } + } + } + let settings = Settings::parse(["test", "-a"]); assert_eq!(settings.foo, Vec::::new());