Skip to content

Commit 158aeb2

Browse files
committed
Generate implementation of FromStr
1 parent 2b79b40 commit 158aeb2

File tree

9 files changed

+167
-199
lines changed

9 files changed

+167
-199
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## Next version
22
* Add `::all()` to the kind type to iterate over all kind variants
33
* Generate customizable implementation of `Display` trait
4+
* Generate implementation of `FromStr` trait
45

56
## v0.0.3 - 2023-08-05
67
* Make generated `kind()` function public

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ enum Drink {
9494

9595
### Derive traits
9696

97-
By default the kind type implements the following traits: `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `Display`, `From<T>`, `From<&T>`.
97+
By default the kind type implements the following traits: `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `Display`, `FromStr`, `From<T>`, `From<&T>`.
9898

9999
Extra traits can be derived with `derive(..)` attribute:
100100

@@ -114,7 +114,7 @@ let mut drink_kinds = HashSet::new();
114114
drink_kinds.insert(DrinkKind::Mate);
115115
```
116116

117-
### Customize Display trait
117+
### Display trait
118118

119119
Implementation of `Display` trait can be customized in the `serde` fashion:
120120

@@ -134,6 +134,31 @@ assert_eq!(tea.to_string(), "very_hot_black_tea");
134134

135135
The possible values are `"snake_case"`, `"camelCase"`, `"PascalCase"`, `"SCREAMING_SNAKE_CASE"`, `"kebab-case"`, `"SCREAMING-KEBAB-CASE"`, `"Title Case"`, `"lowercase"`, `"UPPERCASE"`.
136136

137+
### FromStr trait
138+
139+
The kind type implements `FromStr` trait. The implementation tries it's best to parse, checking all the possible cases mentioned above.
140+
141+
```rs
142+
use kinded::Kinded;
143+
144+
#[derive(Kinded)]
145+
#[kinded(display = "snake_case")]
146+
enum Drink {
147+
VeryHotBlackTea,
148+
Milk { fat: f64 },
149+
}
150+
151+
assert_eq!(
152+
"VERY_HOT_BLACK_TEA".parse::<DrinkKind>().unwrap(),
153+
DrinkKind::VeryHotBlackTea
154+
);
155+
156+
assert_eq!(
157+
"veryhotblacktea".parse::<DrinkKind>().unwrap(),
158+
DrinkKind::VeryHotBlackTea
159+
);
160+
```
161+
137162

138163
## A note about enum-kinds
139164

kinded/src/errors.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/// An error which is returned when parsing of a kind type failures.
2-
#[derive(Debug)]
32
pub struct ParseKindError {
43
kind_type_name: String,
54
given_string: String,
@@ -8,10 +7,13 @@ pub struct ParseKindError {
87
impl ParseKindError {
98
/// This method is used by `kinded` macro to construct an error for FromStr trait and is not
109
/// recommend for a direct usage by users.
11-
pub fn from_type_name_and_string(
12-
kind_type_name: String,
13-
given_string: String,
14-
) -> ParseKindError {
10+
pub fn from_type_and_string<KindType>(given_string: String) -> ParseKindError {
11+
let full_kind_type_name = std::any::type_name::<KindType>();
12+
let kind_type_name = full_kind_type_name
13+
.split("::")
14+
.last()
15+
.expect("Type name cannot be empty")
16+
.to_string();
1517
ParseKindError {
1618
kind_type_name,
1719
given_string,
@@ -25,10 +27,13 @@ impl ::core::fmt::Display for ParseKindError {
2527
kind_type_name,
2628
given_string,
2729
} = self;
28-
write!(
29-
f,
30-
r#"Failed to parse {kind_type_name} from "{given_string}""#
31-
)
30+
write!(f, r#"Failed to parse "{given_string}" as {kind_type_name}"#)
31+
}
32+
}
33+
34+
impl ::core::fmt::Debug for ParseKindError {
35+
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> Result<(), ::core::fmt::Error> {
36+
write!(f, "ParseKindError: {self}")
3237
}
3338
}
3439

kinded/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,31 @@
141141
//! assert_eq!(tea.to_string(), "very_hot_black_tea");
142142
//! ```
143143
//!
144+
//! ### FromStr trait
145+
//!
146+
//! The kind type implements `FromStr` trait. The implementation tries it's best to parse, checking all the possible cases mentioned above.
147+
//!
148+
//! ```
149+
//! use kinded::Kinded;
150+
//!
151+
//! #[derive(Kinded)]
152+
//! #[kinded(display = "snake_case")]
153+
//! enum Drink {
154+
//! VeryHotBlackTea,
155+
//! Milk { fat: f64 },
156+
//! }
157+
//!
158+
//! assert_eq!(
159+
//! "VERY_HOT_BLACK_TEA".parse::<DrinkKind>().unwrap(),
160+
//! DrinkKind::VeryHotBlackTea
161+
//! );
162+
//!
163+
//! assert_eq!(
164+
//! "veryhotblacktea".parse::<DrinkKind>().unwrap(),
165+
//! DrinkKind::VeryHotBlackTea
166+
//! );
167+
//! ```
168+
//!
144169
//! The possible values are `"snake_case"`, `"camelCase"`, `"PascalCase"`, `"SCREAMING_SNAKE_CASE"`, `"kebab-case"`, `"SCREAMING-KEBAB-CASE"`, `"Title Case"`, `"lowercase"`, `"UPPERCASE"`.
145170
//!
146171
//! ## A note about the war in Ukraine 🇺🇦

kinded_macros/src/gen/kind_enum.rs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn gen_impl_display_trait(meta: &Meta) -> TokenStream {
6464

6565
let match_branches = meta.variants.iter().map(|variant| {
6666
let original_variant_name_str = variant.ident.to_string();
67-
let cased_variant_name = apply_display_case(original_variant_name_str, maybe_case);
67+
let cased_variant_name = apply_maybe_case(original_variant_name_str, maybe_case);
6868
let variant_name = &variant.ident;
6969
quote!(
7070
#kind_name::#variant_name => write!(f, #cased_variant_name)
@@ -82,12 +82,9 @@ fn gen_impl_display_trait(meta: &Meta) -> TokenStream {
8282
)
8383
}
8484

85-
fn apply_display_case(original: String, maybe_display_case: Option<DisplayCase>) -> String {
86-
use convert_case::{Case, Casing};
87-
85+
fn apply_maybe_case(original: String, maybe_display_case: Option<DisplayCase>) -> String {
8886
if let Some(display_case) = maybe_display_case {
89-
let case: Case = display_case.into();
90-
original.to_case(case)
87+
display_case.apply(&original)
9188
} else {
9289
original
9390
}
@@ -99,22 +96,37 @@ fn gen_impl_from_str_trait(meta: &Meta) -> TokenStream {
9996
let original_match_branches = meta.variants.iter().map(|variant| {
10097
let ident = &variant.ident;
10198
let name_str = ident.to_string();
102-
quote!(#name_str => Ok(#kind_name::#ident),)
99+
quote!(#name_str => return Ok(#kind_name::#ident),)
100+
});
101+
102+
let alt_match_branches = meta.variants.iter().map(|variant| {
103+
let ident = &variant.ident;
104+
let name_str = ident.to_string();
105+
let alternatives = DisplayCase::all().map(|case| case.apply(&name_str));
106+
quote!(#(#alternatives)|* => return Ok(#kind_name::#ident),)
103107
});
104108

105109
quote!(
106110
impl ::core::str::FromStr for #kind_name {
107111
type Err = ::kinded::ParseKindError;
108112

109113
fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
110-
match s {
111-
#(#original_match_branches)*
112-
_ => {
113-
let type_name: String = std::any::type_name::<#kind_name>().to_owned();
114-
let error = ::kinded::ParseKindError::from_type_name_and_string(type_name, s.to_owned());
115-
Err(error)
116-
},
117-
}
114+
// First try to match the variants as they are
115+
match s { // match s {
116+
#(#original_match_branches)* // "HotMate" => Mate::HotMate,
117+
_ => () // _ => (),
118+
} //
119+
120+
// Now try to match all possible alternative spelling of
121+
// the variants
122+
match s { // match s {
123+
#(#alt_match_branches)* // "hot_mate" | "HOT_MATE" | "hotMate" | .. => Mate::HotMate
124+
_ => () // _ => ()
125+
} // }
126+
127+
// If still no success, then return an error
128+
let error = ::kinded::ParseKindError::from_type_and_string::<#kind_name>(s.to_owned());
129+
Err(error)
118130
}
119131
}
120132
)

kinded_macros/src/lib.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,6 @@ pub(crate) mod parse;
1414
use proc_macro2::TokenStream;
1515
use syn::DeriveInput;
1616

17-
// DONE:
18-
// * Allow renaming of kind type
19-
// * Allow to derive custom traits
20-
21-
// TODO:
22-
// * Extract test_suite
23-
// * Make it work with generics
24-
// * Consider supporting FromStr / Display?
25-
// * Allow to iterate, access ALL variants of Kind
26-
// * Features:
27-
// * enum-map
28-
// * Write documentation
29-
// * lib.rs
30-
// * README
31-
// * How it differs from enum-kinds ?
32-
//
33-
3417
#[proc_macro_derive(Kinded, attributes(kinded))]
3518
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
3619
expand_derive(input)

kinded_macros/src/mod.rs

Lines changed: 0 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,3 @@
11
use crate::models::{DisplayCase, FieldsType, Meta, Variant};
22
use proc_macro2::{Ident, TokenStream};
33
use quote::quote;
4-
5-
pub fn generate(meta: Meta) -> TokenStream {
6-
let enum_kind = gen_enum_kind(&meta);
7-
let impl_display_for_enum_kind = gen_impl_display_for_enum_kind(&meta);
8-
let impl_from_str_for_enum_kind = gen_impl_from_str_for_enum_kind(&meta);
9-
10-
let fn_kind = gen_fn_kind(&meta);
11-
let type_name = &meta.ident;
12-
let kind_name = meta.kind_name();
13-
let generics = &meta.generics;
14-
15-
let type_with_generics = quote!(#type_name #generics);
16-
17-
quote!(
18-
#enum_kind // enum DrinkKind { Mate, Coffee, Tea }
19-
20-
#impl_display_for_enum_kind // impl std::fmt::Display for DrinkKind { ... }
21-
22-
#impl_from_str_for_enum_kind // impl std::str::FromStr for DrinkKind { ... }
23-
24-
impl #generics #type_with_generics { // impl<T> Drink<T> {
25-
#fn_kind // fn kind(&self) -> DrinkKind { ... }
26-
} // }
27-
28-
impl #generics ::kinded::Kinded for #type_with_generics { // impl<T> ::kinded::Kinded for Drink<T> {
29-
type Kind = #kind_name; // type Kind = DrinkKind;
30-
//
31-
fn kind(&self) -> #kind_name { // fn kind(&self) -> DrinkKind {
32-
self.kind() // self.kind()
33-
} // }
34-
} // }
35-
36-
impl #generics From<#type_with_generics> for #kind_name { // impl<'a, T> From<Drink<'a, T>> for DrinkKind {
37-
fn from(value: #type_with_generics) -> #kind_name { // fn from(value: Drink<'a, T>) -> DrinkKind {
38-
value.kind() // value.kind()
39-
} // }
40-
} // }
41-
42-
impl #generics From<&#type_with_generics> for #kind_name { // impl<'a, T> From<Drink<'a, T>> for DrinkKind {
43-
fn from(value: &#type_with_generics) -> #kind_name { // fn from(value: &Drink<'a, T>) -> DrinkKind {
44-
value.kind() // value.kind()
45-
} // }
46-
} // }
47-
)
48-
}
49-
50-
fn gen_enum_kind(meta: &Meta) -> TokenStream {
51-
let vis = &meta.vis;
52-
let kind_name = meta.kind_name();
53-
let variant_names: Vec<&Ident> = meta.variants.iter().map(|v| &v.ident).collect();
54-
let traits = meta.derive_traits();
55-
56-
quote!(
57-
#[derive(#(#traits),*)] // #[derive(Debug, Clone, Copy, PartialEq, Eq)]
58-
#vis enum #kind_name { // pub enum DrinkKind {
59-
#(#variant_names),* // Mate, Coffee, Tea
60-
} // }
61-
62-
impl #kind_name { // impl DrinkKind {
63-
pub fn all() -> impl Iterator<Item = #kind_name> { // pub fn all() -> impl Iterator<Item = DrinkKind> {
64-
[ // [
65-
#(#kind_name::#variant_names),* // DrinkKind::Mate, DrinkKind::Coffee, DrinkKind::Tea
66-
].into_iter() // ]
67-
} // }
68-
} // }
69-
)
70-
}
71-
72-
fn gen_impl_display_for_enum_kind(meta: &Meta) -> TokenStream {
73-
let kind_name = meta.kind_name();
74-
let maybe_case = meta.kinded_attrs.display;
75-
76-
let match_branches = meta.variants.iter().map(|variant| {
77-
let original_variant_name_str = variant.ident.to_string();
78-
let cased_variant_name = apply_display_case(original_variant_name_str, maybe_case);
79-
let variant_name = &variant.ident;
80-
quote!(
81-
#kind_name::#variant_name => write!(f, #cased_variant_name)
82-
)
83-
});
84-
85-
quote!(
86-
impl std::fmt::Display for #kind_name {
87-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88-
match self {
89-
#(#match_branches),*
90-
}
91-
}
92-
}
93-
)
94-
}
95-
96-
fn gen_impl_from_str_for_enum_kind(meta: &Meta) -> TokenStream {
97-
let kind_name = meta.kind_name();
98-
// let maybe_case = meta.kinded_attrs.display;
99-
100-
// let match_branches = meta.variants.iter().map(|variant| {
101-
// let original_variant_name_str = variant.ident.to_string();
102-
// let cased_variant_name = apply_display_case(original_variant_name_str, maybe_case);
103-
// let variant_name = &variant.ident;
104-
// quote!(
105-
// #kind_name::#variant_name => write!(f, #cased_variant_name)
106-
// )
107-
// });
108-
109-
quote!(
110-
impl ::std::str::FromStr for #kind_name {
111-
type Err = &'static str;
112-
113-
fn from_str(s: &str) -> Result<Self, Self::Err> {
114-
todo!()
115-
}
116-
}
117-
)
118-
}
119-
120-
fn apply_display_case(original: String, maybe_display_case: Option<DisplayCase>) -> String {
121-
use convert_case::{Case, Casing};
122-
123-
if let Some(display_case) = maybe_display_case {
124-
let case: Case = display_case.into();
125-
original.to_case(case)
126-
} else {
127-
original
128-
}
129-
}
130-
131-
fn gen_fn_kind(meta: &Meta) -> TokenStream {
132-
let name = &meta.ident;
133-
let kind_name = meta.kind_name();
134-
let match_branches = meta
135-
.variants
136-
.iter()
137-
.map(|variant| gen_match_branch(name, &kind_name, variant));
138-
139-
quote!(
140-
pub fn kind(&self) -> #kind_name { // pub fn kind(&self) -> DrinkKind {
141-
match self { // match self {
142-
#(#match_branches),* // Drink::Coffee(..) => DrinkKind::Coffee,
143-
} // }
144-
} // }
145-
)
146-
}
147-
148-
fn gen_match_branch(name: &Ident, kind_name: &Ident, variant: &Variant) -> TokenStream {
149-
let variant_name = &variant.ident;
150-
let variant_destruct = match variant.fields_type {
151-
FieldsType::Named => quote!({ .. }),
152-
FieldsType::Unnamed => quote!((..)),
153-
FieldsType::Unit => quote!(),
154-
};
155-
156-
quote!(
157-
#name::#variant_name #variant_destruct => #kind_name::#variant_name
158-
)
159-
}

0 commit comments

Comments
 (0)