Skip to main content

greentic_bundle/cli/
wizard.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::{Args, Subcommand, ValueEnum};
5
6#[derive(Debug, Args)]
7pub struct WizardArgs {
8    #[arg(
9        long,
10        global = true,
11        default_value_t = false,
12        help = "cli.option.schema",
13        long_help = "cli.option.schema.long"
14    )]
15    pub schema: bool,
16    #[command(subcommand)]
17    pub command: Option<WizardCommand>,
18}
19
20#[derive(Debug, Subcommand)]
21pub enum WizardCommand {
22    #[command(about = "cli.wizard.run.about")]
23    Run(WizardRunArgs),
24    #[command(about = "cli.wizard.validate.about")]
25    Validate(WizardValidateArgs),
26    #[command(about = "cli.wizard.apply.about")]
27    Apply(WizardApplyArgs),
28}
29
30#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
31pub enum WizardMode {
32    Create,
33    Update,
34    Doctor,
35}
36
37#[derive(Debug, Args, Default)]
38pub struct WizardRunArgs {
39    #[arg(long, value_name = "FILE", help = "cli.option.answers")]
40    pub answers: Option<PathBuf>,
41    #[arg(
42        long = "emit-answers",
43        value_name = "FILE",
44        help = "cli.option.emit_answers"
45    )]
46    pub emit_answers: Option<PathBuf>,
47    #[arg(
48        long = "schema-version",
49        value_name = "VER",
50        help = "cli.option.schema_version"
51    )]
52    pub schema_version: Option<String>,
53    #[arg(long, default_value_t = false, help = "cli.option.migrate")]
54    pub migrate: bool,
55    #[arg(long, default_value_t = false, help = "cli.option.dry_run")]
56    pub dry_run: bool,
57    #[arg(long, value_enum, help = "cli.wizard.mode.option")]
58    pub mode: Option<WizardMode>,
59}
60
61#[derive(Debug, Args)]
62pub struct WizardValidateArgs {
63    #[arg(long, value_name = "FILE", help = "cli.option.answers")]
64    pub answers: PathBuf,
65    #[arg(
66        long = "emit-answers",
67        value_name = "FILE",
68        help = "cli.option.emit_answers"
69    )]
70    pub emit_answers: Option<PathBuf>,
71    #[arg(
72        long = "schema-version",
73        value_name = "VER",
74        help = "cli.option.schema_version"
75    )]
76    pub schema_version: Option<String>,
77    #[arg(long, default_value_t = false, help = "cli.option.migrate")]
78    pub migrate: bool,
79    #[arg(long, value_enum, help = "cli.wizard.mode.option")]
80    pub mode: Option<WizardMode>,
81}
82
83#[derive(Debug, Args)]
84pub struct WizardApplyArgs {
85    #[arg(long, value_name = "FILE", help = "cli.option.answers")]
86    pub answers: PathBuf,
87    #[arg(
88        long = "emit-answers",
89        value_name = "FILE",
90        help = "cli.option.emit_answers"
91    )]
92    pub emit_answers: Option<PathBuf>,
93    #[arg(
94        long = "schema-version",
95        value_name = "VER",
96        help = "cli.option.schema_version"
97    )]
98    pub schema_version: Option<String>,
99    #[arg(long, default_value_t = false, help = "cli.option.migrate")]
100    pub migrate: bool,
101    #[arg(long, default_value_t = false, help = "cli.option.dry_run")]
102    pub dry_run: bool,
103    #[arg(long, value_enum, help = "cli.wizard.mode.option")]
104    pub mode: Option<WizardMode>,
105}
106
107pub fn run(args: WizardArgs) -> Result<()> {
108    if args.schema {
109        let schema =
110            crate::wizard::answer_document_schema(args.schema_mode(), args.schema_version())?;
111        println!("{}", serde_json::to_string_pretty(&schema)?);
112        return Ok(());
113    }
114
115    match args.command {
116        None => {
117            let zero_action = embedded_root_zero_action();
118            loop {
119                let result = match crate::wizard::run_interactive_with_zero_action(
120                    None,
121                    None,
122                    None,
123                    crate::wizard::ExecutionMode::Execute,
124                    zero_action,
125                ) {
126                    Ok(result) => result,
127                    Err(error) if error.to_string() == crate::i18n::tr("wizard.exit.message") => {
128                        return Ok(());
129                    }
130                    Err(error) => return Err(error),
131                };
132                let Some(result) = result else {
133                    return Ok(());
134                };
135                crate::wizard::print_plan(&result.plan)?;
136            }
137        }
138        Some(WizardCommand::Run(args)) => crate::wizard::run_command(args),
139        Some(WizardCommand::Validate(args)) => crate::wizard::validate_command(args),
140        Some(WizardCommand::Apply(args)) => crate::wizard::apply_command(args),
141    }
142}
143
144impl WizardArgs {
145    fn schema_mode(&self) -> Option<WizardMode> {
146        match self.command.as_ref() {
147            Some(WizardCommand::Run(args)) => args.mode,
148            Some(WizardCommand::Validate(args)) => args.mode,
149            Some(WizardCommand::Apply(args)) => args.mode,
150            None => None,
151        }
152    }
153
154    fn schema_version(&self) -> Option<&str> {
155        match self.command.as_ref() {
156            Some(WizardCommand::Run(args)) => args.schema_version.as_deref(),
157            Some(WizardCommand::Validate(args)) => args.schema_version.as_deref(),
158            Some(WizardCommand::Apply(args)) => args.schema_version.as_deref(),
159            None => None,
160        }
161    }
162}
163
164fn embedded_root_zero_action() -> crate::wizard::RootMenuZeroAction {
165    match std::env::var("GREENTIC_WIZARD_ROOT_ZERO_ACTION")
166        .ok()
167        .as_deref()
168    {
169        Some("back") => crate::wizard::RootMenuZeroAction::Back,
170        _ => crate::wizard::RootMenuZeroAction::Exit,
171    }
172}