spin_trigger/cli/
launch_metadata.rs

1use clap::CommandFactory;
2use serde::{Deserialize, Serialize};
3use std::ffi::OsString;
4
5use crate::{cli::FactorsTriggerCommand, Trigger};
6
7use super::RuntimeFactorsBuilder;
8
9/// Contains information about the trigger flags (and potentially
10/// in future configuration) that a consumer (such as `spin up`)
11/// can query using `--launch-metadata-only`.
12#[derive(Clone, Debug, Deserialize, Serialize)]
13pub struct LaunchMetadata {
14    all_flags: Vec<LaunchFlag>,
15}
16
17// This assumes no triggers that want to participate in multi-trigger
18// use positional arguments. This is a restriction we'll have to make
19// anyway: suppose triggers A and B both take one positional arg, and
20// the user writes `spin up 123 456` - which value would go to which trigger?
21#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
22struct LaunchFlag {
23    #[serde(skip_serializing_if = "Option::is_none")]
24    #[serde(default)]
25    short: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    #[serde(default)]
28    long: Option<String>,
29}
30
31impl LaunchMetadata {
32    pub fn infer<T: Trigger<B::Factors>, B: RuntimeFactorsBuilder>() -> Self {
33        let all_flags: Vec<_> = FactorsTriggerCommand::<T, B>::command()
34            .get_arguments()
35            .map(LaunchFlag::infer)
36            .collect();
37
38        LaunchMetadata { all_flags }
39    }
40
41    pub fn matches<'a>(&self, groups: &[Vec<&'a OsString>]) -> Vec<&'a OsString> {
42        let mut matches = vec![];
43
44        for group in groups {
45            if group.is_empty() {
46                continue;
47            }
48            if self.is_match(group[0]) {
49                matches.extend(group);
50            }
51        }
52
53        matches
54    }
55
56    fn is_match(&self, arg: &OsString) -> bool {
57        self.all_flags.iter().any(|f| f.is_match(arg))
58    }
59
60    pub fn is_group_match(&self, group: &[&OsString]) -> bool {
61        if group.is_empty() {
62            false
63        } else {
64            self.all_flags.iter().any(|f| f.is_match(group[0]))
65        }
66    }
67}
68
69impl LaunchFlag {
70    fn infer(arg: &clap::Arg) -> Self {
71        Self {
72            long: arg.get_long().map(|s| format!("--{s}")),
73            short: arg.get_short().map(|ch| format!("-{ch}")),
74        }
75    }
76
77    fn is_match(&self, candidate: &OsString) -> bool {
78        let Some(s) = candidate.to_str() else {
79            return false;
80        };
81        let candidate = Some(s.to_owned());
82
83        candidate == self.long || candidate == self.short
84    }
85}