origen/framework/
extensions.rs

1use super::aux_cmds::AuxCmdNamespace;
2use super::plugins::Plugin;
3use super::{Applies, CmdOptCache, CmdSrc, Opt, OptTOML};
4use crate::from_toml_opts;
5use clap::Command as ClapCommand;
6use origen::Result;
7use std::collections::HashMap;
8use std::fmt;
9use std::path::PathBuf;
10
11macro_rules! ext_opt {
12    () => {
13        "ext_opt"
14    };
15}
16
17pub const EXT_BASE_NAME: &'static str = ext_opt!();
18pub const EXT_BASE_PREFIX: &'static str = concat!(ext_opt!(), ".");
19
20#[derive(Debug)]
21pub struct Extensions {
22    extensions: HashMap<CmdSrc, Vec<Extension>>,
23}
24
25impl Extensions {
26    pub fn new() -> Self {
27        Self {
28            extensions: HashMap::new(),
29        }
30    }
31
32    pub fn exts(&self) -> &HashMap<CmdSrc, Vec<Extension>> {
33        &self.extensions
34    }
35
36    fn add_ext<F>(&mut self, t: ExtensionTOML, f: F) -> Result<bool>
37    where
38        F: Fn(ExtensionTOML) -> Result<Option<Extension>>,
39    {
40        let c = CmdSrc::new(&t.extend)?;
41        if let Some(e) = f(t)? {
42            self.extensions.entry(c).or_default().push(e);
43            Ok(true)
44        } else {
45            // Extension doesn't apply in this context/env.
46            Ok(false)
47        }
48    }
49
50    pub fn add_from_app_toml(&mut self, ext_toml: ExtensionTOML) -> Result<bool> {
51        self.add_ext(ext_toml, |t| {
52            Extension::from_extension_toml(ExtensionSource::App, t)
53        })
54    }
55
56    pub fn add_from_pl_toml(&mut self, pl: &Plugin, ext_toml: ExtensionTOML) -> Result<bool> {
57        self.add_ext(ext_toml, |t| {
58            Extension::from_extension_toml(ExtensionSource::Plugin(pl.name.to_owned()), t)
59        })
60    }
61
62    pub fn add_from_aux_toml(
63        &mut self,
64        ns: &AuxCmdNamespace,
65        ext_toml: ExtensionTOML,
66    ) -> Result<bool> {
67        self.add_ext(ext_toml, |t| {
68            Extension::from_extension_toml(ExtensionSource::Aux(ns.namespace(), ns.root()), t)
69        })
70    }
71
72    pub fn apply_to_core_cmd<'a>(&'a self, cmd: &str, app: ClapCommand) -> ClapCommand {
73        let e = CmdSrc::Core(cmd.to_string());
74        let mut cache = CmdOptCache::unchecked_populated(&app, e.to_string());
75        self.apply_to(&e, app, &mut cache)
76    }
77
78    pub fn apply_to_app_cmd<'a>(
79        &'a self,
80        cmd: &str,
81        app: ClapCommand,
82        cache: &mut CmdOptCache,
83    ) -> ClapCommand {
84        self.apply_to(&CmdSrc::App(cmd.to_string()), app, cache)
85    }
86
87    pub fn apply_to_pl_cmd<'a>(
88        &'a self,
89        pl: &str,
90        cmd: &str,
91        app: ClapCommand,
92        cache: &mut CmdOptCache,
93    ) -> ClapCommand {
94        self.apply_to(&CmdSrc::Plugin(pl.to_string(), cmd.to_string()), app, cache)
95    }
96
97    pub fn apply_to_aux_cmd<'a>(
98        &'a self,
99        ns: &str,
100        cmd: &str,
101        app: ClapCommand,
102        cache: &mut CmdOptCache,
103    ) -> ClapCommand {
104        self.apply_to(&CmdSrc::Aux(ns.to_string(), cmd.to_string()), app, cache)
105    }
106
107    // Apply any extensions, returning an unaltered command if no extensions are available for this command.
108    pub fn apply_to<'a>(
109        &'a self,
110        cmd: &CmdSrc,
111        mut app: ClapCommand,
112        cache: &mut CmdOptCache,
113    ) -> ClapCommand {
114        if let Some(exts) = self.extensions.get(cmd) {
115            for ext in exts {
116                if let Some(opts) = ext.opts.as_ref() {
117                    app = super::apply_opts(opts, app, cache, Some(ext));
118                }
119            }
120        }
121        app
122    }
123
124    pub fn get_core_ext(&self, cmd_path: &str) -> Option<&Vec<Extension>> {
125        self.extensions.get(&CmdSrc::Core(cmd_path.to_string()))
126    }
127
128    pub fn get_app_ext(&self, cmd_path: &str) -> Option<&Vec<Extension>> {
129        self.extensions.get(&CmdSrc::App(cmd_path.to_string()))
130    }
131
132    pub fn get_pl_ext(&self, pl: &str, cmd_path: &str) -> Option<&Vec<Extension>> {
133        self.extensions
134            .get(&CmdSrc::Plugin(pl.to_string(), cmd_path.to_string()))
135    }
136
137    pub fn get_aux_ext(&self, ns: &str, cmd_path: &str) -> Option<&Vec<Extension>> {
138        self.extensions
139            .get(&CmdSrc::Aux(ns.to_string(), cmd_path.to_string()))
140    }
141}
142
143#[derive(Debug, Deserialize)]
144pub struct ExtensionTOML {
145    pub extend: String,                  // Command to extend
146    pub in_global_context: Option<bool>, // Extend in the global context
147    pub in_app_context: Option<bool>,    // Extend in application context
148    pub on_env: Option<Vec<String>>,
149    pub opt: Option<Vec<OptTOML>>,
150    // TODO see about supporting some of these in the future?
151    // pub name: String,
152    // pub help: String,
153    // pub alias: Option<String>,
154    // pub arg: Option<Vec<Arg>>,
155    // pub subcommands: Option<Vec<String>>,
156    // pub full_name: String,
157}
158
159#[derive(Debug, Hash, Eq, PartialEq, Clone)]
160pub enum ExtensionSource {
161    App,
162    Plugin(String),
163    Aux(String, PathBuf),
164}
165
166impl ExtensionSource {
167    pub fn to_path(&self) -> String {
168        match self {
169            Self::App => "app".to_string(),
170            Self::Plugin(pl_name) => format!("plugin.{pl_name}"),
171            Self::Aux(ns, _) => format!("aux.{ns}"),
172        }
173    }
174}
175
176impl fmt::Display for ExtensionSource {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        match self {
179            Self::App => write!(f, "the App"),
180            Self::Plugin(pl_name) => write!(f, "plugin '{}'", pl_name),
181            Self::Aux(ns, _) => write!(f, "aux namespace '{}'", ns),
182        }
183    }
184}
185
186#[derive(Debug)]
187pub struct Extension {
188    pub extends: CmdSrc,
189    pub in_global_context: Option<bool>,
190    pub in_app_context: Option<bool>,
191    pub on_env: Option<Vec<String>>,
192    pub opts: Option<Vec<Opt>>,
193
194    pub source: ExtensionSource,
195}
196
197impl Extension {
198    pub fn from_extension_toml(
199        ext_source: ExtensionSource,
200        ext: ExtensionTOML,
201    ) -> Result<Option<Self>> {
202        let mut slf = Self {
203            in_global_context: ext.in_global_context,
204            in_app_context: ext.in_app_context,
205            on_env: ext.on_env,
206            opts: None,
207            source: ext_source,
208            extends: CmdSrc::new(&ext.extend)?,
209        };
210        if !slf.applies()? {
211            return Ok(None);
212        }
213        slf.opts = from_toml_opts!(
214            ext.opt,
215            &slf.extends.to_string(),
216            Some(&slf.source.to_string())
217        );
218        if let Some(opts) = slf.opts.as_mut() {
219            for opt in opts {
220                opt.help += &format!(
221                    " [Extended from {}]",
222                    match slf.source {
223                        ExtensionSource::App => {
224                            "the app".to_string()
225                        }
226                        ExtensionSource::Plugin(ref pl_name) => {
227                            format!("plugin: '{}'", pl_name)
228                        }
229                        ExtensionSource::Aux(ref ns, _) => {
230                            format!("aux namespace: '{}'", ns)
231                        }
232                    }
233                );
234                opt.full_name = Some(format!(
235                    "{}.{}.{}",
236                    EXT_BASE_NAME,
237                    slf.source.to_path(),
238                    opt.name
239                ));
240            }
241        }
242        Ok(Some(slf))
243    }
244}
245
246impl Applies for Extension {
247    fn in_global_context(&self) -> Option<bool> {
248        self.in_global_context
249    }
250
251    fn in_app_context(&self) -> Option<bool> {
252        self.in_app_context
253    }
254
255    fn on_env(&self) -> Option<&Vec<String>> {
256        self.on_env.as_ref()
257    }
258
259    fn on_env_error_msg(&self, e: &String) -> String {
260        format!(
261            "Failed to parse 'on_env' '{}', extending '{}', for {}",
262            e, self.extends, self.source
263        )
264    }
265}