use super::{
some_or_compile_err,
ok_or_compile_err,
quote_compile_err,
gen_doc_attrs,
ChainableOptions
};
use proc_macro2::TokenStream as TokenStream2;
use syn::{
parse::Parser,
parse_quote,
punctuated::Punctuated,
Attribute, Expr,
FnArg, Ident,
ImplItemFn,
ItemStruct,
Lit, Meta,
Token, Type
};
use quote::{
format_ident,
quote, ToTokens
};
use if_chain::if_chain;
#[derive(Clone, Default)]
struct GetterOptions {
enabled: bool,
enabled_mut: bool,
copy_val: bool,
doc_attrs: Vec<Attribute>
}
#[derive(Clone, Default)]
struct SetterOptions {
enabled: bool,
use_into_impl: bool,
doc_attrs: Vec<Attribute>
}
#[derive(Clone)]
struct EncapsulatedField {
name: Ident,
field_type: Type,
getter_opts: GetterOptions,
setter_opts: SetterOptions,
chainable_opts: Option<ChainableOptions>
}
impl EncapsulatedField {
fn new(name: Ident, field_type: Type) -> Self {
Self {
name,
field_type,
setter_opts: SetterOptions::default(),
getter_opts: GetterOptions::default(),
chainable_opts: None
}
}
}
pub fn encapsulated_struct(mut struct_def: ItemStruct) -> TokenStream2 {
let mut fields = Vec::new();
let mut methods: Vec<ImplItemFn> = Vec::new();
let vis = &struct_def.vis;
let type_name = &struct_def.ident;
let (impl_generics, ty_generics, where_clause) = struct_def.generics.split_for_impl();
for field in &mut struct_def.fields {
let name = some_or_compile_err!(field.ident.clone(), "Tuple structs are not supported");
let mut enc_field = EncapsulatedField::new(name, field.ty.clone());
let mut new_attrs = Vec::new();
let mut setter_doc = String::new();
for attr in field.attrs.drain(..) {
let mut my_attr = false;
if let Meta::Path(path) = &attr.meta {
let name = ok_or_compile_err!(path.require_ident());
if name == "getter" {
my_attr = true;
enc_field.getter_opts.enabled = true;
}
else if name == "setter" {
my_attr = true;
enc_field.setter_opts.enabled = true;
}
else if name == "chainable" {
my_attr = true;
let mut chainable_opts = ok_or_compile_err!(ChainableOptions::parse(true, TokenStream2::new()));
if chainable_opts.doc_attrs.is_empty() {
setter_doc.push_str("\n\nChainable variant");
chainable_opts.doc_attrs = gen_doc_attrs(&setter_doc);
}
enc_field.chainable_opts = Some(chainable_opts);
}
}
else if let Meta::List(list) = &attr.meta {
let name = ok_or_compile_err!(list.path.require_ident());
if name == "getter" {
enc_field.getter_opts.enabled = true;
my_attr = true;
let options = ok_or_compile_err!(<Punctuated<Meta, Token![,]>>::parse_terminated.parse2(list.tokens.clone()));
for option in options {
if let Meta::Path(path) = &option {
let name = ok_or_compile_err!(path.require_ident());
if name == "mutable" {
enc_field.getter_opts.enabled_mut = true;
}
else if name == "copy_val" {
enc_field.getter_opts.copy_val = true;
}
else {
return quote_compile_err!(
"Invalid option! Specify \"mutable\" to generate \
a mutable getter method, \"copy_val\" to return \
the value instead of a reference, or \"doc\" to generate documentation"
)
}
}
else if let Meta::NameValue(meta_val) = &option {
let name = ok_or_compile_err!(meta_val.path.require_ident());
if name == "doc" {
if_chain! {
if let Expr::Lit(literal) = &meta_val.value;
if let Lit::Str(str_val) = &literal.lit;
then {
enc_field.getter_opts.doc_attrs = gen_doc_attrs(str_val.value());
}
else {
return quote_compile_err!("Invalid input!");
}
}
}
else {
return quote_compile_err!(
"Invalid option! Specify \"mutable\" to generate \
a mutable getter method, \"copy_val\" to return \
the value instead of a reference, or \"doc\" to \
generate documentation"
)
}
}
else {
return quote_compile_err!("Invalid input!");
}
}
}
else if name == "setter" {
my_attr = true;
enc_field.setter_opts.enabled = true;
let options = ok_or_compile_err!(<Punctuated<Meta, Token![,]>>::parse_terminated.parse2(list.tokens.clone()));
for option in options {
if let Meta::Path(path) = &option {
let name = ok_or_compile_err!(path.require_ident());
if name == "use_into_impl" {
enc_field.setter_opts.use_into_impl = true;
}
else {
return quote_compile_err!(
"Invalid option! Specify \"use_into_impl\" \
to generate an Into implementation for the \
setter method"
);
}
}
else if let Meta::NameValue(meta_val) = &option {
let name = ok_or_compile_err!(meta_val.path.require_ident());
if name == "doc" {
if_chain! {
if let Expr::Lit(literal) = &meta_val.value;
if let Lit::Str(str_val) = &literal.lit;
then {
setter_doc = str_val.value();
enc_field.setter_opts.doc_attrs = gen_doc_attrs(str_val.value());
}
else {
return quote_compile_err!("Invalid input!");
}
}
}
else {
return quote_compile_err!(
"Invalid option! Specify \
\"use_into_impl\" to generate \
an Into implementation for \
the setter method or \"doc\" to \
generate documentation"
)
}
}
}
}
else if name == "chainable" {
my_attr = true;
let mut chainable_opts = ok_or_compile_err!(ChainableOptions::parse(true, list.tokens.clone()));
if chainable_opts.doc_attrs.is_empty() {
setter_doc.push_str("\n\nChainable variant");
chainable_opts.doc_attrs = gen_doc_attrs(&setter_doc);
}
enc_field.chainable_opts = Some(chainable_opts);
}
}
if !my_attr {
new_attrs.push(attr);
}
}
field.attrs = new_attrs;
fields.push(enc_field);
}
for field in &fields {
let name = &field.name;
let field_type = &field.field_type;
if field.setter_opts.enabled {
let fn_name = format_ident!("set_{name}");
let in_arg: FnArg = if field.setter_opts.use_into_impl {
parse_quote!(#name:impl core::convert::Into<#field_type>)
}
else {
parse_quote!(#name:#field_type)
};
let arg = if field.setter_opts.use_into_impl {
parse_quote!(#name.into())
}
else {
name.to_token_stream()
};
let setter_doc_attrs = &field.setter_opts.doc_attrs;
methods.push(
parse_quote! {
#(#setter_doc_attrs)*
#vis fn #fn_name(&mut self, #in_arg) {
self.#name = #arg;
}
}
);
}
if field.getter_opts.enabled {
let fn_name = format_ident!("get_{name}");
let getter_doc_attrs = &field.getter_opts.doc_attrs;
let ret_type: Type = if field.getter_opts.copy_val {
parse_quote!(#field_type)
}
else {
parse_quote!(&#field_type)
};
let ret_stmt: TokenStream2 = if field.getter_opts.copy_val {
parse_quote!(self.#name)
}
else {
parse_quote!(&self.#name)
};
methods.push(
parse_quote! {
#(#getter_doc_attrs)*
#vis fn #fn_name(&self) -> #ret_type {
#ret_stmt
}
}
);
}
if field.getter_opts.enabled_mut {
let fn_name = format_ident!("get_{name}_mut");
let getter_doc_attrs = &field.getter_opts.doc_attrs;
methods.push(
parse_quote! {
#(#getter_doc_attrs)*
#vis fn #fn_name(&mut self) -> &mut #field_type {
&mut self.#name
}
}
);
}
if let Some(chainable_opts) = &field.chainable_opts {
methods.push(chainable_opts.gen_method_from_field(name, field_type, vis))
}
}
quote! {
#struct_def
impl #impl_generics #type_name #ty_generics #where_clause {
#(#methods)*
}
}
}