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 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 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, pub in_global_context: Option<bool>, pub in_app_context: Option<bool>, pub on_env: Option<Vec<String>>,
149 pub opt: Option<Vec<OptTOML>>,
150 }
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}