mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Rework initial macro (part of #20)
This commit is contained in:
@@ -30,7 +30,6 @@ enum AttributeArguments {
|
||||
Value(Expr),
|
||||
NumArgs(RangeInclusive<usize>),
|
||||
File(String),
|
||||
Env(String),
|
||||
ExitCode(i32),
|
||||
Help(String),
|
||||
HelpFlags(Vec<String>),
|
||||
@@ -123,28 +122,6 @@ impl OptionAttr {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FieldAttr {
|
||||
pub(crate) default: Option<Expr>,
|
||||
pub(crate) env: Option<String>,
|
||||
}
|
||||
|
||||
impl FieldAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut field_attr = Self::default();
|
||||
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::Default(e) => field_attr.default = Some(e),
|
||||
AttributeArguments::Env(e) => field_attr.env = Some(e),
|
||||
_ => panic!("Invalid argument"),
|
||||
};
|
||||
}
|
||||
|
||||
field_attr
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ValueAttr {
|
||||
pub(crate) keys: Vec<String>,
|
||||
@@ -265,7 +242,6 @@ impl Parse for AttributeArguments {
|
||||
"default" => return Ok(Self::Default(input.parse::<Expr>()?)),
|
||||
"value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
"file" => return Ok(Self::File(input.parse::<LitStr>()?.value())),
|
||||
"env" => return Ok(Self::Env(input.parse::<LitStr>()?.value())),
|
||||
"help" => return Ok(Self::Help(input.parse::<LitStr>()?.value())),
|
||||
"exit_code" => return Ok(Self::ExitCode(input.parse::<LitInt>()?.base10_parse()?)),
|
||||
"help_flags" => {
|
||||
@@ -313,6 +289,7 @@ impl Parse for AttributeArguments {
|
||||
_ => panic!("Unrecognized argument {name} for option attribute"),
|
||||
};
|
||||
}
|
||||
|
||||
panic!("Arguments to option attribute must be string literals");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Attribute, Field, Ident};
|
||||
|
||||
use crate::attributes::FieldAttr;
|
||||
|
||||
pub(crate) struct FieldData {
|
||||
pub(crate) ident: Ident,
|
||||
pub(crate) default_value: TokenStream,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_field(field: &Field) -> FieldData {
|
||||
let field_ident = field.ident.as_ref().unwrap().clone();
|
||||
|
||||
let field_attr = parse_field_attr(&field.attrs);
|
||||
|
||||
let mut default_value = match field_attr.default {
|
||||
Some(val) => val.to_token_stream(),
|
||||
None => quote!(::core::default::Default::default()),
|
||||
};
|
||||
|
||||
if let Some(env_var) = field_attr.env {
|
||||
default_value = quote!(
|
||||
::std::env::var_os(#env_var)
|
||||
.and_then(|v| ::uutils_args::Value::from_value(&v).ok())
|
||||
.unwrap_or(#default_value)
|
||||
)
|
||||
}
|
||||
|
||||
FieldData {
|
||||
ident: field_ident,
|
||||
default_value,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_field_attr(attrs: &[Attribute]) -> FieldAttr {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("field") {
|
||||
return FieldAttr::parse(attr);
|
||||
}
|
||||
}
|
||||
FieldAttr::default()
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
use syn::{
|
||||
parse_macro_input,
|
||||
Data::Struct,
|
||||
DeriveInput, Fields, parse::{ParseStream, Parse}, Token,
|
||||
};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Attribute, Expr, LitStr, punctuated::Punctuated};
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(env);
|
||||
}
|
||||
|
||||
enum InitialArg {
|
||||
Expr(Expr),
|
||||
Env(String),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InitialField {
|
||||
expr: Option<syn::Expr>,
|
||||
env: Option<String>,
|
||||
}
|
||||
|
||||
impl Parse for InitialArg {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(kw::env) && input.peek2(Token![=]) {
|
||||
input.parse::<kw::env>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
Ok(InitialArg::Env(input.parse::<LitStr>()?.value()))
|
||||
} else {
|
||||
Ok(InitialArg::Expr(input.parse::<Expr>()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InitialField {
|
||||
fn from_attribute(attribute: &Attribute) -> syn::Result<Self> {
|
||||
let mut _self = Self::default();
|
||||
|
||||
let args = attribute.parse_args_with(Punctuated::<InitialArg, Token![,]>::parse_terminated)?;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
InitialArg::Expr(e) => {
|
||||
if _self.expr.is_some() {
|
||||
panic!("Can only specify one initial expression")
|
||||
}
|
||||
_self.expr = Some(e);
|
||||
}
|
||||
InitialArg::Env(s) => {
|
||||
if _self.expr.is_some() {
|
||||
panic!("Can only specify one env variable")
|
||||
}
|
||||
_self.env = Some(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(_self)
|
||||
}
|
||||
|
||||
fn to_expr(self) -> proc_macro2::TokenStream {
|
||||
let mut default_value = match self.expr {
|
||||
Some(val) => quote!(#val.into()),
|
||||
None => quote!(::core::default::Default::default()),
|
||||
};
|
||||
|
||||
if let Some(env_var) = self.env {
|
||||
default_value = quote!(
|
||||
::std::env::var_os(#env_var)
|
||||
.and_then(|v| ::uutils_args::Value::from_value(&v).ok())
|
||||
.unwrap_or(#default_value)
|
||||
);
|
||||
}
|
||||
default_value.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initial(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 Struct(data) = input.data else {
|
||||
panic!("Input should be a struct!");
|
||||
};
|
||||
|
||||
let Fields::Named(fields) = data.fields else {
|
||||
panic!("Fields must be named");
|
||||
};
|
||||
|
||||
// The key of this map is a literal pattern and the value
|
||||
// is whatever code needs to be run when that pattern is encountered.
|
||||
let mut defaults = Vec::new();
|
||||
for field in fields.named {
|
||||
let ident = field.ident;
|
||||
let field = parse_field_attr(&field.attrs);
|
||||
let default_value = field.to_expr();
|
||||
|
||||
defaults.push(quote!(#ident: #default_value));
|
||||
}
|
||||
|
||||
let expanded = quote!(
|
||||
impl #impl_generics Initial for #name #ty_generics #where_clause {
|
||||
fn initial() -> Self {
|
||||
Self {
|
||||
#(#defaults),*
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn parse_field_attr(attrs: &[Attribute]) -> InitialField {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("initial") {
|
||||
return InitialField::from_attribute(attr).expect("Failed to parse initial attribute");
|
||||
}
|
||||
}
|
||||
InitialField::default()
|
||||
}
|
||||
|
||||
+5
-41
@@ -1,64 +1,28 @@
|
||||
mod argument;
|
||||
mod attributes;
|
||||
mod field;
|
||||
mod flags;
|
||||
mod help;
|
||||
mod markdown;
|
||||
mod initial;
|
||||
|
||||
use argument::{
|
||||
long_handling, number_handling, parse_argument, parse_arguments_attr, positional_handling,
|
||||
short_handling,
|
||||
};
|
||||
use attributes::ValueAttr;
|
||||
use field::{parse_field, FieldData};
|
||||
use help::{help_handling, help_string, version_handling};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input,
|
||||
Data::{Enum, Struct},
|
||||
DeriveInput, Fields,
|
||||
Data::Enum,
|
||||
DeriveInput,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(Initial, attributes(field))]
|
||||
#[proc_macro_derive(Initial, attributes(initial))]
|
||||
pub fn initial(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 Struct(data) = input.data else {
|
||||
panic!("Input should be a struct!");
|
||||
};
|
||||
|
||||
let Fields::Named(fields) = data.fields else {
|
||||
panic!("Fields must be named");
|
||||
};
|
||||
|
||||
// The key of this map is a literal pattern and the value
|
||||
// is whatever code needs to be run when that pattern is encountered.
|
||||
let mut defaults = Vec::new();
|
||||
for field in fields.named {
|
||||
let FieldData {
|
||||
ident,
|
||||
default_value,
|
||||
} = parse_field(&field);
|
||||
|
||||
defaults.push(quote!(#ident: #default_value));
|
||||
}
|
||||
|
||||
let expanded = quote!(
|
||||
impl #impl_generics Initial for #name #ty_generics #where_clause {
|
||||
fn initial() -> Self {
|
||||
Self {
|
||||
#(#defaults),*
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
TokenStream::from(expanded)
|
||||
initial::initial(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Arguments, attributes(flag, option, positional, arguments))]
|
||||
|
||||
@@ -23,7 +23,7 @@ enum Arg {
|
||||
#[derive(Initial)]
|
||||
struct Settings {
|
||||
name: String,
|
||||
#[field(default = 1)]
|
||||
#[initial(1)]
|
||||
count: u8,
|
||||
}
|
||||
|
||||
|
||||
@@ -27,3 +27,6 @@ mod ls;
|
||||
|
||||
#[path = "coreutils/tail.rs"]
|
||||
mod tail;
|
||||
|
||||
#[path = "coreutils/uniq.rs"]
|
||||
mod uniq;
|
||||
|
||||
@@ -21,7 +21,7 @@ enum Arg {
|
||||
struct Settings {
|
||||
decode: bool,
|
||||
ignore_garbage: bool,
|
||||
#[field(default = Some(76))]
|
||||
#[initial(Some(76))]
|
||||
wrap: Option<usize>,
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -322,14 +322,14 @@ struct Settings {
|
||||
long_numeric_uid_gid: bool,
|
||||
// alloc_size: bool,
|
||||
// block_size: Option<u64>,
|
||||
#[field(default = default_terminal_size())]
|
||||
#[initial(default_terminal_size())]
|
||||
width: u16,
|
||||
quoting_style: QuotingStyle,
|
||||
indicator_style: IndicatorStyle,
|
||||
// time_style: TimeStyle,
|
||||
context: bool,
|
||||
group_directories_first: bool,
|
||||
#[field(default = '\n')]
|
||||
#[initial('\n')]
|
||||
eol: char,
|
||||
which_files: Files,
|
||||
ignore_backups: bool,
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ fn true_default() {
|
||||
|
||||
#[derive(Initial)]
|
||||
struct Settings {
|
||||
#[field(default = true)]
|
||||
#[initial(true)]
|
||||
foo: bool,
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ fn env_var_string() {
|
||||
|
||||
#[derive(Initial)]
|
||||
struct Settings {
|
||||
#[field(env = "FOO")]
|
||||
#[initial("FOO")]
|
||||
foo: String,
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -170,7 +170,7 @@ fn color() {
|
||||
|
||||
#[derive(Initial)]
|
||||
struct Settings {
|
||||
#[field(default = Color::Auto)]
|
||||
#[initial(Color::Auto)]
|
||||
color: Color,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user