You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
remove Options derive in favor of manual implementation
This commit is contained in:
+1
-1
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+117
-74
@@ -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
@@ -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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user