Skip to content

Commit

Permalink
dd-style and numeric arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
tertsdiepraam committed Nov 1, 2023
1 parent 2f1a9b0 commit 6c16203
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 34 deletions.
75 changes: 63 additions & 12 deletions derive/src/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{Attribute, Fields, FieldsUnnamed, Ident, Meta, Variant};

use crate::{
attributes::{parse_argument_attribute, ArgAttr, ArgumentsAttr},
flags::{Flag, Flags, Value},
flags::{Flags, Value},
};

pub(crate) struct Argument {
Expand All @@ -27,6 +27,9 @@ pub(crate) enum ArgType {
num_args: RangeInclusive<usize>,
last: bool,
},
Free {
filters: Vec<syn::Ident>,
},
}

pub(crate) fn parse_arguments_attr(attrs: &[Attribute]) -> ArgumentsAttr {
Expand Down Expand Up @@ -93,6 +96,9 @@ pub(crate) fn parse_argument(v: Variant) -> Vec<Argument> {
last: pos.last,
}
}
ArgAttr::Free(free) => ArgType::Free {
filters: free.filters,
},
};
Argument {
ident: ident.clone(),
Expand Down Expand Up @@ -129,7 +135,11 @@ fn collect_help(attrs: &[Attribute]) -> String {
fn get_arg_attributes(attrs: &[Attribute]) -> syn::Result<Vec<ArgAttr>> {
attrs
.iter()
.filter(|a| a.path().is_ident("option") || a.path().is_ident("positional"))
.filter(|a| {
a.path().is_ident("option")
|| a.path().is_ident("positional")
|| a.path().is_ident("free")
})
.map(parse_argument_attribute)
.collect()
}
Expand All @@ -147,6 +157,7 @@ pub(crate) fn short_handling(args: &[Argument]) -> (TokenStream, Vec<char>) {
hidden: _,
} => (flags, takes_value, default),
ArgType::Positional { .. } => continue,
ArgType::Free { .. } => continue,
};

if flags.short.is_empty() {
Expand Down Expand Up @@ -196,6 +207,7 @@ pub(crate) fn long_handling(args: &[Argument], help_flags: &Flags) -> TokenStrea
hidden: _,
} => (flags, takes_value, default),
ArgType::Positional { .. } => continue,
ArgType::Free { .. } => continue,
};

if flags.long.is_empty() {
Expand Down Expand Up @@ -269,27 +281,65 @@ pub(crate) fn long_handling(args: &[Argument], help_flags: &Flags) -> TokenStrea
)
}

pub(crate) fn number_handling(args: &[Argument]) -> TokenStream {
let mut number_args = Vec::new();
pub(crate) fn free_handling(args: &[Argument]) -> TokenStream {
let mut if_expressions = Vec::new();

for arg in args {
let flags = match &arg.arg_type {
// Free arguments
for arg @ Argument { arg_type, .. } in args {
let filters = match arg_type {
ArgType::Free { filters } => filters,
ArgType::Positional { .. } => continue,
ArgType::Option { .. } => continue,
};

for filter in filters {
let ident = &arg.ident;

if_expressions.push(quote!(
if let Some(inner) = #filter(arg) {
let value = ::uutils_args::parse_value_for_option("", ::std::ffi::OsStr::new(inner))?;
let _ = raw.next();
return Ok(Some(Argument::Custom(Self::#ident(value))));
}
));
}
}

// dd-style arguments
let mut dd_branches = Vec::new();
for arg @ Argument { arg_type, .. } in args {
let flags = match arg_type {
ArgType::Option { flags, .. } => flags,
ArgType::Free { .. } => continue,
ArgType::Positional { .. } => continue,
};

let ident = &arg.ident;
for (prefix, _) in &flags.dd_style {
let ident = &arg.ident;

for Flag { flag: prefix, .. } in &flags.number_prefix {
number_args.push(quote!(
if let Some(v) = ::uutils_args::parse_prefix(parser, #prefix) {
return Ok(Some(::uutils_args::Argument::Custom(Self::#ident(v))));
dd_branches.push(quote!(
if prefix == #prefix {
let value = ::uutils_args::parse_value_for_option("", ::std::ffi::OsStr::new(value))?;
let _ = raw.next();
return Ok(Some(Argument::Custom(Self::#ident(value))));
}
));
}
}

quote!(#(#number_args)*)
if !dd_branches.is_empty() {
if_expressions.push(quote!(
if let Some((prefix, value)) = arg.split_once('=') {
#(#dd_branches)*
}
));
}

quote!(if let Some(mut raw) = parser.try_raw_args() {
if let Some(arg) = raw.peek().and_then(|s| s.to_str()) {
#(#if_expressions)*
}
})
}

pub(crate) fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStream) {
Expand All @@ -306,6 +356,7 @@ pub(crate) fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStrea
let (num_args, last) = match arg_type {
ArgType::Positional { num_args, last } => (num_args, last),
ArgType::Option { .. } => continue,
ArgType::Free { .. } => continue,
};

if *num_args.start() > 0 {
Expand Down
22 changes: 22 additions & 0 deletions derive/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ use crate::flags::Flags;
pub(crate) enum ArgAttr {
Option(OptionAttr),
Positional(PositionalAttr),
Free(FreeAttr),
}

pub(crate) fn parse_argument_attribute(attr: &Attribute) -> syn::Result<ArgAttr> {
if attr.path().is_ident("option") {
Ok(ArgAttr::Option(OptionAttr::parse(attr)?))
} else if attr.path().is_ident("positional") {
Ok(ArgAttr::Positional(PositionalAttr::parse(attr)?))
} else if attr.path().is_ident("free") {
Ok(ArgAttr::Free(FreeAttr::parse(attr)?))
} else {
panic!("Internal error: invalid argument attribute");
}
Expand Down Expand Up @@ -185,6 +188,25 @@ impl OptionAttr {
}
}

#[derive(Default)]
pub(crate) struct FreeAttr {
pub(crate) filters: Vec<syn::Ident>,
}

impl FreeAttr {
pub(crate) fn parse(attr: &Attribute) -> syn::Result<Self> {
let mut free_attr = FreeAttr::default();

parse_args(attr, |s: ParseStream| {
let ident = s.parse::<Ident>()?;
free_attr.filters.push(ident);
Ok(())
})?;

Ok(free_attr)
}
}

#[derive(Default)]
pub(crate) struct ValueAttr {
pub(crate) keys: Vec<String>,
Expand Down
24 changes: 9 additions & 15 deletions derive/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use quote::quote;
pub(crate) struct Flags {
pub short: Vec<Flag<char>>,
pub long: Vec<Flag<String>>,
pub number_prefix: Vec<Flag<String>>,
pub dd_style: Vec<(String, String)>,
}

#[derive(Clone)]
Expand All @@ -31,19 +31,7 @@ impl Flags {
}

pub(crate) fn add(&mut self, flag: &str) {
if let Some((prefix, rest)) = flag.split_once('{') {
// It's an argument with a prefix, e.g. "-{N}" or "+{N}"
let (value_name, after) = rest
.split_once('}')
.expect("Missing '}' in flag definition");

assert!(after.is_empty(), "There can be no suffix after '}}'");

self.number_prefix.push(Flag {
flag: prefix.into(),
value: Value::Required(value_name.into()),
});
} else if let Some(s) = flag.strip_prefix("--") {
if let Some(s) = flag.strip_prefix("--") {
// There are three possible patterns:
// --flag
// --flag=value
Expand Down Expand Up @@ -110,11 +98,17 @@ impl Flags {
panic!("Invalid short flag '{flag}'")
};
self.short.push(Flag { flag: f, value });
} else if let Some((s, v)) = flag.split_once('=') {
// It's a dd-style argument: arg=value
assert!(!s.is_empty());
assert!(!v.is_empty());

self.dd_style.push((s.into(), v.into()));
}
}

pub(crate) fn is_empty(&self) -> bool {
self.short.is_empty() && self.long.is_empty() && self.number_prefix.is_empty()
self.short.is_empty() && self.long.is_empty() && self.dd_style.is_empty()
}

pub(crate) fn pat(&self) -> TokenStream {
Expand Down
2 changes: 2 additions & 0 deletions derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub(crate) fn help_string(
// Hidden arguments should not show up in --help
ArgType::Option { hidden: true, .. } => {}
ArgType::Positional { .. } => {}
// TODO: Free arguments should show up in help
ArgType::Free { .. } => {}
}
}

Expand Down
10 changes: 6 additions & 4 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod help_parser;
mod initial;

use argument::{
long_handling, number_handling, parse_argument, parse_arguments_attr, positional_handling,
free_handling, long_handling, parse_argument, parse_arguments_attr, positional_handling,
short_handling,
};
use attributes::ValueAttr;
Expand All @@ -21,7 +21,7 @@ pub fn initial(input: TokenStream) -> TokenStream {
initial::initial(input)
}

#[proc_macro_derive(Arguments, attributes(flag, option, positional, arguments))]
#[proc_macro_derive(Arguments, attributes(flag, option, positional, free, arguments))]
pub fn arguments(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

Expand All @@ -38,7 +38,8 @@ pub fn arguments(input: TokenStream) -> TokenStream {
let exit_code = arguments_attr.exit_code;
let (short, short_flags) = short_handling(&arguments);
let long = long_handling(&arguments, &arguments_attr.help_flags);
let number_argument = number_handling(&arguments);
// let number_argument = number_handling(&arguments);
let free = free_handling(&arguments);
let (positional, missing_argument_checks) = positional_handling(&arguments);
let help_string = help_string(
&arguments,
Expand Down Expand Up @@ -76,7 +77,8 @@ pub fn arguments(input: TokenStream) -> TokenStream {
) -> Result<Option<uutils_args::Argument<Self>>, uutils_args::Error> {
use uutils_args::{Value, lexopt, Error, Argument};

#number_argument
// #number_argment
#free

let arg = match { #next_arg } {
Some(arg) => arg,
Expand Down
50 changes: 50 additions & 0 deletions examples/deprecated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use uutils_args::{Arguments, Initial, Options};

fn parse_minus(s: &str) -> Option<&str> {
let num = s.strip_prefix('-')?;
if num.chars().all(|c| c.is_ascii_digit()) {
Some(num)
} else {
None
}
}
fn parse_plus(s: &str) -> Option<&str> {
let num = s.strip_prefix('+')?;
let num2 = num.strip_prefix('-').unwrap_or(num);
if num2.chars().all(|c| c.is_ascii_digit()) {
Some(num)
} else {
None
}
}

#[derive(Arguments)]
enum Arg {
#[free(parse_minus)]
Min(usize),

#[free(parse_plus)]
Plus(isize),
}

#[derive(Initial)]
struct Settings {
n1: usize,
n2: isize,
}

impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Min(n) => self.n1 = n,
Arg::Plus(n) => self.n2 = n,
}
}
}

fn main() {
assert_eq!(Settings::parse(["test", "-10"]).n1, 10usize);
assert!(Settings::try_parse(["test", "--10"]).is_err());
assert_eq!(Settings::parse(["test", "+10"]).n2, 10isize);
assert_eq!(Settings::parse(["test", "+-10"]).n2, -10isize);
}
2 changes: 1 addition & 1 deletion examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ enum Arg {
/// Just to show off, I can do multiple paragraphs and wrap text!
///
/// # Also headings!
#[option("-n NAME", "--name=NAME")]
#[option("-n NAME", "--name=NAME", "name=NAME")]
Name(String),

/// The **number of times** to `greet`
Expand Down
22 changes: 20 additions & 2 deletions tests/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,30 @@ fn infer_value() {

#[test]
fn deprecated() {
fn parse_minus(s: &str) -> Option<&str> {
let num = s.strip_prefix('-')?;
if num.chars().all(|c| c.is_ascii_digit()) {
Some(num)
} else {
None
}
}
fn parse_plus(s: &str) -> Option<&str> {
let num = s.strip_prefix('+')?;
let num2 = num.strip_prefix('-').unwrap_or(num);
if num2.chars().all(|c| c.is_ascii_digit()) {
Some(num)
} else {
None
}
}

#[derive(Arguments)]
enum Arg {
#[option("-{N}")]
#[free(parse_minus)]
Min(usize),

#[option("+{N}")]
#[free(parse_plus)]
Plus(isize),
}

Expand Down

0 comments on commit 6c16203

Please sign in to comment.