Rework initial macro (part of #20)

This commit is contained in:
Terts Diepraam
2023-03-08 15:12:19 +01:00
parent b0489406ed
commit bf4bac54b7
10 changed files with 143 additions and 115 deletions
+1 -24
View File
@@ -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");
}
}
-43
View File
@@ -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()
}
+127
View File
@@ -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
View File
@@ -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))]
+1 -1
View File
@@ -23,7 +23,7 @@ enum Arg {
#[derive(Initial)]
struct Settings {
name: String,
#[field(default = 1)]
#[initial(1)]
count: u8,
}
+3
View File
@@ -27,3 +27,6 @@ mod ls;
#[path = "coreutils/tail.rs"]
mod tail;
#[path = "coreutils/uniq.rs"]
mod uniq;
+1 -1
View File
@@ -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>,
}
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -170,7 +170,7 @@ fn color() {
#[derive(Initial)]
struct Settings {
#[field(default = Color::Auto)]
#[initial(Color::Auto)]
color: Color,
}