Files
uutils-args/derive/src/lib.rs
T

202 lines
6.5 KiB
Rust
Raw Normal View History

// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
2022-12-10 15:54:31 +01:00
mod argument;
2022-11-27 22:48:05 +01:00
mod attributes;
mod flags;
2022-12-18 16:28:44 +01:00
mod help;
mod help_parser;
2023-03-08 14:57:27 +01:00
mod initial;
2022-12-02 00:41:35 +01:00
use argument::{
2023-11-01 12:23:39 +01:00
free_handling, long_handling, parse_argument, parse_arguments_attr, positional_handling,
short_handling,
};
2022-12-18 16:28:44 +01:00
use attributes::ValueAttr;
use help::{help_handling, help_string, version_handling};
2022-11-27 22:48:05 +01:00
2022-11-27 14:22:27 +01:00
use proc_macro::TokenStream;
use quote::quote;
2023-03-08 15:08:51 +01:00
use syn::{parse_macro_input, Data::Enum, DeriveInput};
2022-11-27 16:33:09 +01:00
2023-03-08 14:57:27 +01:00
#[proc_macro_derive(Initial, attributes(initial))]
pub fn initial(input: TokenStream) -> TokenStream {
2023-03-08 14:57:27 +01:00
initial::initial(input)
2022-11-27 14:22:27 +01:00
}
2022-11-27 16:33:09 +01:00
2023-11-01 12:23:39 +01:00
#[proc_macro_derive(Arguments, attributes(flag, option, positional, free, arguments))]
2022-12-01 18:37:17 +01:00
pub fn arguments(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let Enum(data) = input.data else {
panic!("Input should be an enum!");
};
let arguments_attr = parse_arguments_attr(&input.attrs);
2022-12-10 15:54:31 +01:00
let arguments: Vec<_> = data.variants.into_iter().flat_map(parse_argument).collect();
2022-12-01 18:37:17 +01:00
let exit_code = arguments_attr.exit_code;
let (short, short_flags) = short_handling(&arguments);
let long = long_handling(&arguments, &arguments_attr.help_flags);
2023-11-01 12:23:39 +01:00
// let number_argument = number_handling(&arguments);
let free = free_handling(&arguments);
2022-12-10 15:54:31 +01:00
let (positional, missing_argument_checks) = positional_handling(&arguments);
let help_string = help_string(
&arguments,
&arguments_attr.help_flags,
&arguments_attr.version_flags,
&arguments_attr.file,
);
let help = help_handling(&arguments_attr.help_flags);
let version = version_handling(&arguments_attr.version_flags);
let version_string = quote!(format!(
"{} {}",
option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")),
env!("CARGO_PKG_VERSION"),
));
2022-12-09 01:39:11 +01:00
// This is a bit of a hack to support `echo` and should probably not be
// used in general.
let next_arg = if arguments_attr.parse_echo_style {
quote!(if let Some(val) = uutils_args::__echo_style_positional(parser, &[#(#short_flags),*]) {
Some(lexopt::Arg::Value(val))
} else {
parser.next()?
})
} else {
quote!(parser.next()?)
};
2022-12-01 18:37:17 +01:00
let expanded = quote!(
impl #impl_generics Arguments for #name #ty_generics #where_clause {
const EXIT_CODE: i32 = #exit_code;
2022-12-09 01:39:11 +01:00
#[allow(unreachable_code)]
2022-12-10 15:54:31 +01:00
fn next_arg(
parser: &mut uutils_args::lexopt::Parser, positional_idx: &mut usize
) -> Result<Option<uutils_args::Argument<Self>>, uutils_args::Error> {
2023-02-14 00:51:27 +01:00
use uutils_args::{Value, lexopt, Error, Argument};
2022-12-10 15:54:31 +01:00
2023-11-01 12:23:39 +01:00
// #number_argment
#free
let arg = match { #next_arg } {
Some(arg) => arg,
None => return Ok(None),
};
2022-12-10 15:54:31 +01:00
2022-12-10 16:50:59 +01:00
#help
2022-12-10 15:54:31 +01:00
#version
match arg {
lexopt::Arg::Short(short) => { #short },
lexopt::Arg::Long(long) => { #long },
lexopt::Arg::Value(value) => { #positional },
}
2022-12-01 18:37:17 +01:00
}
2022-12-09 01:39:11 +01:00
fn check_missing(positional_idx: usize) -> Result<(), uutils_args::Error> {
2022-12-10 15:54:31 +01:00
#missing_argument_checks
}
fn help(bin_name: &str) -> ::std::io::Result<()> {
2022-12-10 16:50:59 +01:00
#help_string
2022-12-09 01:39:11 +01:00
}
fn version() -> String {
#version_string
}
2022-12-01 18:37:17 +01:00
}
);
TokenStream::from(expanded)
}
2023-02-14 00:51:27 +01:00
#[proc_macro_derive(Value, attributes(value))]
pub fn value(input: TokenStream) -> TokenStream {
2022-11-27 22:48:05 +01:00
let input = parse_macro_input!(input as DeriveInput);
2022-11-27 16:33:09 +01:00
2022-11-27 22:48:05 +01:00
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let Enum(data) = input.data else {
2022-12-01 18:37:17 +01:00
panic!("Input should be an enum!");
2022-11-27 22:48:05 +01:00
};
let mut options = Vec::new();
2022-11-27 22:48:05 +01:00
let mut match_arms = vec![];
for variant in data.variants {
let variant_name = variant.ident.to_string();
let attrs = variant.attrs.clone();
for attr in attrs {
2023-06-05 01:14:44 +02:00
if !attr.path().is_ident("value") {
2022-11-27 22:48:05 +01:00
continue;
2022-11-27 16:33:09 +01:00
}
2022-11-27 18:41:14 +01:00
2023-06-06 17:26:10 +02:00
let ValueAttr { keys, value } = ValueAttr::parse(&attr).unwrap();
2022-11-27 22:48:05 +01:00
let keys = if keys.is_empty() {
vec![variant_name.to_lowercase()]
} else {
keys
2022-11-27 18:41:14 +01:00
};
2022-11-27 22:48:05 +01:00
options.push(quote!(&[#(#keys),*]));
2022-11-27 22:48:05 +01:00
let stmt = if let Some(v) = value {
quote!(#(| #keys)* => #v)
} else {
let mut v = variant.clone();
v.attrs = vec![];
quote!(#(| #keys)* => Self::#v)
};
match_arms.push(stmt);
2022-11-27 18:41:14 +01:00
}
2022-11-27 22:48:05 +01:00
}
let expanded = quote!(
2023-02-14 00:51:27 +01:00
impl #impl_generics Value for #name #ty_generics #where_clause {
fn from_value(value: &::std::ffi::OsStr) -> ::uutils_args::ValueResult<Self> {
let value = String::from_value(value)?;
let options: &[&[&str]] = &[#(#options),*];
let mut candidates: Vec<&str> = Vec::new();
let mut exact_match: Option<&str> = None;
'outer: for &opt in options {
'inner: for &o in opt {
if value == o {
exact_match = Some(o);
break 'outer;
} else if o.starts_with(&value) {
candidates.push(o);
break 'inner;
}
2022-11-27 22:48:05 +01:00
}
}
let opt = match (exact_match, &candidates[..]) {
(Some(opt), _) => opt,
(None, [opt]) => opt,
2023-02-14 00:51:27 +01:00
(None, []) => return Err("Invalid value".into()),
(None, opts) => return Err(uutils_args::ValueError::AmbiguousValue {
value,
candidates: candidates.iter().map(|s| s.to_string()).collect(),
2023-02-14 00:51:27 +01:00
}.into())
};
Ok(match opt {
#(#match_arms),*,
_ => unreachable!("Should be caught by (None, []) case above.")
2022-11-27 22:48:05 +01:00
})
}
}
);
TokenStream::from(expanded)
}