Skip to main content

vantage_cmd/
cmd.rs

1//! The [`Cmd`] datasource — a locked command, declared env, and a
2//! registry of per-table Rhai scripts.
3
4use std::sync::Arc;
5
6use indexmap::IndexMap;
7use vantage_core::{Result, error};
8
9/// Per-table configuration registered on a [`Cmd`]: the Rhai script that
10/// builds the argv and parses the output, plus optional command / env
11/// overrides that win over the datasource-level defaults.
12#[derive(Clone, Debug)]
13pub struct CmdSpec {
14    pub script: Arc<str>,
15    pub command: Option<String>,
16    pub env: IndexMap<String, String>,
17}
18
19impl CmdSpec {
20    pub fn new(script: impl Into<Arc<str>>) -> Self {
21        Self {
22            script: script.into(),
23            command: None,
24            env: IndexMap::new(),
25        }
26    }
27
28    /// Override the locked command for this table only.
29    pub fn with_command(mut self, command: impl Into<String>) -> Self {
30        self.command = Some(command.into());
31        self
32    }
33
34    /// Declare an env var for this table only (merged over, and winning
35    /// against, the datasource-level env).
36    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
37        self.env.insert(key.into(), value.into());
38        self
39    }
40}
41
42/// A command-execution datasource.
43///
44/// Cheap to clone (everything is `Arc`-backed). The `command` and `env`
45/// here are the locked defaults; individual tables can be registered with
46/// their own [`CmdSpec`] overrides via [`Cmd::with_table`].
47#[derive(Clone, Debug)]
48pub struct Cmd {
49    command: Arc<str>,
50    env: Arc<IndexMap<String, String>>,
51    pass_path: bool,
52    scripts: Arc<IndexMap<String, CmdSpec>>,
53}
54
55impl Cmd {
56    /// Build a datasource locked to `command` (e.g. `"aws"`).
57    pub fn new(command: impl Into<Arc<str>>) -> Self {
58        Self {
59            command: command.into(),
60            env: Arc::new(IndexMap::new()),
61            pass_path: true,
62            scripts: Arc::new(IndexMap::new()),
63        }
64    }
65
66    /// Declare a datasource-level env var, passed to every table's child
67    /// process unless a table overrides it.
68    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
69        Arc::make_mut(&mut self.env).insert(key.into(), value.into());
70        self
71    }
72
73    /// Whether to forward `PATH`/`HOME` from the current process so the
74    /// command can be located. Defaults to `true`; set `false` to require
75    /// an absolute command path and a fully-declared environment.
76    pub fn with_pass_path(mut self, pass_path: bool) -> Self {
77        self.pass_path = pass_path;
78        self
79    }
80
81    /// Register a script under `name` with no overrides.
82    pub fn with_script(self, name: impl Into<String>, script: impl Into<Arc<str>>) -> Self {
83        self.with_table(name, CmdSpec::new(script))
84    }
85
86    /// Register a fully-specified [`CmdSpec`] under `name`.
87    pub fn with_table(mut self, name: impl Into<String>, spec: CmdSpec) -> Self {
88        Arc::make_mut(&mut self.scripts).insert(name.into(), spec);
89        self
90    }
91
92    /// The locked default command.
93    pub fn command(&self) -> &str {
94        &self.command
95    }
96
97    pub(crate) fn pass_path(&self) -> bool {
98        self.pass_path
99    }
100
101    pub(crate) fn spec_for(&self, name: &str) -> Result<&CmdSpec> {
102        self.scripts.get(name).ok_or_else(|| {
103            error!(
104                "no command script registered for table",
105                table = name.to_string()
106            )
107        })
108    }
109
110    /// Effective command for a table: the spec override, else the locked default.
111    pub(crate) fn effective_command(&self, spec: &CmdSpec) -> String {
112        spec.command
113            .clone()
114            .unwrap_or_else(|| self.command.to_string())
115    }
116
117    /// Effective env for a table: datasource env, with the spec's env
118    /// merged on top (spec wins on key clash).
119    pub(crate) fn effective_env(&self, spec: &CmdSpec) -> IndexMap<String, String> {
120        let mut env = (*self.env).clone();
121        for (k, v) in &spec.env {
122            env.insert(k.clone(), v.clone());
123        }
124        env
125    }
126
127    /// A Vista factory bound to this datasource.
128    pub fn vista_factory(&self) -> crate::vista::factory::CmdVistaFactory {
129        crate::vista::factory::CmdVistaFactory::new(self.clone())
130    }
131}