derive-wizard
A Rust procedural macro that automatically generates interactive CLI wizards from struct definitions using requestty.
Showcase
use Wizard;
Password Fields with #[mask]
For password inputs, use the convenient #[mask] attribute to hide user input:
use Wizard;
Long Text with #[multiline]
For longer text input, use the #[multiline] attribute to open the user's preferred text editor:
use Wizard;
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:
use Wizard;
// Simple usage with default backend (requestty)
let config = wizard_builder.build.unwrap;
println!;
// Edit configuration with suggestions pre-filled
let updated_config = wizard_builder
.with_suggestions
.build
.unwrap;
println!;
Additional examples:
use Wizard;
#
#
// With custom backend (e.g., requestty)
let backend = new;
let config = wizard_builder
.with_backend
.build
.unwrap;
println!;
// Combine suggestions with custom backend
let backend = new;
let updated_config = wizard_builder
.with_suggestions
.with_backend
.build
.unwrap;
println!;
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():
use Wizard;
#
#
// Suggest specific fields, ask about all of them
let config = wizard_builder
.suggest_field
.suggest_field
.suggest_field
.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:
use Wizard;
#
#
// Assume specific fields, ask about the rest
let config = wizard_builder
.assume_field // Always use SSL in production
.assume_field // Standard HTTPS port
.build; // Will only ask about 'server'
Key differences:
- Suggestions (
with_suggestions()orsuggest_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:
use Wizard;
#
#
let config = wizard_builder
.suggest_field // Suggest (will ask)
.assume_field // Assume (will skip)
.assume_field // 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:
use Wizard;
// 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:
use ;
#
#
#
#
#
let profile = wizard_builder
.suggest_field
.suggest_field
.assume_field
.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:
use ;
// Each 'name' field gets a unique path:
// - "name" (Organization.name)
// - "primary.name" (primary Department.name)
// - "secondary.name" (secondary Department.name)
let org = wizard_builder
.suggest_field
.assume_field
.assume_field
.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
- input - Basic text input prompt (default for String)
- password - Hidden text input (use
#[mask]on String fields) - editor - Opens text editor for longer input (use
#[multiline]on String fields) - confirm - Yes/No confirmation prompt (default for bool)
- int - Integer input (default for integer types)
- float - Floating point input (default for float types)
- select - Single selection from a list (default for
ListItem) - expand - Single selection with keyboard shortcuts (default for
ExpandItem) multi_select- Multiple selection from a list (default forVec<ListItem>)
Note: The following question types are available in requestty but not currently exposed through attributes:
raw_select- Single selection with index-based inputorder_select- Reorder items in a list
License
MIT OR Apache-2.0