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    #[arg(long = "env", short = 'e', default_value = "local")]
60    pub env: String,
61}
62
63#[derive(Debug, Args)]
64pub struct WizardValidateArgs {
65    #[arg(long, value_name = "FILE", help = "cli.option.answers")]
66    pub answers: PathBuf,
67    #[arg(
68        long = "emit-answers",
69        value_name = "FILE",
70        help = "cli.option.emit_answers"
71    )]
72    pub emit_answers: Option<PathBuf>,
73    #[arg(
74        long = "schema-version",
75        value_name = "VER",
76        help = "cli.option.schema_version"
77    )]
78    pub schema_version: Option<String>,
79    #[arg(long, default_value_t = false, help = "cli.option.migrate")]
80    pub migrate: bool,
81    #[arg(long, value_enum, help = "cli.wizard.mode.option")]
82    pub mode: Option<WizardMode>,
83    #[arg(long = "env", short = 'e', default_value = "local")]
84    pub env: String,
85}
86
87#[derive(Debug, Args)]
88pub struct WizardApplyArgs {
89    #[arg(long, value_name = "FILE", help = "cli.option.answers")]
90    pub answers: PathBuf,
91    #[arg(
92        long = "emit-answers",
93        value_name = "FILE",
94        help = "cli.option.emit_answers"
95    )]
96    pub emit_answers: Option<PathBuf>,
97    #[arg(
98        long = "schema-version",
99        value_name = "VER",
100        help = "cli.option.schema_version"
101    )]
102    pub schema_version: Option<String>,
103    #[arg(long, default_value_t = false, help = "cli.option.migrate")]
104    pub migrate: bool,
105    #[arg(long, default_value_t = false, help = "cli.option.dry_run")]
106    pub dry_run: bool,
107    #[arg(long, value_enum, help = "cli.wizard.mode.option")]
108    pub mode: Option<WizardMode>,
109    #[arg(long = "env", short = 'e', default_value = "local")]
110    pub env: String,
111}
112
113pub fn run(args: WizardArgs) -> Result<()> {
114    if args.schema {
115        let schema =
116            crate::wizard::answer_document_schema(args.schema_mode(), args.schema_version())?;
117        println!("{}", serde_json::to_string_pretty(&schema)?);
118        return Ok(());
119    }
120
121    match args.command {
122        None => {
123            let zero_action = embedded_root_zero_action();
124            loop {
125                let result = match crate::wizard::run_interactive_with_zero_action(
126                    None,
127                    None,
128                    None,
129                    crate::wizard::ExecutionMode::Execute,
130                    zero_action,
131                    "local",
132                ) {
133                    Ok(result) => result,
134                    Err(error) if error.to_string() == crate::i18n::tr("wizard.exit.message") => {
135                        return Ok(());
136                    }
137                    Err(error) => return Err(error),
138                };
139                let Some(result) = result else {
140                    return Ok(());
141                };
142                crate::wizard::print_plan(&result.plan)?;
143            }
144        }
145        Some(WizardCommand::Run(args)) => crate::wizard::run_command(args),
146        Some(WizardCommand::Validate(args)) => crate::wizard::validate_command(args),
147        Some(WizardCommand::Apply(args)) => crate::wizard::apply_command(args),
148    }
149}
150
151impl WizardArgs {
152    fn schema_mode(&self) -> Option<WizardMode> {
153        match self.command.as_ref() {
154            Some(WizardCommand::Run(args)) => args.mode,
155            Some(WizardCommand::Validate(args)) => args.mode,
156            Some(WizardCommand::Apply(args)) => args.mode,
157            None => None,
158        }
159    }
160
161    fn schema_version(&self) -> Option<&str> {
162        match self.command.as_ref() {
163            Some(WizardCommand::Run(args)) => args.schema_version.as_deref(),
164            Some(WizardCommand::Validate(args)) => args.schema_version.as_deref(),
165            Some(WizardCommand::Apply(args)) => args.schema_version.as_deref(),
166            None => None,
167        }
168    }
169}
170
171fn embedded_root_zero_action() -> crate::wizard::RootMenuZeroAction {
172    match std::env::var("GREENTIC_WIZARD_ROOT_ZERO_ACTION")
173        .ok()
174        .as_deref()
175    {
176        Some("back") => crate::wizard::RootMenuZeroAction::Back,
177        _ => crate::wizard::RootMenuZeroAction::Exit,
178    }
179}