mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
add version flags and refactor all flag handling with a Flags struct
This commit is contained in:
+67
-70
@@ -6,7 +6,7 @@ use syn::{Attribute, Fields, FieldsUnnamed, Lit, Meta, Variant, Ident, punctuate
|
||||
|
||||
use crate::{
|
||||
attributes::{parse_argument_attribute, ArgAttr},
|
||||
Arg,
|
||||
flags::Flags,
|
||||
};
|
||||
|
||||
pub(crate) struct Argument {
|
||||
@@ -24,8 +24,7 @@ pub(crate) enum TakesValue {
|
||||
|
||||
pub(crate) enum ArgType {
|
||||
Option {
|
||||
short_flags: Vec<char>,
|
||||
long_flags: Vec<String>,
|
||||
flags: Flags,
|
||||
takes_value: TakesValue,
|
||||
},
|
||||
Positional {
|
||||
@@ -33,24 +32,32 @@ pub(crate) enum ArgType {
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn parse_help_flags(attrs: &[Attribute]) -> (Vec<char>, Vec<String>) {
|
||||
pub(crate) fn parse_help_flags(attrs: &[Attribute]) -> Flags {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("help") {
|
||||
let mut short = Vec::new();
|
||||
let mut long = Vec::new();
|
||||
let mut flags = Flags::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);
|
||||
flags.add(&s);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
return (vec!['h'], vec!["help".into()])
|
||||
return Flags::default_help();
|
||||
}
|
||||
|
||||
pub(crate) fn parse_version_flags(attrs: &[Attribute]) -> Flags {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("version") {
|
||||
let mut flags = Flags::new();
|
||||
for s in attr.parse_args_with(Punctuated::<LitStr, Token![,]>::parse_terminated).unwrap() {
|
||||
let s = s.value().to_string();
|
||||
flags.add(&s);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
return Flags::default_version();
|
||||
}
|
||||
|
||||
pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
@@ -76,15 +83,14 @@ pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
|
||||
let arg_type = match attribute {
|
||||
ArgAttr::Option(opt) => {
|
||||
let (short_flags, long_flags) = flag_names(opt.flags, &name);
|
||||
let flags = opt.flags.or_from_name(&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,
|
||||
flags,
|
||||
takes_value,
|
||||
}
|
||||
}
|
||||
@@ -144,42 +150,21 @@ fn type_is_option(syn_type: &syn::Type) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let ArgType::Option { ref flags, .. } = arg.arg_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if short_flags.is_empty() {
|
||||
if flags.short.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
let pat = flags.short_pat();
|
||||
let expr = argument_expression(arg);
|
||||
match_arms.push(quote!(#(#short_flags)|* => { #expr }))
|
||||
match_arms.push(quote!(#pat => { #expr }))
|
||||
}
|
||||
|
||||
quote!(
|
||||
@@ -190,32 +175,34 @@ pub(crate) fn short_handling(args: &[Argument]) -> TokenStream {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn long_handling(args: &[Argument], long_help_flags: &[String]) -> TokenStream {
|
||||
pub(crate) fn long_handling(args: &[Argument], help_flags: &Flags) -> TokenStream {
|
||||
let mut match_arms = Vec::new();
|
||||
let mut options = Vec::new();
|
||||
|
||||
options.extend(long_help_flags);
|
||||
options.extend(help_flags.long.clone());
|
||||
|
||||
for arg in args {
|
||||
let ArgType::Option { ref long_flags, .. } = arg.arg_type else {
|
||||
let ArgType::Option { ref flags, .. } = arg.arg_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if long_flags.is_empty() {
|
||||
if flags.long.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pat = flags.long_pat();
|
||||
let expr = argument_expression(arg);
|
||||
match_arms.push(quote!(#(#long_flags)|* => { #expr }));
|
||||
options.extend(long_flags);
|
||||
match_arms.push(quote!(#pat => { #expr }));
|
||||
options.extend(flags.long.clone());
|
||||
}
|
||||
|
||||
if options.is_empty() {
|
||||
return quote!(return Err(arg.unexpected().into()));
|
||||
}
|
||||
|
||||
let help_check = if !long_help_flags.is_empty() {
|
||||
quote!(if let #(#long_help_flags)|* = opt {
|
||||
let help_check = if !help_flags.long.is_empty() {
|
||||
let pat = help_flags.long_pat();
|
||||
quote!(if let #pat = opt {
|
||||
return Ok(Some(Argument::Help));
|
||||
})
|
||||
} else {
|
||||
@@ -334,13 +321,12 @@ fn argument_expression(arg: &Argument) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn help_handling(short_help_flags: &[char], long_help_flags: &[String]) -> TokenStream {
|
||||
let pat = match (short_help_flags, long_help_flags) {
|
||||
([], []) => return quote!(),
|
||||
(short, []) => quote!(lexopt::Arg::Short(#(#short)|*)),
|
||||
([], long) => quote!(lexopt::Arg::Long(#(#long)|*)),
|
||||
(short, long) => quote!(lexopt::Arg::Short(#(#short)|*) | lexopt::Arg::Long(#(#long)|*))
|
||||
};
|
||||
pub(crate) fn help_handling(help_flags: &Flags) -> TokenStream {
|
||||
if help_flags.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let pat = help_flags.pat();
|
||||
|
||||
quote!(
|
||||
if let #pat = arg {
|
||||
@@ -349,7 +335,21 @@ pub(crate) fn help_handling(short_help_flags: &[char], long_help_flags: &[String
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn help_string(args: &[Argument], short_help_flags: &[char], long_help_flags: &[String]) -> TokenStream {
|
||||
pub(crate) fn version_handling(version_flags: &Flags) -> TokenStream {
|
||||
if version_flags.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let pat = version_flags.pat();
|
||||
|
||||
quote!(
|
||||
if let #pat = arg {
|
||||
return Ok(Some(Argument::Version));
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn help_string(args: &[Argument], help_flags: &Flags, version_flags: &Flags) -> TokenStream {
|
||||
let mut options = Vec::new();
|
||||
|
||||
let width = 16;
|
||||
@@ -357,19 +357,24 @@ pub(crate) fn help_string(args: &[Argument], short_help_flags: &[char], long_hel
|
||||
|
||||
for Argument { arg_type, help, ..} in args {
|
||||
match arg_type {
|
||||
ArgType::Option { short_flags, long_flags, ..} => {
|
||||
let flags = format_flags(short_flags, long_flags);
|
||||
ArgType::Option { flags, ..} => {
|
||||
let flags = flags.format();
|
||||
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() {
|
||||
let help_flags = help_flags.format();
|
||||
options.push(format_help_line(indent, width, &help_flags, "Display this help message"));
|
||||
}
|
||||
|
||||
if !version_flags.is_empty() {
|
||||
let version_flags = version_flags.format();
|
||||
options.push(format_help_line(indent, width, &version_flags, "Display version information"));
|
||||
}
|
||||
|
||||
let options = format!(
|
||||
"Options:\n{}",
|
||||
options.join("\n"),
|
||||
@@ -380,14 +385,6 @@ pub(crate) fn help_string(args: &[Argument], short_help_flags: &[char], long_hel
|
||||
)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
|
||||
@@ -6,7 +6,7 @@ use syn::{
|
||||
Attribute, Expr, ExprLit, ExprRange, Ident, Lit, LitInt, LitStr, RangeLimits, Token,
|
||||
};
|
||||
|
||||
use crate::Arg;
|
||||
use crate::{flags::Flags, Arg};
|
||||
|
||||
pub(crate) enum ArgAttr {
|
||||
Option(OptionAttr),
|
||||
@@ -25,7 +25,7 @@ pub(crate) fn parse_argument_attribute(attr: &Attribute) -> ArgAttr {
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OptionAttr {
|
||||
pub(crate) flags: Vec<Arg>,
|
||||
pub(crate) flags: Flags,
|
||||
// This should probably not accept any expr to give better errors.
|
||||
// Closures should be allowed though.
|
||||
pub(crate) parser: Option<Expr>,
|
||||
@@ -71,7 +71,8 @@ pub(crate) fn parse_option_attr(attr: &Attribute) -> OptionAttr {
|
||||
|
||||
for arg in parsed_args {
|
||||
match arg {
|
||||
OptionAttrArg::Arg(a) => option_attr.flags.push(a),
|
||||
OptionAttrArg::Arg(Arg::Short(a)) => option_attr.flags.short.push(a),
|
||||
OptionAttrArg::Arg(Arg::Long(a)) => option_attr.flags.long.push(a),
|
||||
OptionAttrArg::Parser(e) => option_attr.parser = Some(e),
|
||||
};
|
||||
}
|
||||
@@ -217,6 +218,6 @@ impl Parse for PositionalAttrArg {
|
||||
// _ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
// };
|
||||
// }
|
||||
panic!("unpexpected argument to positional");
|
||||
panic!("unexpected argument to positional");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Flags {
|
||||
pub short: Vec<char>,
|
||||
pub long: Vec<String>,
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
short: Vec::new(),
|
||||
long: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, flag: &str) {
|
||||
assert!(flag.starts_with('-'), "Flags must start with a '-'");
|
||||
if let Some(s) = flag.strip_prefix("--") {
|
||||
self.long.push(s.to_string());
|
||||
} else if let Some(s) = flag.strip_prefix("-") {
|
||||
assert_eq!(s.len(), 1);
|
||||
self.short.push(s.chars().next().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.short.is_empty() && self.long.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn short_pat(&self) -> TokenStream {
|
||||
let short = &self.short;
|
||||
quote!(#(#short)|*)
|
||||
}
|
||||
|
||||
pub(crate) fn long_pat(&self) -> TokenStream {
|
||||
let long = &self.long;
|
||||
quote!(#(#long)|*)
|
||||
}
|
||||
|
||||
pub(crate) fn pat(&self) -> TokenStream {
|
||||
match (&self.short[..], &self.long[..]) {
|
||||
([], []) => panic!("Creating pattern from empty flags, probably not what you want!"),
|
||||
(short, []) => quote!(lexopt::Arg::Short(#(#short)|*)),
|
||||
([], long) => quote!(lexopt::Arg::Long(#(#long)|*)),
|
||||
(short, long) => {
|
||||
quote!(lexopt::Arg::Short(#(#short)|*) | lexopt::Arg::Long(#(#long)|*))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_help() -> Self {
|
||||
Self {
|
||||
short: vec!['h'],
|
||||
long: vec!["help".into()],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_version() -> Self {
|
||||
Self {
|
||||
short: vec!['V'],
|
||||
long: vec!["version".into()],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format(&self) -> String {
|
||||
self.short
|
||||
.iter()
|
||||
.map(|s| format!("-{s}"))
|
||||
.chain(self.long.iter().map(|l| format!("--{l}")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub(crate) fn or_from_name(self, name: &str) -> Self {
|
||||
if self.is_empty() {
|
||||
let name = name.to_lowercase();
|
||||
let first_char = name.chars().next().unwrap();
|
||||
if name.len() > 1 {
|
||||
Self {
|
||||
short: vec![first_char],
|
||||
long: vec![name.to_string()],
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
short: vec![first_char],
|
||||
long: vec![],
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-5
@@ -1,11 +1,12 @@
|
||||
mod action;
|
||||
mod argument;
|
||||
mod attributes;
|
||||
mod flags;
|
||||
|
||||
use action::{parse_action_attr, ActionAttr, ActionType};
|
||||
use argument::{
|
||||
help_handling, help_string, long_handling, parse_argument, parse_help_flags,
|
||||
positional_handling, short_handling,
|
||||
parse_version_flags, positional_handling, short_handling, version_handling,
|
||||
};
|
||||
use attributes::{parse_value_attr, ValueAttr};
|
||||
|
||||
@@ -116,6 +117,9 @@ pub fn options(input: TokenStream) -> TokenStream {
|
||||
println!("{}", iter.help());
|
||||
std::process::exit(0);
|
||||
},
|
||||
Argument::Version => {
|
||||
println!("{}", iter.version());
|
||||
},
|
||||
Argument::Custom(arg) => {
|
||||
#(#stmts)*
|
||||
}
|
||||
@@ -141,14 +145,21 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
panic!("Input should be an enum!");
|
||||
};
|
||||
|
||||
let (short_help_flags, long_help_flags) = parse_help_flags(&input.attrs);
|
||||
let help_flags = parse_help_flags(&input.attrs);
|
||||
let version_flags = parse_version_flags(&input.attrs);
|
||||
let arguments: Vec<_> = data.variants.into_iter().flat_map(parse_argument).collect();
|
||||
|
||||
let short = short_handling(&arguments);
|
||||
let long = long_handling(&arguments, &long_help_flags);
|
||||
let long = long_handling(&arguments, &help_flags);
|
||||
let (positional, missing_argument_checks) = positional_handling(&arguments);
|
||||
let help_string = help_string(&arguments, &short_help_flags, &long_help_flags);
|
||||
let help = help_handling(&short_help_flags, &long_help_flags);
|
||||
let help_string = help_string(&arguments, &help_flags, &version_flags);
|
||||
let help = help_handling(&help_flags);
|
||||
let version = version_handling(&version_flags);
|
||||
let version_string = quote!(format!(
|
||||
"{} {}",
|
||||
option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
));
|
||||
|
||||
let expanded = quote!(
|
||||
impl #impl_generics Arguments for #name #ty_generics #where_clause {
|
||||
@@ -162,6 +173,8 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
|
||||
#help
|
||||
|
||||
#version
|
||||
|
||||
let parsed = match arg {
|
||||
lexopt::Arg::Short(short) => { #short }
|
||||
lexopt::Arg::Long(long) => { #long }
|
||||
@@ -177,6 +190,10 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
fn help(bin_name: &str) -> String {
|
||||
#help_string
|
||||
}
|
||||
|
||||
fn version() -> String {
|
||||
#version_string
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ impl From<lexopt::Error> for Error {
|
||||
#[derive(Clone)]
|
||||
pub enum Argument<T: Arguments> {
|
||||
Help,
|
||||
Version,
|
||||
Custom(T),
|
||||
}
|
||||
|
||||
@@ -68,6 +69,8 @@ pub trait Arguments: Sized + Clone {
|
||||
fn check_missing(positional_idx: usize) -> Result<(), Error>;
|
||||
|
||||
fn help(bin_name: &str) -> String;
|
||||
|
||||
fn version() -> String;
|
||||
}
|
||||
|
||||
pub struct ArgumentIter<T: Arguments> {
|
||||
@@ -96,6 +99,10 @@ impl<T: Arguments> ArgumentIter<T> {
|
||||
pub fn help(&self) -> String {
|
||||
T::help(self.parser.bin_name().unwrap())
|
||||
}
|
||||
|
||||
pub fn version(&self) -> String {
|
||||
T::version()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Options: Sized + Default {
|
||||
|
||||
Reference in New Issue
Block a user