diff --git a/README.md b/README.md index 3a37a6898..60d3c7da8 100644 --- a/README.md +++ b/README.md @@ -175,10 +175,9 @@ impl Iden for Character { ``` If you're okay with running another procedural macro, you can activate -the `derive` or `attr` feature on the crate to save you some boilerplate. +the `derive` feature on the crate to save you some boilerplate. For more usage information, look at -[the derive examples](https://github.com/SeaQL/sea-query/tree/master/sea-query-derive/tests/pass) -or [the attribute examples](https://github.com/SeaQL/sea-query/tree/master/sea-query-attr/tests/pass). +[the derive examples](https://github.com/SeaQL/sea-query/tree/master/sea-query-derive/tests/pass). ```rust #[cfg(feature = "derive")] @@ -198,7 +197,7 @@ assert_eq!(Glyph.to_string(), "glyph"); ``` ```rust -#[cfg(feature = "attr")] +#[cfg(feature = "derive")] use sea_query::{enum_def, Iden}; #[enum_def] diff --git a/sea-query-attr/src/lib.rs b/sea-query-attr/src/lib.rs index 96f1396e9..143e2c7aa 100644 --- a/sea-query-attr/src/lib.rs +++ b/sea-query-attr/src/lib.rs @@ -38,6 +38,7 @@ impl Default for GenEnumArgs { } } +#[deprecated(since = "0.1.2", note = "use #[enum_def] attr defined in `sea-query-derive` crate")] #[proc_macro_attribute] pub fn enum_def(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as AttributeArgs); @@ -84,7 +85,6 @@ pub fn enum_def(args: TokenStream, input: TokenStream) -> TokenStream { let pascal_def_names = field_names.iter().map(|field| &field.pascal); let pascal_def_names2 = pascal_def_names.clone(); let default_names = field_names.iter().map(|field| &field.default); - let default_names2 = default_names.clone(); let import_name = Ident::new( args.crate_name .unwrap_or_else(|| DEFAULT_CRATE_NAME.to_string()) @@ -101,24 +101,12 @@ pub fn enum_def(args: TokenStream, input: TokenStream) -> TokenStream { #(#pascal_def_names,)* } - impl #import_name::IdenStatic for #enum_name { - fn as_str(&self) -> &'static str { - match self { - #enum_name::Table => stringify!(#table_name), - #(#enum_name::#pascal_def_names2 => stringify!(#default_names2)),* - } - } - } - impl #import_name::Iden for #enum_name { fn unquoted(&self, s: &mut dyn sea_query::Write) { - write!(s, "{}", ::as_str(&self)).unwrap(); - } - } - - impl ::std::convert::AsRef for #enum_name { - fn as_ref(&self) -> &str { - ::as_str(&self) + write!(s, "{}", match self { + #enum_name::Table => stringify!(#table_name), + #(#enum_name::#pascal_def_names2 => stringify!(#default_names)),* + }).unwrap(); } } }) diff --git a/sea-query-attr/tests/pass/table_iden.rs b/sea-query-attr/tests/pass/table_iden.rs index 16580e4f5..0c102dc48 100644 --- a/sea-query-attr/tests/pass/table_iden.rs +++ b/sea-query-attr/tests/pass/table_iden.rs @@ -1,6 +1,5 @@ -use sea_query::{Iden, IdenStatic}; +use sea_query::Iden; use sea_query_attr::enum_def; -use std::convert::AsRef; #[enum_def(table_name = "HelloTable")] pub struct Hello { @@ -9,11 +8,4 @@ pub struct Hello { fn main() { assert_eq!("HelloTable".to_string(), HelloIden::Table.to_string()); - assert_eq!("name".to_string(), HelloIden::Name.to_string()); - - assert_eq!("HelloTable", HelloIden::Table.as_str()); - assert_eq!("name", HelloIden::Name.as_str()); - - assert_eq!("HelloTable", AsRef::::as_ref(&HelloIden::Table)); - assert_eq!("name", AsRef::::as_ref(&HelloIden::Name)); } diff --git a/sea-query-derive/Cargo.toml b/sea-query-derive/Cargo.toml index ce06ff545..b86bed23c 100644 --- a/sea-query-derive/Cargo.toml +++ b/sea-query-derive/Cargo.toml @@ -18,6 +18,7 @@ proc-macro = true syn = { version = "2", default-features = false, features = ["parsing", "proc-macro", "derive", "printing"] } quote = { version = "1", default-features = false } heck = { version = "0.4", default-features = false } +darling = { version = "0.20", default-features = false } proc-macro2 = { version = "1", default-features = false } thiserror = { version = "1.0", default-features = false } diff --git a/sea-query-derive/src/iden_attr.rs b/sea-query-derive/src/iden/attr.rs similarity index 98% rename from sea-query-derive/src/iden_attr.rs rename to sea-query-derive/src/iden/attr.rs index e07b00553..a97dc1990 100644 --- a/sea-query-derive/src/iden_attr.rs +++ b/sea-query-derive/src/iden/attr.rs @@ -3,7 +3,7 @@ use std::convert::{TryFrom, TryInto}; use syn::spanned::Spanned; use syn::{Attribute, Error, Expr, ExprLit, Ident, Lit, LitStr, Meta}; -use crate::{error::ErrorMsg, iden_path::IdenPath}; +use super::{error::ErrorMsg, path::IdenPath}; #[derive(PartialEq, Eq)] pub(crate) enum IdenAttr { diff --git a/sea-query-derive/src/error.rs b/sea-query-derive/src/iden/error.rs similarity index 96% rename from sea-query-derive/src/error.rs rename to sea-query-derive/src/iden/error.rs index b9bf65717..a03a1792d 100644 --- a/sea-query-derive/src/error.rs +++ b/sea-query-derive/src/iden/error.rs @@ -1,6 +1,6 @@ use syn::Ident; -use crate::iden_path::IdenPath; +use super::path::IdenPath; #[derive(Debug, thiserror::Error)] pub enum ErrorMsg { diff --git a/sea-query-derive/src/iden/mod.rs b/sea-query-derive/src/iden/mod.rs new file mode 100644 index 000000000..9dc01a003 --- /dev/null +++ b/sea-query-derive/src/iden/mod.rs @@ -0,0 +1,34 @@ +pub(crate) mod attr; +pub(crate) mod error; +pub(crate) mod path; +pub(crate) mod write_arm; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::Ident; + +use self::write_arm::WriteArm; + +pub(crate) struct DeriveIden; + +impl WriteArm for DeriveIden { + fn variant(variant: TokenStream2, name: TokenStream2) -> TokenStream2 { + quote! { Self::#variant => write!(s, "{}", #name).unwrap() } + } + + fn flattened(variant: TokenStream2, name: &Ident) -> TokenStream2 { + quote! { Self::#variant => #name.unquoted(s) } + } +} + +pub(crate) struct DeriveIdenStatic; + +impl WriteArm for DeriveIdenStatic { + fn variant(variant: TokenStream2, name: TokenStream2) -> TokenStream2 { + quote! { Self::#variant => #name } + } + + fn flattened(variant: TokenStream2, name: &Ident) -> TokenStream2 { + quote! { Self::#variant => #name.as_str() } + } +} diff --git a/sea-query-derive/src/iden_path.rs b/sea-query-derive/src/iden/path.rs similarity index 100% rename from sea-query-derive/src/iden_path.rs rename to sea-query-derive/src/iden/path.rs diff --git a/sea-query-derive/src/iden_variant.rs b/sea-query-derive/src/iden/write_arm.rs similarity index 86% rename from sea-query-derive/src/iden_variant.rs rename to sea-query-derive/src/iden/write_arm.rs index 684856139..a318ae520 100644 --- a/sea-query-derive/src/iden_variant.rs +++ b/sea-query-derive/src/iden/write_arm.rs @@ -6,37 +6,13 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{Error, Fields, FieldsNamed, Ident, Variant}; -use crate::{error::ErrorMsg, find_attr, iden_attr::IdenAttr, must_be_valid_iden}; +use super::{attr::IdenAttr, error::ErrorMsg}; +use crate::{find_attr, must_be_valid_iden}; pub(crate) trait WriteArm { fn variant(variant: TokenStream, name: TokenStream) -> TokenStream; fn flattened(variant: TokenStream, name: &Ident) -> TokenStream; } - -pub(crate) struct DeriveIden; - -impl WriteArm for DeriveIden { - fn variant(variant: TokenStream, name: TokenStream) -> TokenStream { - quote! { Self::#variant => write!(s, "{}", #name).unwrap() } - } - - fn flattened(variant: TokenStream, name: &Ident) -> TokenStream { - quote! { Self::#variant => #name.unquoted(s) } - } -} - -pub(crate) struct DeriveIdenStatic; - -impl WriteArm for DeriveIdenStatic { - fn variant(variant: TokenStream, name: TokenStream) -> TokenStream { - quote! { Self::#variant => #name } - } - - fn flattened(variant: TokenStream, name: &Ident) -> TokenStream { - quote! { Self::#variant => #name.as_str() } - } -} - pub(crate) struct IdenVariant<'a, T> { ident: &'a Ident, fields: &'a Fields, @@ -183,7 +159,7 @@ where T::variant(variant, name) } - pub(super) fn must_be_valid_iden(&self) -> bool { + pub(crate) fn must_be_valid_iden(&self) -> bool { let name: String = match &self.attr { Some(a) => match a { IdenAttr::Rename(name) => name.to_owned(), diff --git a/sea-query-derive/src/lib.rs b/sea-query-derive/src/lib.rs index 30676b555..370f07c01 100644 --- a/sea-query-derive/src/lib.rs +++ b/sea-query-derive/src/lib.rs @@ -1,22 +1,137 @@ use std::convert::{TryFrom, TryInto}; -use heck::ToSnakeCase; +use darling::FromMeta; +use heck::{ToPascalCase, ToSnakeCase}; use proc_macro::{self, TokenStream}; use quote::{quote, quote_spanned}; -use syn::{parse_macro_input, Attribute, DataEnum, DataStruct, DeriveInput, Fields, Variant}; - -mod error; -mod iden_attr; -mod iden_path; -mod iden_variant; - -use self::{ - error::ErrorMsg, - iden_attr::IdenAttr, - iden_path::IdenPath, - iden_variant::{DeriveIden, DeriveIdenStatic, IdenVariant}, +use syn::{ + parse_macro_input, spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, + Fields, Ident, Variant, }; +mod iden; + +use self::iden::{ + attr::IdenAttr, error::ErrorMsg, path::IdenPath, write_arm::IdenVariant, DeriveIden, + DeriveIdenStatic, +}; + +#[proc_macro_derive(Iden, attributes(iden, method))] +pub fn derive_iden(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, data, attrs, .. + } = parse_macro_input!(input); + let table_name = match get_table_name(&ident, attrs) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + + // Currently we only support enums and unit structs + let variants = + match data { + syn::Data::Enum(DataEnum { variants, .. }) => variants, + syn::Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => return impl_iden_for_unit_struct(&ident, &table_name).into(), + _ => return quote_spanned! { + ident.span() => compile_error!("you can only derive Iden on enums or unit structs"); + } + .into(), + }; + + if variants.is_empty() { + return TokenStream::new(); + } + + let output = impl_iden_for_enum(&ident, &table_name, variants.iter()); + + output.into() +} + +#[proc_macro_derive(IdenStatic, attributes(iden, method))] +pub fn derive_iden_static(input: TokenStream) -> TokenStream { + let sea_query_path = sea_query_path(); + + let DeriveInput { + ident, data, attrs, .. + } = parse_macro_input!(input); + + let table_name = match get_table_name(&ident, attrs) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + + // Currently we only support enums and unit structs + let variants = + match data { + syn::Data::Enum(DataEnum { variants, .. }) => variants, + syn::Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => { + let impl_iden = impl_iden_for_unit_struct(&ident, &table_name); + + return quote! { + #impl_iden + + impl #sea_query_path::IdenStatic for #ident { + fn as_str(&self) -> &'static str { + #table_name + } + } + + impl std::convert::AsRef for #ident { + fn as_ref(&self) -> &str { + self.as_str() + } + } + } + .into(); + } + _ => return quote_spanned! { + ident.span() => compile_error!("you can only derive Iden on enums or unit structs"); + } + .into(), + }; + + if variants.is_empty() { + return TokenStream::new(); + } + + let impl_iden = impl_iden_for_enum(&ident, &table_name, variants.iter()); + + let match_arms = match variants + .iter() + .map(|v| (table_name.as_str(), v)) + .map(IdenVariant::::try_from) + .collect::>>() + { + Ok(v) => quote! { #(#v),* }, + Err(e) => return e.to_compile_error().into(), + }; + + let output = quote! { + #impl_iden + + impl #sea_query_path::IdenStatic for #ident { + fn as_str(&self) -> &'static str { + match self { + #match_arms + } + } + } + + impl std::convert::AsRef for #ident { + fn as_ref(&self) -> &'static str { + self.as_str() + } + } + }; + + output.into() +} + fn find_attr(attrs: &[Attribute]) -> Option<&Attribute> { attrs.iter().find(|attr| { attr.path().is_ident(&IdenPath::Iden) || attr.path().is_ident(&IdenPath::Method) @@ -121,126 +236,139 @@ where } } -#[proc_macro_derive(Iden, attributes(iden, method))] -pub fn derive_iden(input: TokenStream) -> TokenStream { - let DeriveInput { - ident, data, attrs, .. - } = parse_macro_input!(input); - let table_name = match get_table_name(&ident, attrs) { - Ok(v) => v, - Err(e) => return e.to_compile_error().into(), - }; - - // Currently we only support enums and unit structs - let variants = - match data { - syn::Data::Enum(DataEnum { variants, .. }) => variants, - syn::Data::Struct(DataStruct { - fields: Fields::Unit, - .. - }) => return impl_iden_for_unit_struct(&ident, &table_name).into(), - _ => return quote_spanned! { - ident.span() => compile_error!("you can only derive Iden on enums or unit structs"); - } - .into(), - }; - - if variants.is_empty() { - return TokenStream::new(); +fn sea_query_path() -> proc_macro2::TokenStream { + if cfg!(feature = "sea-orm") { + quote!(sea_orm::sea_query) + } else { + quote!(sea_query) } +} - let output = impl_iden_for_enum(&ident, &table_name, variants.iter()); - - output.into() +struct NamingHolder { + pub default: Ident, + pub pascal: Ident, } -#[proc_macro_derive(IdenStatic, attributes(iden, method))] -pub fn derive_iden_static(input: TokenStream) -> TokenStream { - let sea_query_path = sea_query_path(); +#[derive(Debug, FromMeta)] +struct GenEnumArgs { + #[darling(default)] + pub prefix: Option, + #[darling(default)] + pub suffix: Option, + #[darling(default)] + pub crate_name: Option, + #[darling(default)] + pub table_name: Option, +} - let DeriveInput { - ident, data, attrs, .. - } = parse_macro_input!(input); +const DEFAULT_PREFIX: &str = ""; +const DEFAULT_SUFFIX: &str = "Iden"; +const DEFAULT_CRATE_NAME: &str = "sea_query"; + +impl Default for GenEnumArgs { + fn default() -> Self { + Self { + prefix: Some(DEFAULT_PREFIX.to_string()), + suffix: Some(DEFAULT_SUFFIX.to_string()), + crate_name: Some(DEFAULT_CRATE_NAME.to_string()), + table_name: None, + } + } +} - let table_name = match get_table_name(&ident, attrs) { +#[proc_macro_attribute] +pub fn enum_def(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = match darling::ast::NestedMeta::parse_meta_list(args.into()) { Ok(v) => v, - Err(e) => return e.to_compile_error().into(), + Err(e) => { + return TokenStream::from(darling::Error::from(e).write_errors()); + } }; + let input = parse_macro_input!(input as DeriveInput); - // Currently we only support enums and unit structs - let variants = - match data { - syn::Data::Enum(DataEnum { variants, .. }) => variants, - syn::Data::Struct(DataStruct { - fields: Fields::Unit, - .. - }) => { - let impl_iden = impl_iden_for_unit_struct(&ident, &table_name); - - return quote! { - #impl_iden - - impl #sea_query_path::IdenStatic for #ident { - fn as_str(&self) -> &'static str { - #table_name - } - } - - impl std::convert::AsRef for #ident { - fn as_ref(&self) -> &str { - self.as_str() - } - } - } - .into(); - } - _ => return quote_spanned! { - ident.span() => compile_error!("you can only derive Iden on enums or unit structs"); - } - .into(), - }; - - if variants.is_empty() { - return TokenStream::new(); - } - - let impl_iden = impl_iden_for_enum(&ident, &table_name, variants.iter()); + let args = match GenEnumArgs::from_list(&attr_args) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(e.write_errors()); + } + }; - let match_arms = match variants - .iter() - .map(|v| (table_name.as_str(), v)) - .map(IdenVariant::::try_from) - .collect::>>() - { - Ok(v) => quote! { #(#v),* }, - Err(e) => return e.to_compile_error().into(), + let fields = match &input.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => &fields.named, + _ => panic!("#[enum_def] can only be used on structs"), }; - let output = quote! { - #impl_iden + let field_names: Vec = fields + .iter() + .map(|field| { + let ident = &field.ident; + let string = ident + .as_ref() + .expect("#[enum_def] can only be used on structs with named fields") + .to_string(); + let as_pascal = string.to_pascal_case(); + NamingHolder { + default: ident.as_ref().unwrap().clone(), + pascal: Ident::new(as_pascal.as_str(), ident.span()), + } + }) + .collect(); + + let table_name = Ident::new( + args.table_name + .unwrap_or_else(|| input.ident.to_string().to_snake_case()) + .as_str(), + input.ident.span(), + ); + + let enum_name = quote::format_ident!( + "{}{}{}", + args.prefix.unwrap_or_else(|| DEFAULT_PREFIX.to_string()), + &input.ident, + args.suffix.unwrap_or_else(|| DEFAULT_SUFFIX.to_string()) + ); + let pascal_def_names = field_names.iter().map(|field| &field.pascal); + let pascal_def_names2 = pascal_def_names.clone(); + let default_names = field_names.iter().map(|field| &field.default); + let default_names2 = default_names.clone(); + let import_name = Ident::new( + args.crate_name + .unwrap_or_else(|| DEFAULT_CRATE_NAME.to_string()) + .as_str(), + input.span(), + ); + + TokenStream::from(quote::quote! { + #input + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum #enum_name { + Table, + #(#pascal_def_names,)* + } - impl #sea_query_path::IdenStatic for #ident { + impl #import_name::IdenStatic for #enum_name { fn as_str(&self) -> &'static str { match self { - #match_arms + #enum_name::Table => stringify!(#table_name), + #(#enum_name::#pascal_def_names2 => stringify!(#default_names2)),* } } } - impl std::convert::AsRef for #ident { - fn as_ref(&self) -> &'static str { - self.as_str() + impl #import_name::Iden for #enum_name { + fn unquoted(&self, s: &mut dyn sea_query::Write) { + write!(s, "{}", ::as_str(&self)).unwrap(); } } - }; - - output.into() -} -fn sea_query_path() -> proc_macro2::TokenStream { - if cfg!(feature = "sea-orm") { - quote!(sea_orm::sea_query) - } else { - quote!(sea_query) - } + impl ::std::convert::AsRef for #enum_name { + fn as_ref(&self) -> &str { + ::as_str(&self) + } + } + }) } diff --git a/sea-query-derive/tests/pass/enum_def/default.rs b/sea-query-derive/tests/pass/enum_def/default.rs new file mode 100644 index 000000000..ec43784bb --- /dev/null +++ b/sea-query-derive/tests/pass/enum_def/default.rs @@ -0,0 +1,10 @@ +use sea_query_attr::enum_def; + +#[enum_def] +pub struct Hello { + pub name: String +} + +fn main() { + println!("{:?}", HelloIden::Name); +} \ No newline at end of file diff --git a/sea-query-derive/tests/pass/enum_def/prefixed.rs b/sea-query-derive/tests/pass/enum_def/prefixed.rs new file mode 100644 index 000000000..a571bbfce --- /dev/null +++ b/sea-query-derive/tests/pass/enum_def/prefixed.rs @@ -0,0 +1,10 @@ +use sea_query_attr::enum_def; + +#[enum_def(prefix = "Enum", suffix = "")] +pub struct Hello { + pub name: String +} + +fn main() { + println!("{:?}", EnumHello::Name); +} \ No newline at end of file diff --git a/sea-query-derive/tests/pass/enum_def/suffixed.rs b/sea-query-derive/tests/pass/enum_def/suffixed.rs new file mode 100644 index 000000000..675b53805 --- /dev/null +++ b/sea-query-derive/tests/pass/enum_def/suffixed.rs @@ -0,0 +1,10 @@ +use sea_query_attr::enum_def; + +#[enum_def(prefix = "", suffix = "Def")] +pub struct Hello { + pub name: String +} + +fn main() { + println!("{:?}", HelloDef::Name); +} \ No newline at end of file diff --git a/sea-query-derive/tests/pass/enum_def/table_iden.rs b/sea-query-derive/tests/pass/enum_def/table_iden.rs new file mode 100644 index 000000000..16580e4f5 --- /dev/null +++ b/sea-query-derive/tests/pass/enum_def/table_iden.rs @@ -0,0 +1,19 @@ +use sea_query::{Iden, IdenStatic}; +use sea_query_attr::enum_def; +use std::convert::AsRef; + +#[enum_def(table_name = "HelloTable")] +pub struct Hello { + pub name: String, +} + +fn main() { + assert_eq!("HelloTable".to_string(), HelloIden::Table.to_string()); + assert_eq!("name".to_string(), HelloIden::Name.to_string()); + + assert_eq!("HelloTable", HelloIden::Table.as_str()); + assert_eq!("name", HelloIden::Name.as_str()); + + assert_eq!("HelloTable", AsRef::::as_ref(&HelloIden::Table)); + assert_eq!("name", AsRef::::as_ref(&HelloIden::Name)); +} diff --git a/sea-query-derive/tests/test_build.rs b/sea-query-derive/tests/test_build.rs index 3552a13ff..3e9ecf134 100644 --- a/sea-query-derive/tests/test_build.rs +++ b/sea-query-derive/tests/test_build.rs @@ -5,5 +5,6 @@ fn build_tests() { // all of these are exactly the same as the examples in `examples/derive.rs` t.pass("./tests/pass/*.rs"); + t.pass("./tests/pass/enum_attr/*.rs"); t.pass("./tests/pass-static/*.rs"); }