Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,34 @@ let mut drink_kinds = HashSet::new();
drink_kinds.insert(DrinkKind::Mate);
```

#### Variant Attributes

Some traits require or allow additional configuration via attributes on variants.
These can be passed through using `#[kinded(...)]`.
You can only pass one attribute per `#[kinded(...)]`, though you can use multiple of these attributes per variant.

```rs
use kinded::Kinded;

#[derive(Kinded)]
#[kinded(derive(Default, Hash, Default))]
enum Drink {
#[kinded(default)]
#[kinded(doc = "Not suitable for small children. Please talk to your IT-person about the harmful effects of mate addiction.")]
Mate,
#[kinded(doc = "Not suitable for small children!")]
Coffee(String),
#[kinded(default)]
#[kinded(doc = "Only suitable for small children if caffeine-free.")]
Tea { variety: String, caffeine: bool }
}

// Now we can access default without having to implement it ourself.
// Also the drink kinds now have a small doc!
let default_drink_kind = DrinkKind::default();
```
This can also be used to configure derivations of [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) and [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) of [`serde`](https://docs.rs/serde/latest/serde/index.html) or
[`EnumMessage`](https://docs.rs/strum/latest/strum/trait.EnumMessage.html) from [`strum`](https://docs.rs/strum/latest/strum/) via `#[kinded(strum(message = "Important message"))]`
### Display trait

Implementation of `Display` trait can be customized in the `serde` fashion:
Expand Down
10 changes: 8 additions & 2 deletions kinded_macros/src/generate/kind_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ fn gen_definition(meta: &Meta) -> TokenStream {
let vis = &meta.vis;
let kind_name = meta.kind_name();
let variant_names: Vec<&Ident> = meta.variants.iter().map(|v| &v.ident).collect();
let variant_attr: Vec<&[TokenStream]> = meta
.variants
.iter()
.map(|v| v.kinded_variant_attrs.as_slice())
.collect();
let traits = meta.derive_traits();

quote!(
#[derive(#(#traits),*)] // #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(#(#traits),*)] // #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#vis enum #kind_name { // pub enum DrinkKind {
#(#variant_names),* // Mate, Coffee, Tea
#( #(#[#variant_attr])* //
#variant_names),* // Mate, Coffee, #[default] Tea
} // }

impl #kind_name { // impl DrinkKind {
Expand Down
4 changes: 4 additions & 0 deletions kinded_macros/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ impl Meta {
pub struct Variant {
pub ident: Ident,
pub fields_type: FieldsType,
/// Attributes specified with `#[kinded(CONTENT)]` above the variants of the original enum.
/// The token streams in here only contain the `CONTENT` from the example above.
/// These can be pasted as is into the kind enum.
pub kinded_variant_attrs: Vec<TokenStream>,
}

/// This mimics syn::Fields, but without payload.
Expand Down
25 changes: 25 additions & 0 deletions kinded_macros/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ fn parse_variant(variant: &syn::Variant) -> Variant {
Variant {
ident: variant.ident.clone(),
fields_type: parse_fields_type(&variant.fields),
kinded_variant_attrs: parse_kinded_variant_attrs(&variant),
}
}

Expand All @@ -49,6 +50,30 @@ fn parse_fields_type(fields: &syn::Fields) -> FieldsType {
}
}

/// This function takes a variant and returns a vector of token stream.
/// The token streams were previously the content of a `#[kinded(...)]` attribute on this variant.
/// Other attributes are ignored as they are (probably) meant for the main enum.
///
/// Multiple `#[kinded(...)]` attributes can be present on one variant, therefore a vector will collect them.
fn parse_kinded_variant_attrs(variant: &'_ syn::Variant) -> Vec<proc_macro2::TokenStream> {
variant
.attrs
.iter()
.filter_map(|attr| {
// Only keep attributes of the form `path(...)`
match &attr.meta {
syn::Meta::List(meta_list) => Some(meta_list),
_ => None,
}
})
.filter_map(|meta_list|
// and only if the path is "kinded"
// then will probably save a teeny tiny amount of time
// as it skips the cloning in cases were the path does not match.
meta_list.path.is_ident("kinded").then(|| meta_list.tokens.clone()))
.collect()
}

/// Find `#[kinded(..)]` attribute on the enum.
fn find_kinded_attr(input: &DeriveInput) -> Result<Option<&Attribute>, syn::Error> {
let kinded_attrs: Vec<_> = input
Expand Down
1 change: 1 addition & 0 deletions test_suite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ edition = "2024"

[dependencies]
kinded = { path = "../kinded" }
strum = { version = "0.27.2", features = ["derive"] }
50 changes: 50 additions & 0 deletions test_suite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,56 @@
extern crate alloc;

use kinded::Kinded;
mod variant_attributes {
//! This test module uses the [`EnumMessage`]-Trait because if variant attributes do not work,
//! the derive-macro will still finish successfully.
//! The corresponding methods will just return `None`.

use super::*;
use alloc::string::String;
use strum::EnumMessage;

#[derive(Kinded)]
#[kinded(derive(Hash, Default, EnumMessage))]
enum Drink {
#[kinded(doc = "Not suitable for small children. Please talk to your IT-person about the harmful effects of mate addiction.")]
#[kinded(strum(
message = "Made from fermented leaves.",
detailed_message="Caffeinated beverage from South America. Not suitable for small children because of caffeine."
))]
Mate,
#[kinded(doc = "Not suitable for small children!")]
#[kinded(strum(
message = "Made from roasted, ground beans.",
detailed_message="Beverage made from roasted, ground beans that originated somewhere around the read sea. Not suitable for small children, especially Espresso."
))]
Coffee(String),
#[kinded(default)]
#[kinded(doc = "Only suitable for small children if caffeine-free.")]
#[kinded(strum(
message = "Made from fermented leaves.",
detailed_message="Beverage made from fermented leaves that originated from china. Some contain caffeine and are not suitable for small children."
))]
Tea { variety: String, caffeine: bool }
}
#[test]
pub fn test_message() {
let mate = DrinkKind::Mate;
assert_eq!(Some("Made from fermented leaves."), mate.get_message());
assert_eq!(Some("Caffeinated beverage from South America. Not suitable for small children because of caffeine."), mate.get_detailed_message());
assert_eq!(Some("Not suitable for small children. Please talk to your IT-person about the harmful effects of mate addiction."), mate.get_documentation());

let coffee = DrinkKind::Coffee;
assert_eq!(Some("Made from roasted, ground beans."), coffee.get_message());
assert_eq!(Some("Beverage made from roasted, ground beans that originated somewhere around the read sea. Not suitable for small children, especially Espresso."), coffee.get_detailed_message());
assert_eq!(Some("Not suitable for small children!"), coffee.get_documentation());

let tea = DrinkKind::Tea;
assert_eq!(Some("Made from fermented leaves."), tea.get_message());
assert_eq!(Some("Beverage made from fermented leaves that originated from china. Some contain caffeine and are not suitable for small children."), tea.get_detailed_message());
assert_eq!(Some("Only suitable for small children if caffeine-free."), tea.get_documentation());
}
}

#[derive(Kinded)]
enum Role {
Expand Down