From 5f507924bf6c1cbf8b68963a80f21264fc807a6b Mon Sep 17 00:00:00 2001 From: Callum Cunha Date: Mon, 11 Mar 2024 10:24:14 +0000 Subject: [PATCH] feat: added away to apply defaults --- src/lib.rs | 13 +++-- src/logic/args.rs | 119 +++++++++++++++++++++++++++++++++++++++------- src/patch.rs | 10 ++-- src/view.rs | 7 +-- tests/patch.rs | 13 +++++ tests/view.rs | 12 +++++ 6 files changed, 149 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48990d0..404bc85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,26 +6,33 @@ mod patch; mod view; use crate::logic::is_attribute; +use logic::args::AttrArgsDefaults; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; #[proc_macro_error] -#[proc_macro_derive(Models, attributes(view, patch, clone))] +#[proc_macro_derive(Models, attributes(model, view, patch))] pub fn models(input: TokenStream) -> TokenStream { let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let model_attr = ast.attrs + .iter() + .filter(|v| is_attribute(v, "model")) + .collect::>(); + let defaults = AttrArgsDefaults::parse(model_attr); + let views: Vec = ast .attrs .iter() .filter(|v| is_attribute(v, "view")) - .map(|a| view::impl_view_model(&ast, a)) + .map(|a| view::impl_view_model(&ast, a, &defaults)) .collect(); let patches: Vec = ast .attrs .iter() .filter(|v| is_attribute(v, "patch")) - .map(|a| patch::impl_patch_model(&ast, a)) + .map(|a| patch::impl_patch_model(&ast, a, &defaults)) .collect(); let gen = quote::quote!( diff --git a/src/logic/args.rs b/src/logic/args.rs index ce1241f..96d918f 100644 --- a/src/logic/args.rs +++ b/src/logic/args.rs @@ -1,10 +1,71 @@ use super::{ - abort_unexpected_args, extract_idents, has_oai_attribute, take_ident_group, take_ident_ident, take_ident_literal, take_path_group + abort_unexpected_args, extract_idents, has_oai_attribute, take_ident_group, take_ident_ident, + take_ident_literal, take_path_group, }; use proc_macro2::{Ident, TokenTree}; use proc_macro_error::abort; use syn::{Attribute, Field}; +#[derive(Clone)] +pub(crate) struct AttrArgsDefaults { + pub fields: Option, + pub derive: Option>, + pub preset: Option, + pub attributes_with: AttributesWith, // Has it's own None +} + +impl AttrArgsDefaults { + /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) + pub(crate) fn parse(attr: Vec<&Attribute>) -> Self { + if attr.is_empty() { + return Self { + fields: None, + derive: None, + preset: None, + attributes_with: AttributesWith::None, + }; + } else if attr.len() > 1 { + abort!( + attr[1], + "Expected only one `model` attribute to derive defaults from." + ) + } + + let attr = attr.first().unwrap(); + + let mut tks: Vec = attr + .meta + .require_list() + .unwrap() + .to_owned() + .tokens + .into_iter() + .collect::>(); + let args = &mut tks; + + let fields = { + let fields = FieldsArg::parse(args, attr); + match fields.is_default() { + true => None, + false => Some(fields), + } + }; + let derive = take_path_group("derive", args); + let preset = Preset::parse(args); + let attributes_with = AttributesWith::parse(args).unwrap_or_else(|| match preset { + Some(p) => p.attr_with(), + None => AttributesWith::None, + }); + + Self { + fields, + derive, + preset, + attributes_with, + } + } +} + #[derive(Clone)] pub(crate) struct AttrArgs { pub name: Ident, @@ -24,7 +85,11 @@ impl AttrArgs { } /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) - pub(crate) fn parse(attr: &Attribute, abort_unexpected: bool) -> (Self, Vec) { + pub(crate) fn parse( + attr: &Attribute, + defaults: &AttrArgsDefaults, + abort_unexpected: bool, + ) -> (Self, Vec) { let tks: Vec = attr .meta .require_list() @@ -44,19 +109,28 @@ impl AttrArgs { } }; - if tks.len() < 3 { - abort!(attr, "Invalid syntax, expected at least one argument"); - } - - let mut args = tks[2..].to_vec(); + let mut args = match tks.len() < 3 { + true => vec![], + false => tks[2..].to_vec(), + }; let args_mr = &mut args; // Parse Expected Macro Args - let fields = FieldsArg::parse(args_mr, attr); - let derive = take_path_group("derive", args_mr); - let preset = Preset::parse(args_mr).unwrap_or_default(); - let attributes_with = AttributesWith::parse(args_mr).unwrap_or_else(|| preset.attr_with()); - + let fields = { + let fields = FieldsArg::parse(args_mr, attr); + match &defaults.fields { + Some(f) if fields.is_default() => f.clone(), + _ => fields, + } + }; + + let derive = take_path_group("derive", args_mr).or(defaults.derive.clone()); + let preset = Preset::parse(args_mr).or(defaults.preset); + let attributes_with = AttributesWith::parse(args_mr).unwrap_or_else(|| match &preset { + Some(preset) => preset.attr_with(), + _ => defaults.attributes_with, + }); + if abort_unexpected { Self::abort_unexpected(&args, &[]) } @@ -66,8 +140,8 @@ impl AttrArgs { name, fields, derive, - preset, - attributes_with + preset: preset.unwrap_or_default(), + attributes_with, }, args, ) @@ -100,7 +174,7 @@ impl FieldsArg { match (field_arg, omit_args) { (Some(g), None) => Self::Fields(extract_idents(g)), (None, Some(g)) => Self::Omit(extract_idents(g)), - (None, None) => Self::Omit(vec![]), + (None, None) => Self::default(), (Some(_), Some(_)) => abort!( attr_spanned, "Cannot have both `fields` and `omit` arguments" @@ -108,6 +182,14 @@ impl FieldsArg { } } + /// Similar to an is_empty function but only checks if omit is empty as thats the default case + pub(crate) fn is_default(&self) -> bool { + match self { + Self::Omit(fields) => fields.is_empty(), + _ => false, + } + } + pub(crate) fn predicate(&self, field: &Ident) -> bool { match self { Self::Fields(fields) => fields.contains(field), @@ -116,6 +198,12 @@ impl FieldsArg { } } +impl Default for FieldsArg { + fn default() -> Self { + Self::Omit(vec![]) + } +} + #[derive(Debug, Clone, Copy, Default)] pub(crate) enum AttributesWith { #[default] @@ -218,7 +306,6 @@ impl OptionType { ), }) } - } #[derive(Debug, Clone, Copy, Default)] diff --git a/src/patch.rs b/src/patch.rs index e51d3bd..d2b22ff 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -1,5 +1,5 @@ use crate::logic::{ - args::{AttrArgs, OptionType}, + args::{AttrArgs, AttrArgsDefaults, OptionType}, *, }; use proc_macro2::{Ident, TokenStream}; @@ -7,9 +7,13 @@ use proc_macro_error::abort; use quote::quote; use syn::{Attribute, DeriveInput, Type}; -pub fn impl_patch_model(ast: &DeriveInput, attr: &Attribute) -> TokenStream { +pub fn impl_patch_model( + ast: &DeriveInput, + attr: &Attribute, + defaults: &AttrArgsDefaults, +) -> TokenStream { // Argument and Variable Initialization and Prep - let (args, mut remainder) = AttrArgs::parse(attr, false); + let (args, mut remainder) = AttrArgs::parse(attr, defaults, false); let AttrArgs { name, fields: _, diff --git a/src/view.rs b/src/view.rs index 65be667..87c7eb3 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,4 +1,4 @@ -use crate::logic::{args::AttrArgs, *}; +use crate::logic::{args::{AttrArgs, AttrArgsDefaults}, *}; use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort; use quote::quote; @@ -6,10 +6,11 @@ use syn::{self, Attribute, DataEnum, DataStruct, DeriveInput}; pub fn impl_view_model( ast: &DeriveInput, - attr: &Attribute + attr: &Attribute, + defaults: &AttrArgsDefaults ) -> TokenStream { // Argument and Variable Initialization and Prep - let (args, _) = AttrArgs::parse(attr, true); + let (args, _) = AttrArgs::parse(attr, defaults, true); let AttrArgs { name, fields: _, diff --git a/tests/patch.rs b/tests/patch.rs index ed9d9e0..b36d8d4 100644 --- a/tests/patch.rs +++ b/tests/patch.rs @@ -64,6 +64,19 @@ impl UserAlt { } } +//------------------ Structs -- defaults + +#[derive(Models)] +#[model(fields(display_name, bio), attributes_with = "none")] +#[patch(UserProfileDefaults)] +struct UserDefaults{ + id: i32, + display_name: String, + bio: String, + password: String, +} + + #[test] fn alt_omitted_only() { let user = UserAlt::new(); diff --git a/tests/view.rs b/tests/view.rs index f10485e..87e6f22 100644 --- a/tests/view.rs +++ b/tests/view.rs @@ -89,6 +89,18 @@ struct UserAttrNone{ password: String, } +//------------------ Structs -- defaults + +#[derive(Models)] +#[model(fields(display_name, bio), attributes_with = "none")] +#[view(UserProfileDefaults)] +struct UserDefaults{ + id: i32, + display_name: String, + bio: String, + password: String, +} + //------------------ Enums #[derive(Debug, Clone, Models)]