enum_ext 0.5.0

procedural macro that enhances enums with additional methods and conversions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# Enum Extension Library

[![Rust](https://github.com/cubicle-jockey/enum_ext/actions/workflows/rust.yml/badge.svg)](https://github.com/cubicle-jockey/enum_ext/actions/workflows/rust.yml)
[![Dependency Review](https://github.com/cubicle-jockey/enum_ext/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/cubicle-jockey/enum_ext/actions/workflows/dependency-review.yml)
[![Crate](https://img.shields.io/crates/v/enum_ext.svg)](https://crates.io/crates/enum_ext)
[![API](https://docs.rs/enum_ext/badge.svg)](https://docs.rs/enum_ext)

This Rust crate provides `procedural` and `attribute` macros that enhance Rust enums with additional methods and
conversions. It simplifies working with enums by automatically generating utility methods for common tasks such as
retrieving a list of variants, counting variants, and converting between discriminants and integer types.

See the `enum_ext!` and `#[enum_extend]` macro examples below for more information.

Both macros generate the same utility methods, so you can choose the one that best fits your coding style.

## Utility Functions

### Core Functions

- **`list()`**: Returns an array containing all variants of the enum.
- **`count()`**: Returns the number of variants in the enum.
- **`ordinal()`**: Returns the ordinal (index) of a variant.
- **`from_ordinal(ordinal: usize)`**: Returns the variant corresponding to the given ordinal.
- **`ref_from_ordinal(ordinal: usize)`**: Returns a reference to the variant corresponding to the given ordinal.
- **`valid_ordinal(ordinal: usize)`**: Checks if the given ordinal is valid for the enum.
- **`iter()`**: Returns an iterator over all the variants of the enum.
- **`pretty_print()`**: Returns a formatted string displaying the enum and all its variants in a pretty-print format.

### String Conversion Functions

- **`pascal_spaced(&self)`**: Converts the variant name to spaced PascalCase. For instance, `InQA` becomes `"In QA"`.
- **`from_pascal_spaced(name: &str)`**: Returns the variant corresponding to the spaced PascalCase name. For example,
  `"In QA"` becomes `InQA`.
- **`snake_case(&self)`**: Converts the variant name to snake_case. For instance, `InQA` becomes `"in_qa"`.
- **`from_snake_case(name: &str)`**: Returns the variant corresponding to the snake_case name. For example, `"in_qa"`
  becomes `InQA`.
- **`kebab_case(&self)`**: Converts the variant name to kebab-case. For instance, `InQA` becomes `"in-qa"`.
- **`from_kebab_case(name: &str)`**: Returns the variant corresponding to the kebab-case name. For example, `"in-qa"`
  becomes `InQA`.

### Navigation Functions

- **`next(&self)`**: Returns the next variant in ordinal order (wraps around to first when at last).
- **`previous(&self)`**: Returns the previous variant in ordinal order (wraps around to last when at first).
- **`next_linear(&self)`**: Returns the next variant without wrapping (returns `None` at end).
- **`previous_linear(&self)`**: Returns the previous variant without wrapping (returns `None` at start).

### Validation Functions

- **`is_first(&self)`**: Returns `true` if this is the first variant (ordinal 0).
- **`is_last(&self)`**: Returns `true` if this is the last variant.
- **`comes_before(&self, other: &Self)`**: Returns `true` if this variant comes before the other in ordinal order.
- **`comes_after(&self, other: &Self)`**: Returns `true` if this variant comes after the other in ordinal order.

### Filtering Functions

- **`variants_containing(substring: &str)`**: Returns variants whose names contain the substring.
- **`variants_starting_with(prefix: &str)`**: Returns variants whose names start with the prefix.
- **`variants_ending_with(suffix: &str)`**: Returns variants whose names end with the suffix.

### Batch Operations

- **`slice(start: usize, end: usize)`**: Returns a slice of variants from start to end ordinal.
- **`range(range: core::ops::Range<usize>)`**: Returns variants in the specified ordinal range.
- **`first_n(n: usize)`**: Returns the first N variants.
- **`last_n(n: usize)`**: Returns the last N variants.

### Metadata Functions

- **`variant_name(&self)`**: Returns the variant name as a string.
- **`variant_names()`**: Returns all variant names as a vector of strings.

### Random Selection (Optional Feature)

- **`random()`**: Returns a random variant (requires `"random"` feature).
- **`random_with_rng<R: Rng>(rng: &mut R)`**: Returns a random variant using provided RNG (requires `"random"` feature).

### Integer Conversion Functions (When IntType is specified)

- **`from_<IntType>(value: <IntType>)`** and **`as_<IntType>(&self)`**: Convert to and from the specified integer type,
  if defined in the attributes.
    - For example, `from_i32(10)` and `as_i32()` if `IntType = "i32"`, or `from_u32(10)` and `as_u32()` if
      `IntType = "u32"`, etc.

### `See examples in the repository for more information.`

## Attributes

Attributes are optional and used to customize the generated methods.

* `IntType` is currently the only attribute supported and specifies the discriminant type for conversion methods. The
  generated methods allow
  conversion from this type to an enum variant and vice versa. Supported types include standard Rust
  integer types like `i32`, `u32`, `i64`, etc. If this attribute is not specified, `usize` is used as the default.
    * **Note**: If the enum has discriminant values, `#[derive(Clone)]` is added to the enum (if not already present).

## Features

This crate supports optional features that can be enabled in your `Cargo.toml`:

* `random` - Enables random variant selection functionality (`random()` and `random_with_rng()` methods). Add this to
  your `Cargo.toml`:
  ```toml
  [dependencies]
  rand = "0.9"
  enum_ext = { version = "0.5.0", features = ["random"] }
  ```

Assigning attributes vary slightly depending on the macro used.

When using `enum_extend`, the attribute is applied directly in the tag:

```rust
use enum_ext::enum_extend;

// example with no attribute
#[enum_extend]
#[derive(Debug, Clone, PartialEq)]
pub enum Discr1 {
    A = 10,
    B = 20,
    C = 30,
}

// example with an attribute
#[enum_extend(IntType = "i32")]  // <- `IntType` is the discriminant type for conversion methods
#[derive(Debug, Clone, PartialEq)]
pub enum Discr2 {
    A = 10,
    B = 20,
    C = 30,
}

```

When using `enum_ext!`, the attribute is applied in an `enum_def` parameter to the macro:

```rust
use enum_ext::enum_ext;

enum_ext!(
    #[enum_def(IntType = "i32")]  // <- `IntType` is the discriminant type. 
    #[derive(Debug, Clone, PartialEq)]
    pub enum AdvancedEnum {
        A = 10,  
        B = 20,
        C = 30,
    }
);
```

## Usage

### Using the `#[enum_extend]` Attribute Macro

To use the enum_extend attribute macro, simply include it in your Rust project and apply it to your enum definitions.
Here's an example:

```rust
fn main() {
    use enum_ext::enum_extend;

    #[enum_extend(IntType = "i32")]
    #[derive(Debug, Default, Clone, PartialEq)]
    pub enum AdvancedEnum {
        #[default]
        A = 10,
        B = 20,
        C = 30,
    }

    for x in AdvancedEnum::iter() {
        let i = x.as_i32();
        let v = AdvancedEnum::from_i32(i).unwrap();
        assert_eq!(i, v.as_i32());
        assert_eq!(*x, v); // This comparison requires that PartialEq be derived
    }

    let v = AdvancedEnum::from_i32(20).unwrap();
    assert_eq!(v, AdvancedEnum::B);
}
```

### Using the `enum_ext!` Procedural Macro

To use the `enum_ext!` macro, simply include it in your Rust project and apply it to your enum definitions. Here's an
example:

```rust
fn main() {
    use enum_ext::enum_ext;

    enum_ext!(
        #[derive(Debug, Clone, PartialEq)]
        pub enum SimpleEnum {
            A,
            B,
            C,
        }
    );
    // With this, you can now use the generated methods on SimpleEnum:
    let x = SimpleEnum::B;
    assert_eq!(x.ordinal(), 1); // B is the second variant, so its ordinal is 1

    let mut count = 0;

    // enum_ext gives enums an iterator and variants can be iterated over
    for x in SimpleEnum::iter() {
        // The ordinal of the variant can be retrieved
        let i = x.ordinal();
        assert_eq!(i, count);
        count += 1;
    }

    // enums also get a list method that returns an array of all variants
    let list = SimpleEnum::list();
    assert_eq!(list, [SimpleEnum::A, SimpleEnum::B, SimpleEnum::C]);

    enum_ext!(
        #[derive(Debug, Clone, Default, PartialEq)]
        pub enum TicketStatus {
            #[default]
            Open,
            InDev,
            Completed,
            InQA,
            CodeReview,
            FinalQA,
            FinalCodeReview,
            Accepted,
            Closed,
        }
    );

    // enums now have a pascal_spaced method that returns the variant name in spaced PascalCase.
    // This is useful for displaying enum variants in a user-friendly format (e.g., in a UI).
    // One example usage is converting InQA to "In QA" for display on a web page.
    let status = TicketStatus::InQA;
    assert_eq!(status.pascal_spaced(), "In QA");

    // enums also get a from_pascal_spaced method that returns the variant from the spaced PascalCase name.
    // This is useful for converting user-friendly format back to an enum variant.
    // This is the reverse of the example above, converting "In QA" back to an enum.
    let status2 = TicketStatus::from_pascal_spaced("In QA").unwrap();
    assert_eq!(status2, TicketStatus::InQA);
}
```

Additional utility methods are generated for the enum variants:

```rust
use enum_ext::enum_extend;

#[enum_extend(IntType = "i32")]
#[derive(Debug, PartialEq)]
pub enum DevelopmentStatus {
    InDev = 10,
    InQA = 20,
    CodeReview = 30,
    FinalQA = 40,
    FinalCodeReview = 50,
    Accepted = 60,
    Closed = 70,
}

fn main() {
    // Using list()
    let variants = DevelopmentStatus::list();
    assert_eq!(variants,
               [DevelopmentStatus::InDev,
                   DevelopmentStatus::InQA,
                   DevelopmentStatus::CodeReview,
                   DevelopmentStatus::FinalQA,
                   DevelopmentStatus::FinalCodeReview,
                   DevelopmentStatus::Accepted,
                   DevelopmentStatus::Closed]);

    // Using count()
    let count = DevelopmentStatus::count();
    assert_eq!(count, 7);

    // Using ordinal()
    let ordinal = DevelopmentStatus::CodeReview.ordinal();
    assert_eq!(ordinal, 2);  // CodeReview is the third variant, so its ordinal is 2
    assert_eq!(DevelopmentStatus::from_ordinal(2), Some(DevelopmentStatus::CodeReview));

    // Using iter()
    for (i, variant) in DevelopmentStatus::iter().enumerate() {
        assert_eq!(i, variant.ordinal());
    }

    // Using from_i32() and as_i32()
    let variant = DevelopmentStatus::from_i32(20).unwrap();
    assert_eq!(variant, DevelopmentStatus::InQA);
    assert_eq!(variant.as_i32(), 20);

    // Using pascal_spaced() method that returns the variant name in spaced PascalCase.
    // This is useful for displaying enum variants in a user-friendly format (e.g., in a UI).
    // One example usage is converting InQA to "In QA" for display on a web page.
    let status = DevelopmentStatus::InQA;
    assert_eq!(status.pascal_spaced(), "In QA");

    // Using from_pascal_spaced() method that returns the variant from the spaced PascalCase name.
    // This is useful for converting user-friendly format back to an enum variant.
    // This is the reverse of the example above, converting "In QA" back to an enum.
    let status2 = DevelopmentStatus::from_pascal_spaced("In QA").unwrap();
    assert_eq!(status2, DevelopmentStatus::InQA);
}
```

### Complex enum support

Starting with v0.5.0, enums with payloads (tuple or struct variants) are supported. These are referred to as complex
enums below.

Requirements and recommendations:

- Every complex enum variant must declare an explicit discriminant expression (for example, A(u32) = 4, B((u32, i16)) =
  8). The macro will emit a compile error if any payload-carrying variant is missing a discriminant.
- The integer representation (#[repr(..)]) is added automatically when you specify IntType, or when discriminants are
  present. If you do not specify IntType, the default conversion target is usize and as_usize will be generated.

What is generated for complex enums:

- Provided methods (all const and using match on self):
    - count(), ordinal(), valid_ordinal()
    - pascal_spaced(), snake_case(), kebab_case()
    - variant_name(), is_first(), is_last(), comes_before(), comes_after()
    - `as_<IntType>(&self) -> <IntType>` (for example, `as_u32()`), which returns the discriminant value
- Omitted methods (not generated for complex enums because they require constructing values without payloads):
    - list(), iter(), slice(), range(), first_n(), last_n()
    - from_ordinal(), ref_from_ordinal(), next(), previous(), next_linear(), previous_linear()
    - `from_<IntType>(...)`, `impl From<<IntType>> for YourEnum`
    - from_pascal_spaced(...), from_snake_case(...), from_kebab_case(...), variant_names()
    - random() helpers (feature = "random")

Example:

```rust
use enum_ext::enum_extend;

#[enum_extend(IntType = "u32")] // IntType is optional; defaults to usize when omitted
#[derive(Debug, Clone, Copy, PartialEq)]
enum Complex {
    AlphaOne(u32) = 4,
    BetaTwo((u32, i16)) = 8,
    CharlieThree { fred: u32, barny: i16 } = 16,
}

fn main() {
    let a = Complex::AlphaOne(10);
    let b = Complex::BetaTwo((1, -2));
    let c = Complex::CharlieThree { fred: 5, barny: -7 };

    // integer conversion retains match-based logic and remains const
    assert_eq!(a.as_u32(), 4);
    assert_eq!(b.as_u32(), 8);
    assert_eq!(c.as_u32(), 16);

    // ordinal and name helpers still work
    assert_eq!(a.ordinal(), 0);
    assert_eq!(a.pascal_spaced(), "Alpha One");
    assert_eq!(a.snake_case(), "alpha_one");
    assert_eq!(a.kebab_case(), "alpha-one");
}
```

## Changes

### v0.5.0

- Added support for complex enums (variants with payloads)
    - <b>note</b>: not all utility features are possible for complex enums and are
      omitted from these types of enums only (non-complex enums still have them). see the
      [Complex enum support]#complex-enum-support section for more details.
  ```rust
  use enum_ext::enum_extend;
  
  #[enum_extend(IntType = "i32")]
  #[derive(Debug, Clone, PartialEq)]
  pub enum DiscrExpression {
      // singles
      X10(u32) = 10,
      // tuples
      X25((i32,i16)) = 5 * 5,
      // structs
      Y26{foo: u32,bar: String} = 13 + 13,      
  }
  ```

### v0.4.5

- Added support for discriminant expressions, instead of just literals.
  ```rust
  use enum_ext::enum_extend;
  
  #[enum_extend(IntType = "i32")]
  #[derive(Debug, Clone, Copy, PartialEq)]
  pub enum DiscrExpression {
      // literals
      X10 = 10,
      // expressions
      X25 = 5 * 5,
      Y26 = 13 + 13,
      Z100 = 10 * (5 + 5),
  }
  ```

### v0.4.4

- swapped `std::ops::Range` with `core::ops::Range` for compatibility with `no_std`

### v0.4.3

- as_<int_type> is now `const` fn if dev derives `Copy` on enum.

### v0.4.2

- Parse the configured `IntType` into a real Rust type using `syn::parse_str::<syn::Type>` instead of string-based token
  hacks; this makes `IntType` handling more robust and prevents malformed type tokens from being emitted.
- Emit `#[repr(...)]` whenever the user explicitly specifies `IntType` (even if no discriminants are present), so the
  enum layout matches the requested integer representation.
- Reject multiple `#[enum_def(...)]` attributes on the derive macro; the macro now returns a clear compile error if more
  than one `enum_def` attribute is present.
- Use the local `EnumDefArgs::default()` directly and tidy up attribute parsing code paths for clarity.
- Improve tests and validation across the macros;