seaplane_cli/cli/cmds/formation/
plan.rs1use clap::{ArgMatches, Command};
2use const_format::concatcp;
3
4#[cfg(not(any(feature = "ui_tests", feature = "semantic_ui_tests")))]
5use crate::cli::cmds::flight::SeaplaneFlightPlan;
6use crate::{
7 cli::{
8 cmds::formation::{common, SeaplaneFormationFetch, SeaplaneFormationLaunch},
9 specs::{FLIGHT_SPEC, REGION_SPEC},
10 CliCommand,
11 },
12 context::{Ctx, FlightCtx},
13 error::{CliErrorKind, Context, Result},
14 ops::formation::{Formation, FormationConfiguration},
15 printer::Color,
16};
17
18static LONG_ABOUT: &str =
19 "Make a new local Formation Plan (and optionally launch an instance of it)
20
21Include local Flight Plans by using `--include-flight-plan`. Multiple Flights may be included in a
22Formation Plan using a SEMICOLON separated list, or using the argument multiple times.
23
24You can also create a new Flight Plan using the INLINE-SPEC option of `--include-flight-plan`.
25
26Flight Plans created using INLINE-SPEC are automatically included in the Formation Plan.";
27
28#[allow(missing_debug_implementations)]
32pub struct SeaplaneFormationPlanArgMatches<'a>(pub &'a ArgMatches);
33
34#[derive(Copy, Clone, Debug)]
35pub struct SeaplaneFormationPlan;
36
37impl SeaplaneFormationPlan {
38 pub fn command() -> Command {
39 Command::new("plan")
40 .after_help(concatcp!(FLIGHT_SPEC, "\n\n", REGION_SPEC))
41 .visible_aliases(["create", "add"])
42 .about("Create a Seaplane Formation")
43 .long_about(LONG_ABOUT)
44 .args(common::args())
45 .arg(arg!(--force).help("Override any existing Formation with the same NAME"))
46 .arg(arg!(--fetch|sync|synchronize - ('F')).help("Fetch remote instances prior to creating this plan to check for conflicts (by default only local references are considered)"))
47 }
48}
49
50impl CliCommand for SeaplaneFormationPlan {
51 fn run(&self, ctx: &mut Ctx) -> Result<()> {
52 if ctx.args.fetch {
53 let old_name = ctx.args.name_id.take();
54 ctx.internal_run = true;
55 SeaplaneFormationFetch.run(ctx)?;
56 ctx.internal_run = false;
57 ctx.args.name_id = old_name;
58 }
59
60 let formation_ctx = ctx.formation_ctx.get_or_init();
61
62 let name = &formation_ctx.name_id;
64 if ctx.db.formations.contains_name(name) {
65 if !ctx.args.force {
66 let mut err = CliErrorKind::DuplicateName(name.to_owned())
67 .into_err()
68 .context("(hint: try '")
69 .color_context(Color::Green, format!("seaplane formation edit {}", &name))
70 .context("' instead)\n");
71 if ctx.db.needs_persist {
72 err = err.context("\nRolling back created Flight Plans!\n");
73 }
74 return Err(err);
75 }
76
77 ctx.db.formations.remove_name(name);
84 }
85
86 if ctx.db.needs_persist {
87 ctx.persist_flights()?;
90 ctx.db.needs_persist = false;
91 }
92
93 let mut new_formation = Formation::new(&formation_ctx.name_id);
95
96 let cfg = formation_ctx.configuration_model(ctx)?;
97 let formation_cfg = FormationConfiguration::new(cfg);
98 new_formation.local.insert(formation_cfg.id);
99 ctx.db.formations.configurations.push(formation_cfg);
100
101 let id = new_formation.id.to_string();
102 ctx.db.formations.formations.push(new_formation);
103
104 ctx.persist_formations()?;
105
106 cli_print!("Successfully created local Formation Plan '");
107 cli_print!(@Green, "{}", &formation_ctx.name_id);
108 cli_print!("' with ID '");
109 cli_print!(@Green, "{}", &id[..8]);
110 cli_println!("'");
111
112 if formation_ctx.launch || formation_ctx.grounded {
114 ctx.args.name_id = Some(formation_ctx.name_id.clone());
116 ctx.args.exact = true;
118 ctx.args.fetch = false;
120 SeaplaneFormationLaunch.run(ctx)?;
122 }
123
124 Ok(())
125 }
126
127 fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
128 ctx.args.fetch = matches.get_flag("fetch");
129 ctx.args.force = matches.get_flag("force");
130
131 let mut flights: Vec<_> = matches
133 .get_many::<String>("include-flight-plan")
134 .unwrap_or_default()
135 .collect();
136
137 let inline_flights = vec_remove_if!(flights, |f: &str| f.contains('='));
139 for flight in inline_flights {
140 let mut cloned_ctx = ctx.clone();
141 cloned_ctx.args.stateless = true;
144 cloned_ctx.internal_run = true;
145 cloned_ctx
146 .flight_ctx
147 .init(FlightCtx::from_inline_flight(flight, &ctx.registry)?);
148
149 #[cfg(not(any(feature = "ui_tests", feature = "semantic_ui_tests")))]
150 {
151 let flight_plan: Box<dyn CliCommand> = Box::new(SeaplaneFlightPlan);
152 flight_plan.run(&mut cloned_ctx)?;
153 }
154
155 let name = cloned_ctx.flight_ctx.get_or_init().name_id.clone();
156 #[cfg(not(any(feature = "ui_tests", feature = "semantic_ui_tests")))]
158 ctx.db
159 .flights
160 .add_flight(cloned_ctx.db.flights.remove_flight(&name, true).unwrap());
161
162 ctx.formation_ctx
165 .get_mut_or_init()
166 .cfg_ctx
167 .flights
168 .push(name);
169
170 ctx.db.needs_persist = true;
171 }
172
173 #[cfg(not(any(feature = "ui_tests", feature = "semantic_ui_tests")))]
175 for name in ctx
176 .db
177 .flights
178 .add_from_at_strs(vec_remove_if!(flights, |f: &str| f.starts_with('@')))?
179 {
180 ctx.formation_ctx
181 .get_mut_or_init()
182 .cfg_ctx
183 .flights
184 .push(name);
185 ctx.db.needs_persist = true;
186 }
187
188 ctx.formation_ctx
192 .get_mut_or_init()
193 .update_from_formation_plan(
194 &SeaplaneFormationPlanArgMatches(matches),
195 &ctx.db.flights,
196 )?;
197
198 Ok(())
199 }
200}