origen/framework/
helps.rs

1use super::extensions::ExtensionSource;
2use crate::commands::_prelude::*;
3use origen_metal::indexmap::IndexSet;
4use std::collections::HashMap;
5use std::fmt;
6
7pub const NOT_EXTENDABLE_MSG: &'static str = "This command does not support extensions.";
8
9#[derive(Debug)]
10pub struct CmdHelps {
11    helps: HashMap<CmdSrc, CmdHelp>,
12}
13
14impl CmdHelps {
15    pub fn new() -> Self {
16        Self {
17            helps: HashMap::new(),
18        }
19    }
20
21    pub fn core_cmd(&self, cmd: &str) -> Command {
22        self.apply_core_cmd_helps(cmd, Command::new(cmd.to_string()))
23    }
24
25    pub fn core_subc(&self, cmd_path: &[&str]) -> Command {
26        self.apply_core_subc_helps(cmd_path, Command::new(cmd_path.last().unwrap().to_string()))
27    }
28
29    pub fn add_core_cmd(&mut self, cmd_name: &str) -> &mut CmdHelp {
30        self.helps
31            .entry(CmdSrc::Core(cmd_name.to_string()))
32            .or_default()
33    }
34
35    pub fn add_core_sub_cmd(&mut self, cmd_path: &[&str]) -> &mut CmdHelp {
36        self.helps
37            .entry(CmdSrc::Core(cmd_path.join(".")))
38            .or_default()
39    }
40
41    pub fn add_app_cmd(&mut self, cmd_name: &str) -> &mut CmdHelp {
42        self.helps
43            .entry(CmdSrc::App(cmd_name.to_string()))
44            .or_default()
45    }
46
47    pub fn add_pl_cmd(&mut self, pl_name: &str, cmd_name: &str) -> &mut CmdHelp {
48        self.helps
49            .entry(CmdSrc::Plugin(pl_name.to_string(), cmd_name.to_string()))
50            .or_default()
51    }
52
53    pub fn add_aux_cmd(&mut self, ns: &str, cmd_name: &str) -> &mut CmdHelp {
54        self.helps
55            .entry(CmdSrc::Aux(ns.to_string(), cmd_name.to_string()))
56            .or_default()
57    }
58
59    pub fn apply_core_cmd_helps<'a>(&'a self, cmd_name: &str, app: Command) -> Command {
60        self.apply_helps(&CmdSrc::Core(cmd_name.to_string()), app)
61    }
62
63    pub fn apply_core_subc_helps<'a>(&'a self, cmd_path: &[&str], app: Command) -> Command {
64        self.apply_helps(&CmdSrc::Core(cmd_path.join(".")), app)
65    }
66
67    pub fn apply_helps<'a>(&'a self, cmd_src: &CmdSrc, mut app: Command) -> Command {
68        if let Some(helps) = self.helps.get(cmd_src) {
69            if let Some(h) = helps.before_help.as_ref() {
70                app = app.before_help(h.clone());
71            }
72            if let Some(h) = helps.help.as_ref() {
73                app = app.about(h.clone());
74            }
75            if let Some(h) = helps.after_help.as_ref() {
76                app = app.after_help(h.clone());
77            }
78        } else {
79            log_error!(
80                "Could not apply help messages to {} - no such command found",
81                cmd_src
82            );
83        }
84        app
85    }
86
87    pub fn apply_exts(&mut self, extensions: &Extensions) {
88        for (target, exts) in extensions.exts() {
89            if let Some(help) = self.helps.get_mut(&target) {
90                if !help.extendable {
91                    log_error!("Command '{}' does not support extensions but an extension was attempted from:", target);
92                    for ext in exts {
93                        log_error!("\t{}", ext.source);
94                    }
95                    continue;
96                }
97
98                let mut extended_from_app = false;
99                let mut pls: IndexSet<&str> = IndexSet::new();
100                let mut nspaces: IndexSet<&str> = IndexSet::new();
101                for ext in exts.iter() {
102                    match ext.source {
103                        ExtensionSource::App => extended_from_app = true,
104                        ExtensionSource::Plugin(ref n) => {
105                            pls.insert(n);
106                        }
107                        ExtensionSource::Aux(ref n, _) => {
108                            nspaces.insert(n);
109                        }
110                    }
111                }
112                let mut msg = "This command is extended from:".to_string();
113                if extended_from_app {
114                    msg += "\n    - the App";
115                }
116                if !pls.is_empty() {
117                    msg += &format!(
118                        "\n    - Plugins: {}",
119                        pls.iter()
120                            .map(|n| format!("'{}'", n))
121                            .collect::<Vec<String>>()
122                            .join(", ")
123                    );
124                }
125                if !nspaces.is_empty() {
126                    msg += &format!(
127                        "\n    - Aux Namespaces: {}",
128                        nspaces
129                            .iter()
130                            .map(|n| format!("'{}'", n))
131                            .collect::<Vec<String>>()
132                            .join(", ")
133                    );
134                }
135                if let Some(after) = help.after_help.as_ref() {
136                    help.after_help = Some(after.to_string() + "\n\n" + &msg);
137                } else {
138                    help.after_help = Some(msg);
139                }
140            } else {
141                log_error!("Tried to extend unknown command '{}' from:", target);
142                for ext in exts {
143                    log_error!("\t{}", ext.source);
144                }
145            }
146        }
147    }
148}
149
150#[derive(Debug)]
151pub struct CmdHelp {
152    help: Option<String>,
153    after_help: Option<String>,
154    before_help: Option<String>,
155    extendable: bool,
156}
157
158impl Default for CmdHelp {
159    fn default() -> Self {
160        Self {
161            help: None,
162            after_help: None,
163            before_help: None,
164            extendable: true,
165        }
166    }
167}
168
169impl CmdHelp {
170    pub fn set_help_msg(&mut self, help_msg: &str) -> &mut Self {
171        self.help = Some(help_msg.to_string());
172        self
173    }
174
175    pub fn set_as_not_extendable(&mut self) -> &mut Self {
176        self.extendable = false;
177        if let Some(h) = self.after_help.as_mut() {
178            self.after_help = Some(format!("{h}\n\n{NOT_EXTENDABLE_MSG}"));
179        } else {
180            self.after_help = Some(NOT_EXTENDABLE_MSG.to_string());
181        }
182        self
183    }
184}
185
186#[derive(Debug, Hash, Eq, PartialEq)]
187pub enum CmdSrc {
188    Core(String),           // Core command
189    App(String),            // App command
190    Plugin(String, String), // Plugin command
191    Aux(String, String),    // Aux command
192}
193
194impl CmdSrc {
195    pub fn new(target: &str) -> Result<Self> {
196        let (scope, t) = target
197            .split_once('.')
198            .ok_or_else(|| format!("Could not discern scope from '{}'", target))?;
199        Ok(match scope {
200            "origen" => Self::Core(t.to_string()),
201            "app" => Self::App(t.to_string()),
202            "plugin" => {
203                let (pl_name, pl_t) = t
204                    .split_once('.')
205                    .ok_or_else(|| format!("Could not discern plugin from '{}'", t))?;
206                Self::Plugin(pl_name.to_string(), pl_t.to_string())
207            }
208            "aux" | "aux_ns" => {
209                let (ns_name, aux_t) = t.split_once('.').ok_or_else(|| {
210                    format!("Could not discern auxillary command namespace from '{}'", t)
211                })?;
212                Self::Aux(ns_name.to_string(), aux_t.to_string())
213            }
214            _ => bail!(
215                "Unknown target scope '{}'. Expected 'origen', 'app', 'aux', or 'plugin'",
216                scope
217            ),
218        })
219    }
220
221    pub fn offset_path(&self) -> &str {
222        match self {
223            Self::Core(cmd) | Self::App(cmd) => &cmd,
224            Self::Plugin(_, cmd) | Self::Aux(_, cmd) => &cmd,
225        }
226    }
227}
228
229impl fmt::Display for CmdSrc {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        match self {
232            Self::Core(cmd) => {
233                write!(f, "origen.{}", cmd)
234            }
235            Self::App(cmd) => {
236                write!(f, "app.{}", cmd)
237            }
238            Self::Plugin(pl_name, cmd) => {
239                write!(f, "plugin.{}.{}", pl_name, cmd)
240            }
241            Self::Aux(ns_name, cmd) => {
242                write!(f, "aux_ns.{}.{}", ns_name, cmd)
243            }
244        }
245    }
246}