You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
basic help flag
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Attribute, Fields, FieldsUnnamed, Lit, Meta, Variant, Ident, punctuated::Punctuated, LitStr, Token};
|
||||
|
||||
use crate::{
|
||||
attributes::{parse_argument_attribute, ArgAttr},
|
||||
Arg,
|
||||
};
|
||||
|
||||
pub(crate) struct Argument {
|
||||
ident: Ident,
|
||||
name: String,
|
||||
arg_type: ArgType,
|
||||
help: String,
|
||||
}
|
||||
|
||||
pub(crate) enum TakesValue {
|
||||
Yes,
|
||||
Optional,
|
||||
No,
|
||||
}
|
||||
|
||||
pub(crate) enum ArgType {
|
||||
Option {
|
||||
short_flags: Vec<char>,
|
||||
long_flags: Vec<String>,
|
||||
takes_value: TakesValue,
|
||||
},
|
||||
Positional {
|
||||
num_args: RangeInclusive<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn parse_help_flags(attrs: &[Attribute]) -> (Vec<char>, Vec<String>) {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("help") {
|
||||
let mut short = Vec::new();
|
||||
let mut long = Vec::new();
|
||||
for s in attr.parse_args_with(Punctuated::<LitStr, Token![,]>::parse_terminated).unwrap() {
|
||||
let s = s.value().to_string();
|
||||
if let Some(s) = s.strip_prefix("--") {
|
||||
long.push(s.to_string());
|
||||
} else if let Some(s) = s.strip_prefix("-") {
|
||||
assert_eq!(s.len(), 1);
|
||||
short.push(s.chars().next().unwrap())
|
||||
}
|
||||
return (short, long);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (vec!['h'], vec!["help".into()])
|
||||
}
|
||||
|
||||
pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
let ident = v.ident;
|
||||
let name = ident.to_string();
|
||||
let attribute = get_arg_attribute(&v.attrs)?;
|
||||
let help = collect_help(&v.attrs);
|
||||
|
||||
let field = match v.fields {
|
||||
Fields::Unit => None,
|
||||
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
|
||||
let v: Vec<_> = unnamed.iter().collect();
|
||||
assert!(
|
||||
v.len() == 1,
|
||||
"Variants in an Arguments enum can have at most 1 field."
|
||||
);
|
||||
Some(v[0].ty.clone())
|
||||
}
|
||||
Fields::Named(_) => {
|
||||
panic!("Named fields are not supported in Arguments");
|
||||
}
|
||||
};
|
||||
|
||||
let arg_type = match attribute {
|
||||
ArgAttr::Option(opt) => {
|
||||
let (short_flags, long_flags) = flag_names(opt.flags, &name);
|
||||
let takes_value = match field {
|
||||
None => TakesValue::No,
|
||||
Some(x) if type_is_option(&x) => TakesValue::Optional,
|
||||
Some(_) => TakesValue::Yes,
|
||||
};
|
||||
ArgType::Option {
|
||||
short_flags,
|
||||
long_flags,
|
||||
takes_value,
|
||||
}
|
||||
}
|
||||
ArgAttr::Positional(pos) => {
|
||||
assert!(field.is_some(), "Positional arguments must have a field");
|
||||
ArgType::Positional {
|
||||
num_args: pos.num_args,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some(Argument {
|
||||
ident,
|
||||
name,
|
||||
arg_type,
|
||||
help,
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_help(attrs: &[Attribute]) -> String {
|
||||
let mut help = Vec::new();
|
||||
for attr in attrs {
|
||||
let Ok(meta) = attr.parse_meta() else { continue; };
|
||||
let Meta::NameValue(name_value) = meta else { continue; };
|
||||
if !name_value.path.is_ident("doc") {
|
||||
continue;
|
||||
}
|
||||
let Lit::Str(litstr) = name_value.lit else { continue; };
|
||||
help.push(litstr.value().trim().to_string())
|
||||
}
|
||||
help.join(" ")
|
||||
}
|
||||
|
||||
fn get_arg_attribute(attrs: &[Attribute]) -> Option<ArgAttr> {
|
||||
let attrs: Vec<_> = attrs
|
||||
.iter()
|
||||
.filter(|a| a.path.is_ident("option") || a.path.is_ident("positional"))
|
||||
.collect();
|
||||
match attrs[..] {
|
||||
[] => None,
|
||||
[attr] => Some(parse_argument_attribute(attr)),
|
||||
_ => panic!("Can only specify one #[option] or #[positional] per argument variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_is_option(syn_type: &syn::Type) -> bool {
|
||||
if let syn::Type::Path(field_type_path) = syn_type {
|
||||
field_type_path
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| s.ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
== vec!["Option"]
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn flag_names(flags: Vec<Arg>, field_name: &str) -> (Vec<char>, Vec<String>) {
|
||||
let field_name = field_name.to_lowercase();
|
||||
if flags.is_empty() {
|
||||
let first_char = field_name.chars().next().unwrap();
|
||||
if field_name.len() > 1 {
|
||||
(vec![first_char], vec![field_name.to_string()])
|
||||
} else {
|
||||
(vec![first_char], vec![])
|
||||
}
|
||||
} else {
|
||||
let mut shorts = Vec::new();
|
||||
let mut longs = Vec::new();
|
||||
for flag in flags {
|
||||
match flag {
|
||||
Arg::Short(x) => shorts.push(x),
|
||||
Arg::Long(x) => longs.push(x),
|
||||
};
|
||||
}
|
||||
(shorts, longs)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn short_handling(args: &[Argument]) -> TokenStream {
|
||||
let mut match_arms = Vec::new();
|
||||
|
||||
for arg in args {
|
||||
let ArgType::Option { ref short_flags, .. } = arg.arg_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if short_flags.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let expr = argument_expression(arg);
|
||||
match_arms.push(quote!(#(#short_flags)|* => { #expr }))
|
||||
}
|
||||
|
||||
quote!(
|
||||
match short {
|
||||
#(#match_arms)*
|
||||
_ => return Err(arg.unexpected().into()),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn long_handling(args: &[Argument]) -> TokenStream {
|
||||
let mut match_arms = Vec::new();
|
||||
let mut options = Vec::new();
|
||||
|
||||
for arg in args {
|
||||
let ArgType::Option { ref long_flags, .. } = arg.arg_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if long_flags.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let expr = argument_expression(arg);
|
||||
match_arms.push(quote!(#(#long_flags)|* => { #expr }));
|
||||
options.extend(long_flags);
|
||||
}
|
||||
|
||||
if options.is_empty() {
|
||||
return quote!(return Err(arg.unexpected().into()));
|
||||
}
|
||||
|
||||
let num_opts = options.len();
|
||||
|
||||
quote!(
|
||||
let long_options: [&str; #num_opts] = [#(#options),*];
|
||||
let mut candidates = Vec::new();
|
||||
let mut exact_match = None;
|
||||
for opt in long_options {
|
||||
if opt == long {
|
||||
exact_match = Some(opt);
|
||||
break;
|
||||
} else if opt.starts_with(long) {
|
||||
candidates.push(opt);
|
||||
}
|
||||
}
|
||||
|
||||
let opt = match (exact_match, &candidates[..]) {
|
||||
(Some(opt), _) => opt,
|
||||
(None, [opt]) => opt,
|
||||
(None, []) => return Err(arg.unexpected().into()),
|
||||
(None, opts) => return Err(Error::AmbiguousOption {
|
||||
option: long.to_string(),
|
||||
candidates: candidates.iter().map(|s| s.to_string()).collect(),
|
||||
})
|
||||
};
|
||||
|
||||
match opt {
|
||||
#(#match_arms)*
|
||||
_ => unreachable!("Should be caught by (None, []) case above.")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStream) {
|
||||
let mut match_arms = Vec::new();
|
||||
// The largest index of the previous argument, so the the argument after this should
|
||||
// belong to the next argument.
|
||||
let mut last_index = 0;
|
||||
|
||||
// The minimum number of arguments needed to not return a missing argument error.
|
||||
let mut minimum_needed = 0;
|
||||
let mut missing_argument_checks = vec![];
|
||||
|
||||
for arg @ Argument { name, arg_type, .. } in args {
|
||||
let ArgType::Positional{ ref num_args } = arg_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if *num_args.start() > 0 {
|
||||
minimum_needed = last_index + num_args.start();
|
||||
missing_argument_checks.push(quote!(if positional_idx < #minimum_needed {
|
||||
missing.push(#name);
|
||||
}));
|
||||
}
|
||||
|
||||
last_index += num_args.end();
|
||||
|
||||
let expr = argument_expression(arg);
|
||||
match_arms.push(quote!(0..=#last_index => { #expr }));
|
||||
}
|
||||
|
||||
let value_handling = quote!(
|
||||
*positional_idx += 1;
|
||||
match positional_idx {
|
||||
#(#match_arms)*
|
||||
_ => return Err(lexopt::Arg::Value(value).unexpected().into()),
|
||||
}
|
||||
);
|
||||
|
||||
let missing_argument_checks = quote!(
|
||||
// We have the minimum number of required arguments overall.
|
||||
// So we don't need to check the others.
|
||||
if positional_idx >= #minimum_needed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut missing: Vec<&str> = vec![];
|
||||
#(#missing_argument_checks)*
|
||||
if !missing.is_empty() {
|
||||
Err(uutils_args::Error::MissingPositionalArguments(
|
||||
missing.iter().map(ToString::to_string).collect::<Vec<String>>()
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
|
||||
(value_handling, missing_argument_checks)
|
||||
}
|
||||
|
||||
fn argument_expression(arg: &Argument) -> TokenStream {
|
||||
let Argument { ident, arg_type, .. } = arg;
|
||||
match arg_type {
|
||||
ArgType::Option { takes_value, .. } => match takes_value {
|
||||
TakesValue::No => quote!(Self::#ident),
|
||||
TakesValue::Optional => quote!(
|
||||
Self::#ident(match parser.optional_value() {
|
||||
Some(x) => Some(FromValue::from_value(x)?),
|
||||
None => None,
|
||||
})
|
||||
),
|
||||
TakesValue::Yes => quote!(
|
||||
Self::#ident(FromValue::from_value(parser.value()?)?)
|
||||
),
|
||||
}
|
||||
ArgType::Positional { .. } => quote!(
|
||||
Self::#ident(FromValue::from_value(value)?)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn help_handling(args: &[Argument], short_help_flags: &[char], long_help_flags: &[String]) -> String {
|
||||
let mut options = Vec::new();
|
||||
|
||||
let width = 16;
|
||||
let indent = 2;
|
||||
|
||||
for Argument { arg_type, help, ..} in args {
|
||||
match arg_type {
|
||||
ArgType::Option { short_flags, long_flags, ..} => {
|
||||
let flags = format_flags(short_flags, long_flags);
|
||||
options.push(format_help_line(indent, width, &flags, help));
|
||||
}
|
||||
ArgType::Positional { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
let help_flags = format_flags(short_help_flags, long_help_flags);
|
||||
if !help_flags.is_empty() {
|
||||
options.push(format_help_line(indent, width, &help_flags, "Display this help message"));
|
||||
}
|
||||
|
||||
format!(
|
||||
"Options:\n{}",
|
||||
options.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_flags(short: &[char], long: &[String]) -> String {
|
||||
short.iter().map(|s| format!("-{s}"))
|
||||
.chain(
|
||||
long.iter().map(|l| format!("--{l}"))
|
||||
).collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn format_help_line(indent: usize, width: usize, flags: &str, help: &str) -> String {
|
||||
let indent = " ".repeat(indent);
|
||||
if help == "" {
|
||||
format!("{indent}{flags}")
|
||||
} else if flags.len() >= width {
|
||||
let help_indent = " ".repeat(width);
|
||||
format!("{indent}{flags}\n{indent}{help_indent}{help}")
|
||||
} else {
|
||||
let help_indent = " ".repeat(width-flags.len());
|
||||
format!("{indent}{flags}{help_indent}{help}")
|
||||
}
|
||||
}
|
||||
+15
-45
@@ -3,20 +3,24 @@ use std::ops::RangeInclusive;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
Attribute, ExprLit, ExprRange, Ident, Lit, LitInt, LitStr, RangeLimits, Token,
|
||||
Attribute, Expr, ExprLit, ExprRange, Ident, Lit, LitInt, LitStr, RangeLimits, Token,
|
||||
};
|
||||
|
||||
use crate::{Arg, Expr};
|
||||
use crate::Arg;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FlagAttr {
|
||||
pub(crate) flags: Vec<Arg>,
|
||||
pub(crate) value: Option<syn::Expr>,
|
||||
pub(crate) enum ArgAttr {
|
||||
Option(OptionAttr),
|
||||
Positional(PositionalAttr),
|
||||
}
|
||||
|
||||
enum FlagAttrArg {
|
||||
Arg(Arg),
|
||||
Value(Expr),
|
||||
pub(crate) fn parse_argument_attribute(attr: &Attribute) -> ArgAttr {
|
||||
if attr.path.is_ident("option") {
|
||||
ArgAttr::Option(parse_option_attr(attr))
|
||||
} else if attr.path.is_ident("positional") {
|
||||
ArgAttr::Positional(parse_positional_attr(attr))
|
||||
} else {
|
||||
panic!("Internal error: invalid argument attribute");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -57,41 +61,7 @@ enum PositionalAttrArg {
|
||||
NumArgs(RangeInclusive<usize>),
|
||||
}
|
||||
|
||||
pub(crate) fn parse_flag_attr(attr: Attribute) -> FlagAttr {
|
||||
let mut flag_attr = FlagAttr::default();
|
||||
let Ok(parsed_args) = attr
|
||||
.parse_args_with(Punctuated::<FlagAttrArg, Token![,]>::parse_terminated)
|
||||
else {
|
||||
return flag_attr;
|
||||
};
|
||||
for arg in parsed_args {
|
||||
match arg {
|
||||
FlagAttrArg::Arg(a) => flag_attr.flags.push(a),
|
||||
FlagAttrArg::Value(e) => flag_attr.value = Some(e),
|
||||
};
|
||||
}
|
||||
flag_attr
|
||||
}
|
||||
|
||||
impl Parse for FlagAttrArg {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(LitStr) {
|
||||
return parse_flag(input).map(Self::Arg);
|
||||
}
|
||||
|
||||
if input.peek(Ident) {
|
||||
let name = input.parse::<Ident>()?.to_string();
|
||||
input.parse::<Token![=]>()?;
|
||||
match name.as_str() {
|
||||
"value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
_ => panic!("Unrecognized argument {} for flag attribute", name),
|
||||
};
|
||||
}
|
||||
panic!("Arguments to flag attribute must be string literals");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_option_attr(attr: Attribute) -> OptionAttr {
|
||||
pub(crate) fn parse_option_attr(attr: &Attribute) -> OptionAttr {
|
||||
let mut option_attr = OptionAttr::default();
|
||||
let Ok(parsed_args) = attr
|
||||
.parse_args_with(Punctuated::<OptionAttrArg, Token![,]>::parse_terminated)
|
||||
@@ -177,7 +147,7 @@ fn parse_flag(input: ParseStream) -> syn::Result<Arg> {
|
||||
panic!("Arguments to flag must start with \"-\" or \"--\"");
|
||||
}
|
||||
|
||||
pub(crate) fn parse_positional_attr(attr: Attribute) -> PositionalAttr {
|
||||
pub(crate) fn parse_positional_attr(attr: &Attribute) -> PositionalAttr {
|
||||
let mut positional_attr = PositionalAttr::default();
|
||||
let Ok(parsed_args) = attr
|
||||
.parse_args_with(Punctuated::<PositionalAttrArg, Token![,]>::parse_terminated)
|
||||
|
||||
+42
-215
@@ -1,19 +1,21 @@
|
||||
mod action;
|
||||
mod argument;
|
||||
mod attributes;
|
||||
|
||||
use action::{parse_action_attr, ActionAttr, ActionType};
|
||||
use attributes::{
|
||||
parse_flag_attr, parse_option_attr, parse_positional_attr, parse_value_attr, FlagAttr,
|
||||
OptionAttr, PositionalAttr, ValueAttr,
|
||||
use argument::{
|
||||
help_handling, long_handling, parse_argument, parse_help_flags, positional_handling,
|
||||
short_handling,
|
||||
};
|
||||
use attributes::{parse_value_attr, ValueAttr};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse::Parse,
|
||||
parse_macro_input, Attribute,
|
||||
parse_macro_input,
|
||||
Data::{Enum, Struct},
|
||||
DeriveInput, Expr, Fields,
|
||||
DeriveInput, Fields,
|
||||
};
|
||||
|
||||
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
|
||||
@@ -22,12 +24,6 @@ enum Arg {
|
||||
Long(String),
|
||||
}
|
||||
|
||||
enum DeriveAttribute {
|
||||
Flag(FlagAttr),
|
||||
Option(OptionAttr),
|
||||
Positional(PositionalAttr),
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Options, attributes(arg_type, map, set, set_true, set_false, collect))]
|
||||
pub fn options(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
@@ -112,11 +108,18 @@ pub fn options(input: TokenStream) -> TokenStream {
|
||||
I: IntoIterator + 'static,
|
||||
I::Item: Into<std::ffi::OsString>,
|
||||
{
|
||||
use uutils_args::lexopt;
|
||||
use uutils_args::FromValue;
|
||||
use uutils_args::{lexopt, FromValue, Argument};
|
||||
let mut iter = #arg_type::parse(args);
|
||||
while let Some(arg) = iter.next_arg()? {
|
||||
#(#stmts)*
|
||||
match arg {
|
||||
Argument::Help => {
|
||||
println!("{}", #arg_type::help());
|
||||
std::process::exit(0);
|
||||
},
|
||||
Argument::Custom(arg) => {
|
||||
#(#stmts)*
|
||||
}
|
||||
}
|
||||
}
|
||||
#arg_type::check_missing(iter.positional_idx)?;
|
||||
Ok(())
|
||||
@@ -138,184 +141,42 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
panic!("Input should be an enum!");
|
||||
};
|
||||
|
||||
let mut short_match_arms = Vec::new();
|
||||
let mut long_match_arms = Vec::new();
|
||||
let mut long_options = Vec::new();
|
||||
let (short_help_flags, long_help_flags) = parse_help_flags(&input.attrs);
|
||||
let arguments: Vec<_> = data.variants.into_iter().flat_map(parse_argument).collect();
|
||||
|
||||
let mut positional_indices = Vec::new();
|
||||
|
||||
for variant in data.variants {
|
||||
let variant_ident = variant.ident;
|
||||
let variant_name = variant_ident.to_string();
|
||||
let mut short_flags = Vec::new();
|
||||
let mut long_flags = Vec::new();
|
||||
let parse_expr = match variant.fields {
|
||||
Fields::Unit => {
|
||||
for attr in variant.attrs {
|
||||
let Some(attr) = parse_attr(attr) else { continue; };
|
||||
let DeriveAttribute::Flag(f) = attr else {
|
||||
panic!("unsupported attribute");
|
||||
};
|
||||
let (mut shorts, mut longs) = flag_names(f.flags, &variant_name);
|
||||
short_flags.append(&mut shorts);
|
||||
long_flags.append(&mut longs);
|
||||
}
|
||||
quote!(Self::#variant_ident)
|
||||
}
|
||||
Fields::Unnamed(f) => {
|
||||
let v: Vec<_> = f.unnamed.iter().collect();
|
||||
assert_eq!(v.len(), 1, "Options can have only one field");
|
||||
let field_type = v[0].ty.clone();
|
||||
let field_is_option = {
|
||||
if let syn::Type::Path(field_type_path) = field_type {
|
||||
field_type_path
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| s.ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
== vec!["Option"]
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
for attr in variant.attrs {
|
||||
let Some(attr) = parse_attr(attr) else { continue; };
|
||||
match attr {
|
||||
DeriveAttribute::Option(f) => {
|
||||
let (mut shorts, mut longs) = flag_names(f.flags, &variant_name);
|
||||
short_flags.append(&mut shorts);
|
||||
long_flags.append(&mut longs);
|
||||
}
|
||||
DeriveAttribute::Positional(p) => {
|
||||
positional_indices.push((
|
||||
p.num_args,
|
||||
variant_name.to_uppercase(),
|
||||
quote!(Self::#variant_ident (FromValue::from_value(value)?)),
|
||||
));
|
||||
}
|
||||
_ => panic!("unsupported attribute"),
|
||||
};
|
||||
}
|
||||
if field_is_option {
|
||||
quote!(Self::#variant_ident (match parser.optional_value() {
|
||||
Some(v) => Some(FromValue::from_value(v)?),
|
||||
None => None,
|
||||
}))
|
||||
} else {
|
||||
quote!(Self::#variant_ident (FromValue::from_value(parser.value()?)?))
|
||||
}
|
||||
}
|
||||
_ => panic!("unimplemented"),
|
||||
};
|
||||
|
||||
if !short_flags.is_empty() {
|
||||
short_match_arms.push(quote!(#(#short_flags)|* => { #parse_expr }));
|
||||
};
|
||||
|
||||
if !long_flags.is_empty() {
|
||||
long_match_arms.push(quote!(#(#long_flags)|* => { #parse_expr }));
|
||||
long_options.append(&mut long_flags);
|
||||
};
|
||||
}
|
||||
|
||||
let long_handling = if long_options.is_empty() {
|
||||
quote!(return Err(arg.unexpected().into()))
|
||||
} else {
|
||||
let long_opt_len = long_options.len();
|
||||
quote!(
|
||||
let long_options: [&str; #long_opt_len] = [#(#long_options),*];
|
||||
let mut candidates = Vec::new();
|
||||
let mut exact_match = None;
|
||||
for opt in long_options {
|
||||
if opt == long {
|
||||
exact_match = Some(opt);
|
||||
break;
|
||||
} else if opt.starts_with(long) {
|
||||
candidates.push(opt);
|
||||
}
|
||||
}
|
||||
|
||||
let opt = match (exact_match, &candidates[..]) {
|
||||
(Some(opt), _) => opt,
|
||||
(None, [opt]) => opt,
|
||||
(None, []) => return Err(arg.unexpected().into()),
|
||||
(None, opts) => return Err(Error::AmbiguousOption {
|
||||
option: long.to_string(),
|
||||
candidates: candidates.iter().map(|s| s.to_string()).collect(),
|
||||
})
|
||||
};
|
||||
|
||||
match opt {
|
||||
#(#long_match_arms)*
|
||||
_ => unreachable!("Should be caught by (None, []) case above.")
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let mut positional_match_arms = Vec::new();
|
||||
// The largest index of the previous argument, so the the argument after this should
|
||||
// belong to the next argument.
|
||||
let mut last_index = 0;
|
||||
|
||||
// The minimum number of arguments needed to not return a missing argument error.
|
||||
let mut minimum_needed = 0;
|
||||
let mut missing_argument_checks = vec![];
|
||||
for (range, arg_name, expr) in positional_indices {
|
||||
if *range.start() > 0 {
|
||||
minimum_needed = last_index + range.start();
|
||||
missing_argument_checks.push(quote!(if positional_idx < #minimum_needed {
|
||||
missing.push(#arg_name);
|
||||
}));
|
||||
}
|
||||
last_index += range.end();
|
||||
positional_match_arms.push(quote!(0..=#last_index => { #expr }));
|
||||
}
|
||||
let short = short_handling(&arguments);
|
||||
let long = long_handling(&arguments);
|
||||
let (positional, missing_argument_checks) = positional_handling(&arguments);
|
||||
let help = help_handling(&arguments, &short_help_flags, &long_help_flags);
|
||||
|
||||
let expanded = quote!(
|
||||
impl #impl_generics Arguments for #name #ty_generics #where_clause {
|
||||
#[allow(unreachable_code)]
|
||||
fn next_arg(parser: &mut uutils_args::lexopt::Parser, positional_idx: &mut usize) -> Result<Option<Self>, uutils_args::Error> {
|
||||
use uutils_args::FromValue;
|
||||
use uutils_args::lexopt::Arg;
|
||||
use uutils_args::Error;
|
||||
fn next_arg(
|
||||
parser: &mut uutils_args::lexopt::Parser, positional_idx: &mut usize
|
||||
) -> Result<Option<uutils_args::Argument<Self>>, uutils_args::Error> {
|
||||
use uutils_args::{FromValue, lexopt, Error, Argument};
|
||||
|
||||
let Some(arg) = parser.next()? else { return Ok(None); };
|
||||
|
||||
if let lexopt::Arg::Short('h') | lexopt::Arg::Long("help") = arg {
|
||||
return Ok(Some(Argument::Help));
|
||||
}
|
||||
|
||||
let parsed = match arg {
|
||||
Arg::Short(short) => {
|
||||
match short {
|
||||
#(#short_match_arms)*
|
||||
_ => return Err(arg.unexpected().into())
|
||||
}
|
||||
}
|
||||
Arg::Long(long) => {
|
||||
#long_handling
|
||||
}
|
||||
Arg::Value(value) => {
|
||||
*positional_idx += 1;
|
||||
match positional_idx {
|
||||
#(#positional_match_arms)*
|
||||
_ => return Err(Arg::Value(value).unexpected().into()),
|
||||
}
|
||||
}
|
||||
lexopt::Arg::Short(short) => { #short }
|
||||
lexopt::Arg::Long(long) => { #long }
|
||||
lexopt::Arg::Value(value) => { #positional }
|
||||
};
|
||||
Ok(Some(parsed))
|
||||
Ok(Some(Argument::Custom(parsed)))
|
||||
}
|
||||
|
||||
fn check_missing(positional_idx: usize) -> Result<(), uutils_args::Error> {
|
||||
// We have the minimum number of required arguments overall.
|
||||
// So we don't need to check the others.
|
||||
if positional_idx >= #minimum_needed {
|
||||
return Ok(());
|
||||
}
|
||||
let mut missing: Vec<&str> = vec![];
|
||||
#(#missing_argument_checks)*
|
||||
if !missing.is_empty() {
|
||||
Err(uutils_args::Error::MissingPositionalArguments(
|
||||
missing.iter().map(ToString::to_string).collect::<Vec<String>>()
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
#missing_argument_checks
|
||||
}
|
||||
|
||||
fn help() -> &'static str {
|
||||
#help
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -382,37 +243,3 @@ pub fn from_value(input: TokenStream) -> TokenStream {
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn flag_names(flags: Vec<Arg>, field_name: &str) -> (Vec<char>, Vec<String>) {
|
||||
let field_name = field_name.to_lowercase();
|
||||
if flags.is_empty() {
|
||||
let first_char = field_name.chars().next().unwrap();
|
||||
if field_name.len() > 1 {
|
||||
(vec![first_char], vec![field_name.to_string()])
|
||||
} else {
|
||||
(vec![first_char], vec![])
|
||||
}
|
||||
} else {
|
||||
let mut shorts = Vec::new();
|
||||
let mut longs = Vec::new();
|
||||
for flag in flags {
|
||||
match flag {
|
||||
Arg::Short(x) => shorts.push(x),
|
||||
Arg::Long(x) => longs.push(x),
|
||||
};
|
||||
}
|
||||
(shorts, longs)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attr(attr: Attribute) -> Option<DeriveAttribute> {
|
||||
if attr.path.is_ident("flag") {
|
||||
Some(DeriveAttribute::Flag(parse_flag_attr(attr)))
|
||||
} else if attr.path.is_ident("option") {
|
||||
Some(DeriveAttribute::Option(parse_option_attr(attr)))
|
||||
} else if attr.path.is_ident("positional") {
|
||||
Some(DeriveAttribute::Positional(parse_positional_attr(attr)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use uutils_args::{Arguments, Options};
|
||||
|
||||
#[derive(Clone, Arguments)]
|
||||
enum Arg {
|
||||
/// The name to greet
|
||||
#[option]
|
||||
Name(String),
|
||||
|
||||
/// The number of times to greet
|
||||
#[option]
|
||||
Count(u8),
|
||||
}
|
||||
|
||||
#[derive(Default, Options)]
|
||||
#[arg_type(Arg)]
|
||||
struct Settings {
|
||||
#[set(Arg::Name)]
|
||||
name: String,
|
||||
#[set(Arg::Count)]
|
||||
count: u8,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let settings = Settings::parse(std::env::args_os()).unwrap();
|
||||
for _ in 0..settings.count {
|
||||
println!("Hello, {}!", settings.name);
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -45,6 +45,12 @@ impl From<lexopt::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Argument<T: Arguments> {
|
||||
Help,
|
||||
Custom(T),
|
||||
}
|
||||
|
||||
pub trait Arguments: Sized + Clone {
|
||||
fn parse<I>(args: I) -> ArgumentIter<Self>
|
||||
where
|
||||
@@ -57,9 +63,11 @@ pub trait Arguments: Sized + Clone {
|
||||
fn next_arg(
|
||||
parser: &mut lexopt::Parser,
|
||||
positional_idx: &mut usize,
|
||||
) -> Result<Option<Self>, Error>;
|
||||
) -> Result<Option<Argument<Self>>, Error>;
|
||||
|
||||
fn check_missing(positional_idx: usize) -> Result<(), Error>;
|
||||
|
||||
fn help() -> &'static str;
|
||||
}
|
||||
|
||||
pub struct ArgumentIter<T: Arguments> {
|
||||
@@ -75,13 +83,13 @@ impl<T: Arguments> ArgumentIter<T> {
|
||||
I::Item: Into<OsString>,
|
||||
{
|
||||
Self {
|
||||
parser: lexopt::Parser::from_args(args),
|
||||
parser: lexopt::Parser::from_iter(args),
|
||||
positional_idx: 0,
|
||||
t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_arg(&mut self) -> Result<Option<T>, Error> {
|
||||
pub fn next_arg(&mut self) -> Result<Option<Argument<T>>, Error> {
|
||||
T::next_arg(&mut self.parser, &mut self.positional_idx)
|
||||
}
|
||||
}
|
||||
|
||||
+104
-65
@@ -1,8 +1,8 @@
|
||||
use uutils_args::{ArgumentIter, Arguments, Options};
|
||||
use uutils_args::{Argument, ArgumentIter, Arguments, Options};
|
||||
|
||||
fn to_vec<T: Arguments>(mut args: ArgumentIter<T>) -> Vec<T> {
|
||||
let mut v = Vec::new();
|
||||
while let Some(arg) = args.next_arg().unwrap() {
|
||||
while let Some(Argument::Custom(arg)) = args.next_arg().unwrap() {
|
||||
v.push(arg);
|
||||
}
|
||||
v
|
||||
@@ -12,7 +12,7 @@ fn to_vec<T: Arguments>(mut args: ArgumentIter<T>) -> Vec<T> {
|
||||
fn one_flag() {
|
||||
#[derive(Arguments, Clone, Debug, PartialEq, Eq)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
Foo,
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ fn one_flag() {
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
let iter = Arg::parse(["-f", "--foo"]);
|
||||
let iter = Arg::parse(["test", "-f", "--foo"]);
|
||||
assert_eq!(to_vec(iter), vec![Arg::Foo, Arg::Foo]);
|
||||
|
||||
let settings = Settings::parse(["-f"]).unwrap();
|
||||
let settings = Settings::parse(["test", "-f"]).unwrap();
|
||||
assert!(settings.foo);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ fn one_flag() {
|
||||
fn two_flags() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
A,
|
||||
#[flag]
|
||||
#[option]
|
||||
B,
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ fn two_flags() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-a"]).unwrap(),
|
||||
Settings::parse(["test", "-a"]).unwrap(),
|
||||
Settings { a: true, b: false }
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -58,11 +58,11 @@ fn two_flags() {
|
||||
Settings { a: false, b: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["-b"]).unwrap(),
|
||||
Settings::parse(["test", "-b"]).unwrap(),
|
||||
Settings { a: false, b: true }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["-a", "-b"]).unwrap(),
|
||||
Settings::parse(["test", "-a", "-b"]).unwrap(),
|
||||
Settings { a: true, b: true }
|
||||
);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ fn two_flags() {
|
||||
fn long_and_short_flag() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
Foo,
|
||||
}
|
||||
|
||||
@@ -86,15 +86,21 @@ fn long_and_short_flag() {
|
||||
Settings::parse::<&[&std::ffi::OsStr]>(&[]).unwrap(),
|
||||
Settings { foo: false },
|
||||
);
|
||||
assert_eq!(Settings::parse(["--foo"]).unwrap(), Settings { foo: true },);
|
||||
assert_eq!(Settings::parse(["-f"]).unwrap(), Settings { foo: true },);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "--foo"]).unwrap(),
|
||||
Settings { foo: true },
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-f"]).unwrap(),
|
||||
Settings { foo: true },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_alias() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("-b")]
|
||||
#[option("-b")]
|
||||
Foo,
|
||||
}
|
||||
|
||||
@@ -105,14 +111,17 @@ fn short_alias() {
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-b"]).unwrap(), Settings { foo: true },);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-b"]).unwrap(),
|
||||
Settings { foo: true },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_alias() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("--bar")]
|
||||
#[option("--bar")]
|
||||
Foo,
|
||||
}
|
||||
|
||||
@@ -123,16 +132,19 @@ fn long_alias() {
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["--bar"]).unwrap(), Settings { foo: true },);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "--bar"]).unwrap(),
|
||||
Settings { foo: true },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_and_long_alias() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("-b", "--bar")]
|
||||
#[option("-b", "--bar")]
|
||||
Foo,
|
||||
#[flag("-f", "--foo")]
|
||||
#[option("-f", "--foo")]
|
||||
Bar,
|
||||
}
|
||||
|
||||
@@ -155,21 +167,21 @@ fn short_and_long_alias() {
|
||||
bar: true,
|
||||
};
|
||||
|
||||
assert_eq!(Settings::parse(["--bar"]).unwrap(), foo_true);
|
||||
assert_eq!(Settings::parse(["-b"]).unwrap(), foo_true);
|
||||
assert_eq!(Settings::parse(["--foo"]).unwrap(), bar_true);
|
||||
assert_eq!(Settings::parse(["-f"]).unwrap(), bar_true);
|
||||
assert_eq!(Settings::parse(["test", "--bar"]).unwrap(), foo_true);
|
||||
assert_eq!(Settings::parse(["test", "-b"]).unwrap(), foo_true);
|
||||
assert_eq!(Settings::parse(["test", "--foo"]).unwrap(), bar_true);
|
||||
assert_eq!(Settings::parse(["test", "-f"]).unwrap(), bar_true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xyz_map_to_abc() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
X,
|
||||
#[flag]
|
||||
#[option]
|
||||
Y,
|
||||
#[flag]
|
||||
#[option]
|
||||
Z,
|
||||
}
|
||||
|
||||
@@ -185,7 +197,7 @@ fn xyz_map_to_abc() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-x"]).unwrap(),
|
||||
Settings::parse(["test", "-x"]).unwrap(),
|
||||
Settings {
|
||||
a: true,
|
||||
b: true,
|
||||
@@ -194,7 +206,7 @@ fn xyz_map_to_abc() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-y"]).unwrap(),
|
||||
Settings::parse(["test", "-y"]).unwrap(),
|
||||
Settings {
|
||||
a: false,
|
||||
b: true,
|
||||
@@ -203,7 +215,7 @@ fn xyz_map_to_abc() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-xy"]).unwrap(),
|
||||
Settings::parse(["test", "-xy"]).unwrap(),
|
||||
Settings {
|
||||
a: true,
|
||||
b: true,
|
||||
@@ -212,7 +224,7 @@ fn xyz_map_to_abc() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-z"]).unwrap(),
|
||||
Settings::parse(["test", "-z"]).unwrap(),
|
||||
Settings {
|
||||
a: true,
|
||||
b: true,
|
||||
@@ -225,9 +237,9 @@ fn xyz_map_to_abc() {
|
||||
fn non_rust_ident() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("--foo-bar")]
|
||||
#[option("--foo-bar")]
|
||||
FooBar,
|
||||
#[flag("--super")]
|
||||
#[option("--super")]
|
||||
Super,
|
||||
}
|
||||
|
||||
@@ -241,7 +253,7 @@ fn non_rust_ident() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--foo-bar", "--super"]).unwrap(),
|
||||
Settings::parse(["test", "--foo-bar", "--super"]).unwrap(),
|
||||
Settings { a: true, b: true }
|
||||
)
|
||||
}
|
||||
@@ -250,7 +262,7 @@ fn non_rust_ident() {
|
||||
fn number_flag() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("-1")]
|
||||
#[option("-1")]
|
||||
One,
|
||||
}
|
||||
#[derive(Default, Options, PartialEq, Eq, Debug)]
|
||||
@@ -260,16 +272,19 @@ fn number_flag() {
|
||||
one: bool,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-1"]).unwrap(), Settings { one: true })
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-1"]).unwrap(),
|
||||
Settings { one: true }
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_bool() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
A,
|
||||
#[flag]
|
||||
#[option]
|
||||
B,
|
||||
}
|
||||
|
||||
@@ -283,16 +298,28 @@ fn false_bool() {
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-a"]).unwrap(), Settings { foo: true });
|
||||
assert_eq!(Settings::parse(["-b"]).unwrap(), Settings { foo: false });
|
||||
assert_eq!(Settings::parse(["-ab"]).unwrap(), Settings { foo: false });
|
||||
assert_eq!(Settings::parse(["-ba"]).unwrap(), Settings { foo: true });
|
||||
assert_eq!(
|
||||
Settings::parse(["-a", "-b"]).unwrap(),
|
||||
Settings::parse(["test", "-a"]).unwrap(),
|
||||
Settings { foo: true }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-b"]).unwrap(),
|
||||
Settings { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["-b", "-a"]).unwrap(),
|
||||
Settings::parse(["test", "-ab"]).unwrap(),
|
||||
Settings { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-ba"]).unwrap(),
|
||||
Settings { foo: true }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-a", "-b"]).unwrap(),
|
||||
Settings { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-b", "-a"]).unwrap(),
|
||||
Settings { foo: true }
|
||||
);
|
||||
|
||||
@@ -304,16 +331,28 @@ fn false_bool() {
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
assert_eq!(Settings2::parse(["-a"]).unwrap(), Settings2 { foo: true });
|
||||
assert_eq!(Settings2::parse(["-b"]).unwrap(), Settings2 { foo: false });
|
||||
assert_eq!(Settings2::parse(["-ab"]).unwrap(), Settings2 { foo: false });
|
||||
assert_eq!(Settings2::parse(["-ba"]).unwrap(), Settings2 { foo: true });
|
||||
assert_eq!(
|
||||
Settings2::parse(["-a", "-b"]).unwrap(),
|
||||
Settings2::parse(["test", "-a"]).unwrap(),
|
||||
Settings2 { foo: true }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings2::parse(["test", "-b"]).unwrap(),
|
||||
Settings2 { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings2::parse(["-b", "-a"]).unwrap(),
|
||||
Settings2::parse(["test", "-ab"]).unwrap(),
|
||||
Settings2 { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings2::parse(["test", "-ba"]).unwrap(),
|
||||
Settings2 { foo: true }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings2::parse(["test", "-a", "-b"]).unwrap(),
|
||||
Settings2 { foo: false }
|
||||
);
|
||||
assert_eq!(
|
||||
Settings2::parse(["test", "-b", "-a"]).unwrap(),
|
||||
Settings2 { foo: true }
|
||||
);
|
||||
}
|
||||
@@ -330,11 +369,11 @@ fn enum_flag() {
|
||||
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
Foo,
|
||||
#[flag]
|
||||
#[option]
|
||||
Bar,
|
||||
#[flag]
|
||||
#[option]
|
||||
Baz,
|
||||
}
|
||||
|
||||
@@ -355,12 +394,12 @@ fn enum_flag() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--bar"]).unwrap().foo,
|
||||
Settings::parse(["test", "--bar"]).unwrap().foo,
|
||||
SomeEnum::VariantBar
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--baz"]).unwrap().foo,
|
||||
Settings::parse(["test", "--baz"]).unwrap().foo,
|
||||
SomeEnum::VariantBaz,
|
||||
);
|
||||
}
|
||||
@@ -369,7 +408,7 @@ fn enum_flag() {
|
||||
fn count() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag]
|
||||
#[option]
|
||||
Verbosity,
|
||||
}
|
||||
|
||||
@@ -380,20 +419,20 @@ fn count() {
|
||||
verbosity: u8,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-v"]).unwrap().verbosity, 1);
|
||||
assert_eq!(Settings::parse(["-vv"]).unwrap().verbosity, 2);
|
||||
assert_eq!(Settings::parse(["-vvv"]).unwrap().verbosity, 3);
|
||||
assert_eq!(Settings::parse(["test", "-v"]).unwrap().verbosity, 1);
|
||||
assert_eq!(Settings::parse(["test", "-vv"]).unwrap().verbosity, 2);
|
||||
assert_eq!(Settings::parse(["test", "-vvv"]).unwrap().verbosity, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_long_args() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[flag("--all")]
|
||||
#[option("--all")]
|
||||
All,
|
||||
#[flag("--almost-all")]
|
||||
#[option("--almost-all")]
|
||||
AlmostAll,
|
||||
#[flag("--author")]
|
||||
#[option("--author")]
|
||||
Author,
|
||||
}
|
||||
|
||||
@@ -408,8 +447,8 @@ fn infer_long_args() {
|
||||
author: bool,
|
||||
}
|
||||
|
||||
assert!(Settings::parse(["--all"]).unwrap().all);
|
||||
assert!(Settings::parse(["--alm"]).unwrap().almost_all);
|
||||
assert!(Settings::parse(["--au"]).unwrap().author);
|
||||
assert!(Settings::parse(["--a"]).is_err());
|
||||
assert!(Settings::parse(["test", "--all"]).unwrap().all);
|
||||
assert!(Settings::parse(["test", "--alm"]).unwrap().almost_all);
|
||||
assert!(Settings::parse(["test", "--au"]).unwrap().author);
|
||||
assert!(Settings::parse(["test", "--a"]).is_err());
|
||||
}
|
||||
|
||||
+43
-29
@@ -16,7 +16,9 @@ fn string_option() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--message=hello"]).unwrap().message,
|
||||
Settings::parse(["test", "--message=hello"])
|
||||
.unwrap()
|
||||
.message,
|
||||
"hello"
|
||||
);
|
||||
}
|
||||
@@ -48,12 +50,12 @@ fn enum_option() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--format=bar"]).unwrap().format,
|
||||
Settings::parse(["test", "--format=bar"]).unwrap().format,
|
||||
Format::Bar
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--format", "baz"]).unwrap().format,
|
||||
Settings::parse(["test", "--format", "baz"]).unwrap().format,
|
||||
Format::Baz
|
||||
);
|
||||
}
|
||||
@@ -83,11 +85,11 @@ fn enum_option_with_fields() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["-i=thin"]).unwrap().indent,
|
||||
Settings::parse(["test", "-i=thin"]).unwrap().indent,
|
||||
Indent::Spaces(4)
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["-i=wide"]).unwrap().indent,
|
||||
Settings::parse(["test", "-i=wide"]).unwrap().indent,
|
||||
Indent::Spaces(8)
|
||||
);
|
||||
}
|
||||
@@ -130,8 +132,14 @@ fn enum_with_complex_from_value() {
|
||||
indent: Indent,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-i=tabs"]).unwrap().indent, Indent::Tabs);
|
||||
assert_eq!(Settings::parse(["-i=4"]).unwrap().indent, Indent::Spaces(4));
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-i=tabs"]).unwrap().indent,
|
||||
Indent::Tabs
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "-i=4"]).unwrap().indent,
|
||||
Indent::Spaces(4)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -164,23 +172,29 @@ fn color() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
Settings::parse(["--color=yes"]).unwrap().color,
|
||||
Settings::parse(["test", "--color=yes"]).unwrap().color,
|
||||
Color::Always
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["--color=always"]).unwrap().color,
|
||||
Settings::parse(["test", "--color=always"]).unwrap().color,
|
||||
Color::Always
|
||||
);
|
||||
assert_eq!(Settings::parse(["--color=no"]).unwrap().color, Color::Never);
|
||||
assert_eq!(
|
||||
Settings::parse(["--color=never"]).unwrap().color,
|
||||
Settings::parse(["test", "--color=no"]).unwrap().color,
|
||||
Color::Never
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["--color=auto"]).unwrap().color,
|
||||
Settings::parse(["test", "--color=never"]).unwrap().color,
|
||||
Color::Never
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "--color=auto"]).unwrap().color,
|
||||
Color::Auto
|
||||
);
|
||||
assert_eq!(Settings::parse(["--color"]).unwrap().color, Color::Always)
|
||||
assert_eq!(
|
||||
Settings::parse(["test", "--color"]).unwrap().color,
|
||||
Color::Always
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -189,9 +203,9 @@ fn actions() {
|
||||
enum Arg {
|
||||
#[option]
|
||||
Message(String),
|
||||
#[flag]
|
||||
#[option]
|
||||
Send,
|
||||
#[flag]
|
||||
#[option]
|
||||
Receive,
|
||||
}
|
||||
|
||||
@@ -213,7 +227,7 @@ fn actions() {
|
||||
messages: Vec<String>,
|
||||
}
|
||||
|
||||
let settings = Settings::parse(["-m=Hello", "-m=World", "--send"]).unwrap();
|
||||
let settings = Settings::parse(["test", "-m=Hello", "-m=World", "--send"]).unwrap();
|
||||
assert_eq!(settings.messages, vec!["Hello", "World"]);
|
||||
assert_eq!(settings.message1, "World");
|
||||
assert_eq!(settings.message2, "World");
|
||||
@@ -235,11 +249,11 @@ fn width() {
|
||||
Arg::Width(0) => None,
|
||||
Arg::Width(x) => Some(x),
|
||||
)]
|
||||
width: Option<u64>
|
||||
width: Option<u64>,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["-w=0"]).unwrap().width, None);
|
||||
assert_eq!(Settings::parse(["-w=1"]).unwrap().width, Some(1));
|
||||
assert_eq!(Settings::parse(["test", "-w=0"]).unwrap().width, None);
|
||||
assert_eq!(Settings::parse(["test", "-w=1"]).unwrap().width, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -286,15 +300,15 @@ fn integers() {
|
||||
n: i128,
|
||||
}
|
||||
|
||||
assert_eq!(Settings::parse(["--u8=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--u16=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--u32=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--u64=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--u128=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--u8=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--u16=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--u32=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--u64=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--u128=5"]).unwrap().n, 5);
|
||||
|
||||
assert_eq!(Settings::parse(["--i8=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--i16=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--i32=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--i64=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["--i128=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--i8=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--i16=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--i32=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--i64=5"]).unwrap().n, 5);
|
||||
assert_eq!(Settings::parse(["test", "--i128=5"]).unwrap().n, 5);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use std::ffi::OsString;
|
||||
use uutils_args::{Arguments, Options};
|
||||
|
||||
const EMPTY: [OsString; 0] = [];
|
||||
|
||||
#[test]
|
||||
fn one_positional() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[positional(1)]
|
||||
File1(String),
|
||||
}
|
||||
|
||||
#[derive(Default, Options)]
|
||||
#[arg_type(Arg)]
|
||||
struct Settings {
|
||||
#[set(Arg::File1)]
|
||||
file1: String,
|
||||
}
|
||||
|
||||
let settings = Settings::parse(["test", "foo"]).unwrap();
|
||||
assert_eq!(settings.file1, "foo");
|
||||
|
||||
assert!(Settings::parse(EMPTY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_positionals() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[positional(1)]
|
||||
Foo(String),
|
||||
#[positional(1)]
|
||||
Bar(String),
|
||||
}
|
||||
|
||||
#[derive(Default, Options)]
|
||||
#[arg_type(Arg)]
|
||||
struct Settings {
|
||||
#[set(Arg::Foo)]
|
||||
foo: String,
|
||||
#[set(Arg::Bar)]
|
||||
bar: String,
|
||||
}
|
||||
|
||||
let settings = Settings::parse(["test", "a", "b"]).unwrap();
|
||||
assert_eq!(settings.foo, "a");
|
||||
assert_eq!(settings.bar, "b");
|
||||
|
||||
assert!(Settings::parse(EMPTY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_positional() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[positional(0..=1)]
|
||||
Foo(String),
|
||||
}
|
||||
|
||||
#[derive(Default, Options)]
|
||||
#[arg_type(Arg)]
|
||||
struct Settings {
|
||||
#[map(Arg::Foo(s) => Some(s))]
|
||||
foo: Option<String>,
|
||||
}
|
||||
|
||||
let settings = Settings::parse(EMPTY).unwrap();
|
||||
assert_eq!(settings.foo, None);
|
||||
let settings = Settings::parse(["test", "bar"]).unwrap();
|
||||
assert_eq!(settings.foo.unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_positional() {
|
||||
#[derive(Arguments, Clone)]
|
||||
enum Arg {
|
||||
#[positional(..)]
|
||||
Foo(String),
|
||||
}
|
||||
|
||||
#[derive(Default, Options)]
|
||||
#[arg_type(Arg)]
|
||||
struct Settings {
|
||||
#[collect(set(Arg::Foo))]
|
||||
foo: Vec<String>,
|
||||
}
|
||||
|
||||
let settings = Settings::parse(["test", "a", "b", "c"]).unwrap();
|
||||
assert_eq!(settings.foo, vec!["a", "b", "c"]);
|
||||
let settings = Settings::parse(EMPTY).unwrap();
|
||||
assert_eq!(settings.foo, Vec::<String>::new());
|
||||
}
|
||||
Reference in New Issue
Block a user