derive_wizard/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod backend;
4
5pub use backend::{AnswerValue, Answers, BackendError, InterviewBackend, TestBackend};
6pub use derive_wizard_macro::*;
7pub use derive_wizard_types::{interview, question};
8
9#[cfg(feature = "requestty-backend")]
10pub use backend::requestty_backend::RequesttyBackend;
11
12#[cfg(feature = "dialoguer-backend")]
13pub use backend::dialoguer_backend::DialoguerBackend;
14
15#[cfg(feature = "egui-backend")]
16pub use backend::egui_backend::EguiBackend;
17
18pub trait Wizard: Sized {
19    /// Get the interview structure for this type
20    fn interview() -> interview::Interview;
21
22    /// Get the interview structure with default values from this instance
23    fn interview_with_defaults(&self) -> interview::Interview;
24
25    /// Build this type from collected answers
26    fn from_answers(answers: &Answers) -> Result<Self, BackendError>;
27
28    /// Create a builder for this wizard
29    fn wizard_builder() -> WizardBuilder<Self> {
30        WizardBuilder::new()
31    }
32}
33
34/// Builder for configuring and executing a wizard
35#[derive(Default)]
36pub struct WizardBuilder<T: Wizard> {
37    defaults: Option<T>,
38    backend: Option<Box<dyn InterviewBackend>>,
39}
40
41impl<T: Wizard> WizardBuilder<T> {
42    /// Create a new wizard builder
43    pub fn new() -> Self {
44        Self {
45            defaults: None,
46            backend: None,
47        }
48    }
49
50    /// Set default values for the wizard
51    pub fn with_defaults(mut self, defaults: T) -> Self {
52        self.defaults = Some(defaults);
53        self
54    }
55
56    /// Set a custom backend
57    pub fn with_backend<B: InterviewBackend + 'static>(mut self, backend: B) -> Self {
58        self.backend = Some(Box::new(backend));
59        self
60    }
61
62    /// Execute the wizard and return the result
63    #[cfg(feature = "requestty-backend")]
64    pub fn build(self) -> T {
65        use crate::backend::requestty_backend::RequesttyBackend;
66
67        let backend = self.backend.unwrap_or_else(|| Box::new(RequesttyBackend));
68
69        let interview = if let Some(ref defaults) = self.defaults {
70            defaults.interview_with_defaults()
71        } else {
72            T::interview()
73        };
74
75        let answers = backend
76            .execute(&interview)
77            .expect("Failed to execute interview");
78        T::from_answers(&answers).expect("Failed to build from answers")
79    }
80
81    /// Execute the wizard and return the result (no default backend required)
82    #[cfg(not(feature = "requestty-backend"))]
83    pub fn build(self) -> T {
84        let backend = self
85            .backend
86            .expect("No backend specified and requestty-backend feature is not enabled");
87
88        let interview = if let Some(ref defaults) = self.defaults {
89            defaults.interview_with_defaults()
90        } else {
91            T::interview()
92        };
93
94        let answers = backend
95            .execute(&interview)
96            .expect("Failed to execute interview");
97        T::from_answers(&answers).expect("Failed to build from answers")
98    }
99}