Skip to content

Commit

Permalink
feat: added away to apply defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
NexRX committed Mar 11, 2024
1 parent da8d441 commit 5f50792
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 25 deletions.
13 changes: 10 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
let defaults = AttrArgsDefaults::parse(model_attr);

let views: Vec<proc_macro2::TokenStream> = 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<proc_macro2::TokenStream> = 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!(
Expand Down
119 changes: 103 additions & 16 deletions src/logic/args.rs
Original file line number Diff line number Diff line change
@@ -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<FieldsArg>,
pub derive: Option<Vec<syn::Path>>,
pub preset: Option<Preset>,
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<TokenTree> = attr
.meta
.require_list()
.unwrap()
.to_owned()
.tokens
.into_iter()
.collect::<Vec<_>>();
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,
Expand All @@ -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<TokenTree>) {
pub(crate) fn parse(
attr: &Attribute,
defaults: &AttrArgsDefaults,
abort_unexpected: bool,
) -> (Self, Vec<TokenTree>) {
let tks: Vec<TokenTree> = attr
.meta
.require_list()
Expand All @@ -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, &[])
}
Expand All @@ -66,8 +140,8 @@ impl AttrArgs {
name,
fields,
derive,
preset,
attributes_with
preset: preset.unwrap_or_default(),
attributes_with,
},
args,
)
Expand Down Expand Up @@ -100,14 +174,22 @@ 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"
),
}
}

/// 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),
Expand All @@ -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]
Expand Down Expand Up @@ -218,7 +306,6 @@ impl OptionType {
),
})
}

}

#[derive(Debug, Clone, Copy, Default)]
Expand Down
10 changes: 7 additions & 3 deletions src/patch.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use crate::logic::{
args::{AttrArgs, OptionType},
args::{AttrArgs, AttrArgsDefaults, OptionType},
*,
};
use proc_macro2::{Ident, TokenStream};
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: _,
Expand Down
7 changes: 4 additions & 3 deletions src/view.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use crate::logic::{args::AttrArgs, *};
use crate::logic::{args::{AttrArgs, AttrArgsDefaults}, *};
use proc_macro2::{Ident, TokenStream};
use proc_macro_error::abort;
use quote::quote;
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: _,
Expand Down
13 changes: 13 additions & 0 deletions tests/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
12 changes: 12 additions & 0 deletions tests/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down

0 comments on commit 5f50792

Please sign in to comment.