remove Options derive in favor of manual implementation

This commit is contained in:
Terts Diepraam
2023-01-19 16:57:11 +01:00
parent dd5c3efc34
commit 065eb17b19
17 changed files with 659 additions and 646 deletions
+1 -1
View File
@@ -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]
-70
View File
@@ -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<syn::Path>),
Map(Vec<syn::Arm>),
}
fn parse_paths(attr: &Attribute) -> Vec<syn::Path> {
attr.parse_args_with(Punctuated::<syn::Path, Token![|]>::parse_terminated)
.into_iter()
.flatten()
.collect()
}
pub(crate) fn parse_action_attr(attr: &Attribute) -> Option<ActionAttr> {
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::<syn::Arm, Nothing>::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<Self> {
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::<syn::Arm, Nothing>::parse_terminated)?;
Ok(ActionType::Map(arms.into_iter().collect()))
} else {
let pat = content.call(Punctuated::<syn::Path, Token![|]>::parse_terminated)?;
let pat = pat.into_iter().collect();
match &action[..] {
"set" => Ok(ActionType::Set(pat)),
_ => panic!("Unexpected action type in collect {}", action),
}
}
}
}
+1 -61
View File
@@ -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<TokenStream> {
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 }
)
}
}
+5 -42
View File
@@ -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<Self, uutils_args::Error> {
Ok(Self {
#(#defaults),*
})
}
fn apply_args<I>(&mut self, args: I) -> Result<(), uutils_args::Error>
where
I: IntoIterator + 'static,
I::Item: Into<std::ffi::OsString>,
{
use uutils_args::{lexopt, FromValue, Argument};
let mut iter = <Self as Options>::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)*
}
}
}
<Self as Options>::Arg::check_missing(iter.positional_idx)?;
Ok(())
}
}
);
+16 -11
View File
@@ -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 {
+60 -21
View File
@@ -15,7 +15,17 @@ pub enum Argument<T: Arguments> {
Custom(T),
}
pub trait Arguments: Sized + Clone {
fn exit_if_err<T>(res: Result<T, Error>, 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<I>(args: I) -> ArgumentIter<Self>
@@ -36,6 +46,24 @@ pub trait Arguments: Sized + Clone {
fn help(bin_name: &str) -> String;
fn version() -> String;
fn check<I>(args: I)
where
I: IntoIterator + 'static,
I::Item: Into<OsString>,
{
exit_if_err(Self::try_check(args), Self::EXIT_CODE)
}
fn try_check<I>(args: I) -> Result<(), Error>
where
I: IntoIterator + 'static,
I::Item: Into<OsString>,
{
let mut iter = Self::parse(args);
while iter.next_arg()?.is_some() {}
Ok(())
}
}
pub struct ArgumentIter<T: Arguments> {
@@ -57,34 +85,48 @@ impl<T: Arguments> ArgumentIter<T> {
}
}
pub fn next_arg(&mut self) -> Result<Option<Argument<T>>, Error> {
T::next_arg(&mut self.parser, &mut self.positional_idx)
pub fn next_arg(&mut self) -> Result<Option<T>, 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<Self, Error>;
}
pub trait Options: Sized + Initial {
type Arg: Arguments;
fn apply(&mut self, arg: Self::Arg);
fn parse<I>(args: I) -> Self
where
I: IntoIterator + 'static,
I::Item: Into<OsString>,
{
match Self::try_parse(args) {
Ok(v) => v,
Err(err) => {
eprintln!("{err}");
std::process::exit(<Self as Options>::Arg::EXIT_CODE);
}
}
exit_if_err(Self::try_parse(args), Self::Arg::EXIT_CODE)
}
fn try_parse<I>(args: I) -> Result<Self, Error>
@@ -93,16 +135,13 @@ pub trait Options: Sized + Default {
I::Item: Into<OsString>,
{
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<Self, Error>;
fn apply_args<I>(&mut self, args: I) -> Result<(), Error>
where
I: IntoIterator + 'static,
I::Item: Into<OsString>;
}
pub trait FromValue: Sized {
+6 -10
View File
@@ -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());
}
+19 -21
View File
@@ -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<PathBuf>,
}
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);
+15 -13
View File
@@ -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<usize>,
#[map(Arg::File(f) => Some(f))]
file: Option<PathBuf>,
}
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));
+17 -10
View File
@@ -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<String>),
}
#[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<String>,
}
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 {
+30 -23
View File
@@ -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<PathBuf>,
}
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"]);
+87 -104
View File
@@ -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<PathBuf>,
#[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<String>,
//
// 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<u64>,
#[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!(
+17 -16
View File
@@ -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<PathBuf>,
#[map(Arg::Suffix(s) => Some(s))]
suffix: Option<String>,
#[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"]);
+19 -9
View File
@@ -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");
+191 -137
View File
File diff suppressed because it is too large Load Diff
+117 -74
View File
@@ -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<Color>),
}
#[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<String>,
}
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<u64>,
}
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<String>,
}
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");
+58 -23
View File
@@ -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<String>,
}
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<String>,
}
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<String>),
}
#[derive(Default, Options)]
#[arg_type(Arg)]
#[derive(Initial)]
struct Settings {
#[set(Arg::Foo)]
foo: Vec<String>,
}
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<String>),
}
#[derive(Default, Options)]
#[arg_type(Arg)]
#[derive(Initial)]
struct Settings {
#[set(Arg::Foo)]
foo: Vec<String>,
}
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::<String>::new());