Skip to main content

enum_helper_derive/
lib.rs

1//! Procedural macro implementations for [`enum-helper`].
2//!
3//! You normally depend on `enum-helper` (which re-exports these macros)
4//! rather than using this crate directly.
5//!
6//! [`enum-helper`]: https://docs.rs/enum-helper
7
8mod attr;
9mod ctxt;
10mod enum_all;
11mod enum_kind;
12mod enum_str;
13mod symbol;
14mod template;
15
16use proc_macro::TokenStream;
17use syn::{DeriveInput, parse_macro_input};
18
19/// Derive the [`EnumStr`] trait to convert between a unit enum and string.
20///
21/// `EnumStr` only supports unit enums (enums where all variants have no
22/// fields).
23///
24/// This macro generates:
25///
26/// - `impl EnumStr for T`
27/// - `impl From<T> for &'static str`
28/// - `impl AsRef<str> for T`
29/// - `impl FromStr for T`
30/// - `impl TryFrom<&str> for T`
31/// - Error struct for invalid conversion from str
32///
33/// # Container attributes
34///
35/// - `#[enum_str(rename_all = "snake_case")]`: rename all variants by rule
36/// - `#[enum_str(alias_all = "lowercase")]`: add aliases to all variants by rule (repeatable)
37/// - `#[enum_str(default)]`: fills non-unit variant's fields with default value during parsing
38/// - `#[enum_str(error_name = InvalidFoo)]`: custom error type name (default: `Invalid{Enum}`)
39/// - `#[enum_str(error_msg = "…")]`: custom error message template
40/// - `#[enum_str(no_rendering)]`: skip `EnumStr`, `From`, `AsRef` impls
41/// - `#[enum_str(no_parsing)]`: skip `FromStr`, `TryFrom`, and the error struct
42/// - `#[enum_str(no_error_struct)]`: skip the generated error struct (bring your own)
43///
44/// # Variant attributes
45///
46/// - `#[enum_str(rename = "custom_name")]`: override the variant's name
47/// - `#[enum_str(alias = "alt")]`: add an alias (repeatable)
48/// - `#[enum_str(skip)]`: skip variant, only affect parsing
49///
50/// # Available rename rules
51///
52/// - `lowercase`
53/// - `UPPERCASE`
54/// - `PascalCase`
55/// - `camelCase`
56/// - `snake_case`
57/// - `SCREAMING_SNAKE_CASE`
58/// - `kebab-case`
59/// - `SCREAMING-KEBAB-CASE`
60///
61/// # Available error message template variables
62///
63/// - `{name}`: the enum's type name
64/// - `{input}`: the invalid input string (requires allocation)
65/// - `{names}`: all variant primary names
66/// - `{aliases}`: all names and aliases
67///
68/// List variables accept format modifiers:
69///
70/// - `{names}`: default, comma-separated, double-quoted: `"Bar", "Baz"`
71/// - `{names:|}`: custom separator, no quotes: `Bar|Baz`
72/// - `{names: - :'}`: custom separator and quote char: `'Bar' - 'Baz'`
73///
74/// Use `{{` and `}}` for literal braces. `:` cannot be used as a separator or quote character.
75///
76/// # Example
77///
78/// ```
79/// use enum_helper::EnumStr;
80///
81/// #[derive(EnumStr, Debug, PartialEq, Eq)]
82/// #[enum_str(rename_all = "lowercase")]
83/// pub enum Foo {
84///     Bar,
85///     #[enum_str(alias = "bazzz")]
86///     Baz,
87/// }
88///
89/// assert_eq!(Foo::Bar.as_name(), "bar");
90/// assert_eq!("bazzz".parse::<Foo>().unwrap(), Foo::Baz);
91/// ```
92///
93/// [`EnumStr`]: https://docs.rs/enum-helper/latest/enum_helper/trait.EnumStr.html
94#[proc_macro_derive(EnumStr, attributes(enum_str))]
95pub fn derive_enum_str(input: TokenStream) -> TokenStream {
96    let input = parse_macro_input!(input as DeriveInput);
97
98    let ir = match enum_str::parse::parse_ir(&input) {
99        Ok(ir) => ir,
100        Err(err) => return err.into_compile_error().into(),
101    };
102
103    enum_str::generate::generate(ir).into()
104}
105
106/// Derive the [`EnumAll`] trait and generate an array of all variants.
107///
108/// This macro generates:
109///
110/// - `impl EnumAll for T`
111///
112/// # Container attributes
113///
114/// *(none)*
115///
116/// # Variant attributes
117///
118/// - `#[enum_all(skip)]`: exclude this variant from the `ALL` array
119///
120/// # Example
121///
122/// ```
123/// use enum_helper::EnumAll;
124///
125/// #[derive(EnumAll, Debug, PartialEq, Eq)]
126/// enum Foo {
127///     Bar,
128///     Baz,
129///     #[enum_all(skip)]
130///     Skipped
131/// }
132///
133/// assert_eq!(Foo::ALL, [Foo::Bar, Foo::Baz]);
134/// ```
135///
136/// [`EnumAll`]: https://docs.rs/enum-helper/latest/enum_helper/trait.EnumAll.html
137#[proc_macro_derive(EnumAll, attributes(enum_all))]
138pub fn derive_enum_all(input: TokenStream) -> TokenStream {
139    let input = parse_macro_input!(input as DeriveInput);
140
141    let ir = match enum_all::parse::parse_ir(&input) {
142        Ok(ir) => ir,
143        Err(err) => return err.into_compile_error().into(),
144    };
145
146    enum_all::generate::generate(ir).into()
147}
148
149/// Derive the [`EnumKind`] trait and generate a unit kind enum from a data-carrying enum.
150///
151/// This macro generates:
152///
153/// - Kind enum
154/// - `impl EnumKind for T`
155///
156/// # Container attributes
157///
158/// - `#[enum_kind(name = MyKind)]`: custom name for the generated kind enum (default: `{Enum}Kind`)
159/// - `#[enum_kind(attr(...))]`: forward attributes to the generated kind enum (repeatable)
160/// - `#[enum_kind(no_default_derive)]`: disable the default `Debug, Clone, Copy, PartialEq, Eq` derives on generated kind enum
161///
162/// # Variant attributes
163///
164/// - `#[enum_kind(rename = Bar)]`: rename the kind variant
165/// - `#[enum_kind(attr(...))]`: forward attributes to the kind variant (repeatable)
166///
167/// # Example
168///
169/// ```
170/// use enum_helper::EnumKind;
171///
172/// #[derive(EnumKind)]
173/// enum Message {
174///     Text(String),
175///     Quit,
176/// }
177///
178/// let msg = Message::Text("hello".into());
179/// assert_eq!(msg.kind(), MessageKind::Text);
180/// ```
181///
182/// [`EnumKind`]: https://docs.rs/enum-helper/latest/enum_helper/trait.EnumKind.html
183#[proc_macro_derive(EnumKind, attributes(enum_kind))]
184pub fn derive_enum_kind(input: TokenStream) -> TokenStream {
185    let input = parse_macro_input!(input as DeriveInput);
186
187    let ir = match enum_kind::parse::parse_ir(&input) {
188        Ok(ir) => ir,
189        Err(err) => return err.into_compile_error().into(),
190    };
191
192    enum_kind::generate::generate(ir).into()
193}