You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Merge pull request #35 from tertsdiepraam/dd-style
dd-style and numeric arguments
This commit is contained in:
+64
-13
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -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(),
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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 {
|
||||
ArgType::Option { flags, .. } => flags,
|
||||
// Free arguments
|
||||
for arg @ Argument { arg_type, .. } in args {
|
||||
let filters = match arg_type {
|
||||
ArgType::Free { filters } => filters,
|
||||
ArgType::Positional { .. } => continue,
|
||||
ArgType::Option { .. } => continue,
|
||||
};
|
||||
|
||||
let ident = &arg.ident;
|
||||
for filter in filters {
|
||||
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))));
|
||||
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))));
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
quote!(#(#number_args)*)
|
||||
// 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,
|
||||
};
|
||||
|
||||
for (prefix, _) in &flags.dd_style {
|
||||
let ident = &arg.ident;
|
||||
|
||||
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))));
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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> {
|
||||
@@ -17,6 +18,8 @@ pub(crate) fn parse_argument_attribute(attr: &Attribute) -> syn::Result<ArgAttr>
|
||||
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");
|
||||
}
|
||||
@@ -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>,
|
||||
|
||||
+9
-15
@@ -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)]
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ impl InitialField {
|
||||
|
||||
fn into_expr(self) -> proc_macro2::TokenStream {
|
||||
let mut default_value = match self.expr {
|
||||
Some(val) => quote!(#val.into()),
|
||||
Some(val) => quote!(#val),
|
||||
None => quote!(::core::default::Default::default()),
|
||||
};
|
||||
|
||||
|
||||
+6
-4
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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`
|
||||
|
||||
@@ -13,6 +13,9 @@ mod basename;
|
||||
#[path = "coreutils/cat.rs"]
|
||||
mod cat;
|
||||
|
||||
#[path = "coreutils/dd.rs"]
|
||||
mod dd;
|
||||
|
||||
#[path = "coreutils/echo.rs"]
|
||||
mod echo;
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// spell-checker:ignore noxfer infile outfile iseek oseek conv iflag oflag iflags oflags
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uutils_args::{Arguments, Initial, Options, Value};
|
||||
|
||||
#[derive(Value, Debug, PartialEq, Eq)]
|
||||
enum StatusLevel {
|
||||
#[value("none")]
|
||||
None,
|
||||
#[value("noxfer")]
|
||||
Noxfer,
|
||||
#[value("progress")]
|
||||
Progress,
|
||||
}
|
||||
|
||||
// TODO: The bytes arguments should parse sizes
|
||||
#[derive(Arguments)]
|
||||
enum Arg {
|
||||
#[option("if=FILE")]
|
||||
Infile(PathBuf),
|
||||
|
||||
#[option("of=FILE")]
|
||||
Outfile(PathBuf),
|
||||
|
||||
#[option("ibs=BYTES")]
|
||||
Ibs(usize),
|
||||
|
||||
#[option("obs=BYTES")]
|
||||
Obs(usize),
|
||||
|
||||
#[option("bs=BYTES")]
|
||||
Bs(usize),
|
||||
|
||||
#[option("cbs=BYTES")]
|
||||
Cbs(usize),
|
||||
|
||||
#[option("skip=BYTES", "iseek=BYTES")]
|
||||
Skip(u64),
|
||||
|
||||
#[option("seek=BYTES", "oseek=BYTES")]
|
||||
Seek(u64),
|
||||
|
||||
#[option("count=N")]
|
||||
Count(usize),
|
||||
|
||||
#[option("status=LEVEL")]
|
||||
Status(StatusLevel),
|
||||
|
||||
#[option("conv=CONVERSIONS")]
|
||||
Conv(String),
|
||||
|
||||
#[option("iflag=FLAGS")]
|
||||
Iflag(String),
|
||||
|
||||
#[option("oflag=FLAGS")]
|
||||
Oflag(String),
|
||||
}
|
||||
|
||||
#[derive(Initial, Debug, PartialEq, Eq)]
|
||||
struct Settings {
|
||||
infile: Option<PathBuf>,
|
||||
outfile: Option<PathBuf>,
|
||||
#[initial(512)]
|
||||
ibs: usize,
|
||||
#[initial(512)]
|
||||
obs: usize,
|
||||
skip: u64,
|
||||
seek: u64,
|
||||
count: usize,
|
||||
_iconv: Vec<String>,
|
||||
_iflags: Vec<String>,
|
||||
_oconv: Vec<String>,
|
||||
_oflags: Vec<String>,
|
||||
status: Option<StatusLevel>,
|
||||
}
|
||||
|
||||
impl Options<Arg> for Settings {
|
||||
fn apply(&mut self, arg: Arg) {
|
||||
match arg {
|
||||
Arg::Infile(f) => self.infile = Some(f),
|
||||
Arg::Outfile(f) => self.outfile = Some(f),
|
||||
Arg::Ibs(b) => self.ibs = b,
|
||||
Arg::Obs(b) => self.obs = b,
|
||||
Arg::Bs(b) => {
|
||||
self.ibs = b;
|
||||
self.obs = b;
|
||||
}
|
||||
Arg::Cbs(_) => todo!(),
|
||||
Arg::Skip(b) => self.skip = b,
|
||||
Arg::Seek(b) => self.seek = b,
|
||||
Arg::Count(n) => self.count = n,
|
||||
Arg::Status(level) => self.status = Some(level),
|
||||
Arg::Conv(_) => todo!(),
|
||||
Arg::Iflag(_) => todo!(),
|
||||
Arg::Oflag(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(Settings::try_parse(["dd"]).unwrap(), Settings::initial())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infile() {
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "if=hello"]).unwrap(),
|
||||
Settings {
|
||||
infile: Some(PathBuf::from("hello")),
|
||||
..Settings::initial()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outfile() {
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "of=hello"]).unwrap(),
|
||||
Settings {
|
||||
outfile: Some(PathBuf::from("hello")),
|
||||
..Settings::initial()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bs() {
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "ibs=1"]).unwrap(),
|
||||
Settings {
|
||||
ibs: 1,
|
||||
obs: 512,
|
||||
..Settings::initial()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "obs=1"]).unwrap(),
|
||||
Settings {
|
||||
ibs: 512,
|
||||
obs: 1,
|
||||
..Settings::initial()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "ibs=10", "obs=1"]).unwrap(),
|
||||
Settings {
|
||||
ibs: 10,
|
||||
obs: 1,
|
||||
..Settings::initial()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Settings::try_parse(["dd", "ibs=10", "bs=1"]).unwrap(),
|
||||
Settings {
|
||||
ibs: 1,
|
||||
obs: 1,
|
||||
..Settings::initial()
|
||||
}
|
||||
)
|
||||
}
|
||||
+20
-2
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user