Merge pull request #35 from tertsdiepraam/dd-style

dd-style and numeric arguments
This commit is contained in:
Terts Diepraam
2023-11-09 13:07:34 +01:00
committed by GitHub
11 changed files with 339 additions and 36 deletions
+64 -13
View File
@@ -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 {
+22
View File
@@ -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
View File
@@ -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 {
+2
View File
@@ -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 { .. } => {}
}
}
+1 -1
View File
@@ -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
View File
@@ -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,
+50
View File
@@ -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);
}
+1 -1
View File
@@ -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`
+3
View File
@@ -13,6 +13,9 @@ mod basename;
#[path = "coreutils/cat.rs"]
mod cat;
#[path = "coreutils/dd.rs"]
mod dd;
#[path = "coreutils/echo.rs"]
mod echo;
+161
View File
@@ -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
View File
@@ -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),
}