derive-wizard 0.5.0

Derives interactive wizard-like user input for Rust types. Backend-agnostic (supports at least requestty and egui).
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
# derive-wizard

A Rust procedural macro that automatically generates interactive CLI wizards from struct definitions using [requestty](https://crates.io/crates/requestty).

## Showcase

```rust
use derive_wizard::Wizard;

#[derive(Debug, Wizard)]
struct ShowCase {
    // String types - defaults to 'input'
    #[prompt("Enter your name:")]
    name: String,

    // Override with password question type
    #[prompt("Enter your password:")]
    #[mask]
    password: String,

    // Long text with multiline editor
    #[prompt("Enter a bio:")]
    #[multiline]
    bio: String,

    // Bool type - defaults to 'confirm'
    #[prompt("Do you agree to the terms?")]
    agree: bool,

    // Integer types - defaults to 'int'
    #[prompt("Enter your age (i32):")]
    age: i32,

    // Float types - defaults to 'float'
    #[prompt("Enter your height in meters (f64):")]
    height: f64,

    #[prompt("Enter a decimal number (f32):")]
    decimal: f32,
    
    #[prompt("Enter your gender")]
    gender: Gender,
}

#[derive(Debug, Wizard)]
enum Gender {
    Male,
    Female,
    Other(
        #[prompt("Please specify:")]
        String
    ),
}

```

### Password Fields with `#[mask]`

For password inputs, use the convenient `#[mask]` attribute to hide user input:

```rust
use derive_wizard::Wizard;

#[derive(Debug, Wizard)]
struct LoginForm {
    #[prompt("Enter your username:")]
    username: String,

    #[prompt("Enter your password:")]
    #[mask]
    password: String,  // Input will be hidden
}
```

### Long Text with `#[multiline]`

For longer text input, use the `#[multiline]` attribute to open the user's preferred text editor:

```rust
use derive_wizard::Wizard;

#[derive(Debug, Wizard)]
struct Article {
    #[prompt("Enter the title:")]
    title: String,

    #[prompt("Write the article content:")]
    #[multiline]
    content: String,  // Opens text editor (vim, nano, etc.)
}
```

## Attributes

- `#[prompt("message")]` - **Required**. The message to display to the user
- `#[mask]` - **Optional**. For String fields: enables password input (hidden text)
- `#[multiline]` - **Optional**. For String fields: opens text editor for longer input
- `#[validate("function_name")]` - **Optional**. Validates input with a custom function

**Note**: `#[mask]` and `#[multiline]` are mutually exclusive and cannot be used on the same field.

## Using the Builder API

The builder API provides a fluent interface for configuring and executing wizards:

```rust,no_run
use derive_wizard::Wizard;

#[derive(Debug, Clone, Wizard)]
struct Config {
    #[prompt("Enter the server address:")]
    server: String,

    #[prompt("Enter the port:")]
    port: u16,

    #[prompt("Enable SSL?")]
    use_ssl: bool,
}

// Simple usage with default backend (requestty)
let config = Config::wizard_builder().build().unwrap();
println!("Config: {config:#?}");

// Edit configuration with suggestions pre-filled
let updated_config = Config::wizard_builder()
    .with_suggestions(config)
    .build()
    .unwrap();
println!("Updated config: {updated_config:#?}");
```

Additional examples:

```rust,no_run
use derive_wizard::Wizard;

# #[derive(Debug, Clone, Wizard)]
# struct Config {
#     #[prompt("Enter the server address:")]
#     server: String,
#     #[prompt("Enter the port:")]
#     port: u16,
#     #[prompt("Enable SSL?")]
#     use_ssl: bool,
# }
// With custom backend (e.g., requestty)
let backend = derive_wizard::RequesttyBackend::new();
let config = Config::wizard_builder()
    .with_backend(backend)
    .build()
    .unwrap();
println!("Config: {config:#?}");

// Combine suggestions with custom backend
let backend = derive_wizard::RequesttyBackend::new();
let updated_config = Config::wizard_builder()
    .with_suggestions(config)
    .with_backend(backend)
    .build()
    .unwrap();
println!("Updated config: {updated_config:#?}");
```

When `with_suggestions()` is used:

- For **String** fields: the current value is shown as a hint/placeholder
- For **numeric** fields (integers and floats): the current value is shown as suggested
- For **bool** fields: the current value is pre-selected
- For **password** (`#[mask]`) and **multiline** (`#[multiline]`) fields: suggestions are shown as hints (backend-dependent)

### Suggesting Individual Fields

Instead of providing a complete struct with `with_suggestions()`, you can suggest values for specific fields using `suggest_field()`:

```rust,no_run
use derive_wizard::Wizard;

# #[derive(Debug, Clone, Wizard)]
# struct Config {
#     #[prompt("Enter the server address:")]
#     server: String,
#     #[prompt("Enter the port:")]
#     port: u16,
#     #[prompt("Enable SSL?")]
#     use_ssl: bool,
# }
// Suggest specific fields, ask about all of them
let config = Config::wizard_builder()
    .suggest_field("server", "localhost".to_string())
    .suggest_field("port", 8080)
    .suggest_field("use_ssl", false)
    .build();  // All questions asked with pre-filled defaults
```

This is useful when you want to provide defaults for specific fields without needing to construct an entire struct.

### Using Assumptions

Assumptions are different from suggestions - they completely skip the questions and use the provided values directly. Use `assume_field()` to set specific fields while still asking about others:

```rust,no_run
use derive_wizard::Wizard;

# #[derive(Debug, Clone, Wizard)]
# struct Config {
#     #[prompt("Enter the server address:")]
#     server: String,
#     #[prompt("Enter the port:")]
#     port: u16,
#     #[prompt("Enable SSL?")]
#     use_ssl: bool,
# }
// Assume specific fields, ask about the rest
let config = Config::wizard_builder()
    .assume_field("use_ssl", true)      // Always use SSL in production
    .assume_field("port", 443)           // Standard HTTPS port
    .build();  // Will only ask about 'server'
```

**Key differences:**

- **Suggestions** (`with_suggestions()` or `suggest_field()`): Questions are asked, but with pre-filled default values
- **Assumptions** (`assume_field()`): Questions are skipped entirely, values are used as-is

You can also combine both approaches:

```rust,no_run
use derive_wizard::Wizard;

# #[derive(Debug, Clone, Wizard)]
# struct Config {
#     #[prompt("Enter the server address:")]
#     server: String,
#     #[prompt("Enter the port:")]
#     port: u16,
#     #[prompt("Enable SSL?")]
#     use_ssl: bool,
# }
let config = Config::wizard_builder()
    .suggest_field("server", "localhost".to_string())  // Suggest (will ask)
    .assume_field("use_ssl", true)                      // Assume (will skip)
    .assume_field("port", 443)                          // Assume (will skip)
    .build();  // Only asks about 'server' with "localhost" as default
```

Assumptions are useful for:

- Enforcing security policies (e.g., always enable SSL in production)
- Providing sensible defaults that users shouldn't change
- Batch processing with some fixed and some variable fields

### Working with Nested Fields

When your struct contains other `Wizard`-derived types, the fields are automatically namespaced with dot notation to avoid conflicts:

```rust,no_run
use derive_wizard::Wizard;

#[derive(Debug, Clone, Wizard)]
struct Address {
    #[prompt("Street:")]
    street: String,
    
    #[prompt("City:")]
    city: String,
}

#[derive(Debug, Clone, Wizard)]
struct UserProfile {
    #[prompt("Name:")]
    name: String,
    
    #[prompt("Home address:")]
    address: Address,  // Nested Wizard type
}

// The nested Address fields are automatically prefixed:
// - "address.street"
// - "address.city"
```

**Namespace Prefixing**: Each nested field is prefixed with its parent field name and a dot. This allows you to:

- Have duplicate field names in different nested structures
- Target specific nested fields with suggestions and assumptions
- Maintain a flat question list while preserving logical structure

#### Using the `field!` Macro for Nested Fields

To reference nested fields in `suggest_field()` or `assume_field()`, use the `field!` macro with dot notation:

```rust,no_run
use derive_wizard::{Wizard, field};

# #[derive(Debug, Clone, Wizard)]
# struct Address {
#     #[prompt("Street:")]
#     street: String,
#     #[prompt("City:")]
#     city: String,
# }
# 
# #[derive(Debug, Clone, Wizard)]
# struct UserProfile {
#     #[prompt("Name:")]
#     name: String,
#     #[prompt("Address:")]
#     address: Address,
# }
let profile = UserProfile::wizard_builder()
    .suggest_field(field!(name), "John Doe".to_string())
    .suggest_field(
        field!(UserProfile::address::street),
        "123 Main St".to_string()
    )
    .assume_field(
        field!(UserProfile::address::city),
        "Springfield".to_string()
    )
    .build();
```

The `field!` macro supports:

- Simple fields: `field!(name)``"name"`
- One level nesting: `field!(Type::field)``"field"`
- Two level nesting: `field!(Type::nested::field)``"nested.field"`

#### Handling Duplicate Field Names

Namespace prefixing automatically handles duplicate field names across different nested structures:

```rust,no_run
use derive_wizard::{Wizard, field};

#[derive(Debug, Clone, Wizard)]
struct Department {
    #[prompt("Department name:")]
    name: String,
    
    #[prompt("Budget:")]
    budget: i32,
}

#[derive(Debug, Clone, Wizard)]
struct Organization {
    #[prompt("Organization name:")]
    name: String,  // Same field name as Department
    
    #[prompt("Primary department:")]
    primary: Department,
    #[prompt("Secondary department:")]
    secondary: Department,
}

// Each 'name' field gets a unique path:
// - "name" (Organization.name)
// - "primary.name" (primary Department.name)
// - "secondary.name" (secondary Department.name)

let org = Organization::wizard_builder()
    .suggest_field(field!(name), "Acme Corp".to_string())
    .assume_field(
        field!(Organization::primary::name),
        "Engineering".to_string()
    )
    .assume_field(
        field!(Organization::secondary::name),
        "Sales".to_string()
    )
    .build();
```

This namespace approach ensures that:

- No field names collide, even with identical names in different nested structures
- You can precisely target any field using the `field!` macro
- The interview remains a flat list of questions (no complex nesting UI)

## Supported Question Types

The `#[derive(Wizard)]` macro supports all 11 requestty question types:

| Rust Type                          | Default Question Type | Override Options                                    | Returns              |
|------------------------------------|-----------------------|-----------------------------------------------------|----------------------|
| `String`                           | `input`               | `#[mask]` for password, `#[multiline]` for text editor | `String`             |
| `bool`                             | `confirm`             | -                                                   | `bool`               |
| `i8`, `i16`, `i32`, `i64`, `isize` | `int`                 | -                                                   | `i64` (cast to type) |
| `u8`, `u16`, `u32`, `u64`, `usize` | `int`                 | -                                                   | `i64` (cast to type) |
| `f32`, `f64`                       | `float`               | -                                                   | `f64` (cast to type) |
| `ListItem`                         | `select`              | -                                                   | `ListItem`           |
| `ExpandItem`                       | `expand`              | -                                                   | `ExpandItem`         |
| `Vec<ListItem>`                    | `multi_select`        | -                                                   | `Vec<ListItem>`      |

## Question Type Details

1. **input** - Basic text input prompt (default for String)
2. **password** - Hidden text input (use `#[mask]` on String fields)
3. **editor** - Opens text editor for longer input (use `#[multiline]` on String fields)
4. **confirm** - Yes/No confirmation prompt (default for bool)
5. **int** - Integer input (default for integer types)
6. **float** - Floating point input (default for float types)
7. **select** - Single selection from a list (default for `ListItem`)
8. **expand** - Single selection with keyboard shortcuts (default for `ExpandItem`)
9. **`multi_select`** - Multiple selection from a list (default for `Vec<ListItem>`)

Note: The following question types are available in requestty but not currently exposed through attributes:

- **`raw_select`** - Single selection with index-based input
- **`order_select`** - Reorder items in a list

## License

MIT OR Apache-2.0