seaplane_cli/cli/cmds/
init.rs

1use std::fs;
2
3use clap::{ArgMatches, Command};
4
5use crate::{cli::CliCommand, config::RawConfig, context::Ctx, error::Result, fs::conf_dirs};
6
7static LONG_FORCE: &str =
8    "Force create the files and directories (DANGER: will overwrite existing files)
9
10Using --force is the same as using --overwrite=all";
11static LONG_OVERWRITE: &str =
12    "Overwrite select files or directories (DANGER: will overwrite existing data)
13
14Using --overwrite=all is the same as using --force
15
16Multiple items can be passed as a comma separated list, or by using the argument
17multiple times.";
18
19#[derive(Copy, Clone, Debug)]
20pub struct SeaplaneInit;
21
22impl SeaplaneInit {
23    pub fn command() -> Command {
24        Command::new("init")
25            .about("Create the Seaplane directory structure at the appropriate locations")
26            .arg(arg!(--force)
27                .help("Force create the files and directories (DANGER: will overwrite existing files)")
28                .long_help(LONG_FORCE))
29            .arg(arg!(--overwrite =["ITEM"]...)
30                .help("Overwrite select files or directories (DANGER: will overwrite existing data) (supports comma separated list, or multiple uses)")
31                .long_help(LONG_OVERWRITE)
32                .value_parser(["all", "formations", "flights", "config"]))
33    }
34}
35
36impl CliCommand for SeaplaneInit {
37    fn run(&self, ctx: &mut Ctx) -> Result<()> {
38        // Create the data directory
39        cli_debugln!("Creating or using data directory {:?}", ctx.data_dir());
40        fs::create_dir_all(ctx.data_dir())?;
41
42        // We only create the first (most preferred) configuration dir. If the user creates more
43        // down our search path, that's fine, but we only create and advertise the first.
44        let conf_dir = &conf_dirs()[0];
45        cli_debugln!("Creating or using config directory {:?}", conf_dir);
46        fs::create_dir_all(conf_dir)?;
47
48        // Tuple below is: (File, "empty" bytes, it's --force=OPTION)
49        let to_create = &[
50            (
51                conf_dir.join("seaplane.toml"),
52                toml::to_string_pretty(&RawConfig::default()).unwrap(),
53                "config",
54            ),
55            (ctx.formations_file(), "{}".to_string(), "formations"),
56            (ctx.flights_file(), "[]".to_string(), "flights"),
57        ];
58        // TODO: @security create the file with limited permissions
59        let mut did_create = false;
60        for (file, empty_bytes, opt) in to_create {
61            if file.exists() && !(ctx.did_init || ctx.internal_run) {
62                // Due to how match guards work, we can't use them, we have to use if-else
63                if ctx.args.force
64                    || ctx
65                        .args
66                        .overwrite
67                        .iter()
68                        .any(|item| item == opt || item == "all")
69                {
70                    cli_warn!(@Yellow, "warn: ");
71                    cli_warn!("overwriting existing file ");
72                    cli_warn!("{:?} ", file);
73                    cli_warn!("due to '");
74                    cli_warn!(@Green, "{}", if ctx.args.force { "--force".into() } else { format!("--overwrite={opt}")});
75                    cli_warnln!(@noprefix, "'\n");
76                } else {
77                    // We only want to advertise the *least* destructive option, not --force or
78                    // --overwrite=all. The user can find those on their own.
79                    cli_warn!(@Yellow, "warn: ");
80                    cli_warn!("{:?} ", file);
81                    cli_warnln!(@noprefix, "already exists");
82                    cli_warn!("(hint: use '");
83                    cli_warn!(@Green, "seaplane init --overwrite={}", opt);
84                    cli_warnln!(@noprefix, "' to erase and overwrite it)\n");
85                    continue;
86                }
87            }
88            did_create = true;
89            cli_debugln!("creating file {:?}", file);
90            fs::write(file, empty_bytes)?;
91        }
92
93        if !ctx.internal_run {
94            if did_create {
95                cli_println!("Successfully created Seaplane files and directories");
96            } else {
97                cli_println!("All Seaplane files and directories already exist");
98            }
99        }
100
101        Ok(())
102    }
103
104    fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
105        ctx.args.force = matches.get_flag("force");
106        ctx.args.overwrite = matches
107            .get_many::<String>("overwrite")
108            .unwrap_or_default()
109            .map(ToOwned::to_owned)
110            .collect();
111
112        Ok(())
113    }
114}