origen/framework/
plugins.rs

1use super::helps::NOT_EXTENDABLE_MSG;
2use super::{build_path, ClapCommand, Command, CommandTOML, CommandsToml, Extensions};
3use crate::commands::_prelude::*;
4use crate::python;
5use indexmap::IndexMap;
6use origen::{Result, ORIGEN_CONFIG};
7use std::path::PathBuf;
8
9pub(crate) fn add_pl_ns_helps(helps: &mut CmdHelps, plugins: Option<&Plugins>) {
10    if let Some(pls) = plugins {
11        for (pl_name, pl) in pls.plugins.iter() {
12            for (n, c) in pl.commands.iter() {
13                helps.add_pl_cmd(pl_name, n).set_help_msg(&c.help);
14            }
15        }
16    }
17}
18
19pub(crate) fn add_pl_ns_subcmds<'a>(
20    mut pl_sub: App,
21    helps: &'a CmdHelps,
22    plugins: &'a Plugins,
23    exts: &'a Extensions,
24) -> Result<App> {
25    for (pl_name, pl) in plugins.plugins.iter() {
26        let mut pl_sub_sub = ClapCommand::new(pl_name.clone())
27            .arg_required_else_help(true)
28            .after_help(NOT_EXTENDABLE_MSG);
29        for n in pl.top_commands.iter() {
30            pl_sub_sub = pl_sub_sub.subcommand(super::build_commands(
31                &pl.commands.get(n).unwrap(),
32                &|cmd, app, opt_cache| exts.apply_to_pl_cmd(&pl_name, cmd, app, opt_cache),
33                &|cmd| {
34                    plugins
35                        .plugins
36                        .get(pl_name)
37                        .unwrap()
38                        .commands
39                        .get(cmd)
40                        .unwrap()
41                },
42                &|cmd, app| {
43                    helps.apply_helps(&CmdSrc::Plugin(pl_name.to_string(), cmd.to_string()), app)
44                },
45            ));
46        }
47        pl_sub = pl_sub.subcommand(pl_sub_sub)
48    }
49    Ok(pl_sub)
50}
51
52pub struct Plugins {
53    pub plugins: IndexMap<String, Plugin>,
54}
55
56impl Plugins {
57    pub fn new(exts: &mut Extensions) -> Result<Option<Self>> {
58        if ORIGEN_CONFIG.should_collect_plugins() {
59            let mut slf = Self {
60                plugins: IndexMap::new(),
61            };
62
63            python::run_with_callbacks(
64                "import _origen; _origen.plugins.display_plugin_roots()",
65                Some(&mut |line| {
66                    if let Some((status, result)) = line.split_once('|') {
67                        match status {
68                            "success" => {
69                                if let Some((name, path)) = result.split_once('|') {
70                                    match Plugin::new(name, PathBuf::from(path), exts) {
71                                        Ok(pl) => {
72                                            slf.plugins.insert(name.to_string(), pl);
73                                        },
74                                        Err(e) => {
75                                            log_error!("Error collecting plugins: Unable to collect plugin {}: {}", path, e);
76                                        }
77                                    }
78                                } else {
79                                    log_trace!("Error collecting plugins: Malformed output when collecting plugin roots (post status): {}", result)
80                                }
81                            },
82                            _ => log_trace!("Error collecting plugins: Unknown status when collecting plugin roots: {}", status)
83                        }
84                    } else {
85                        log_trace!("Error collecting plugins: Malformed output encountered when collecting plugin roots: {}", line);
86                    }
87                }),
88                Some(&mut |line| {
89                    log_trace!("Error when collecting plugins: {}", line);
90                }),
91            )?;
92            Ok(Some(slf))
93        } else {
94            Ok(None)
95        }
96    }
97
98    pub fn is_empty(&self) -> bool {
99        self.plugins.is_empty()
100    }
101}
102
103pub struct Plugin {
104    pub name: String,
105    pub root: PathBuf,
106    // TODO see about making this indices instead of duplicating string
107    pub top_commands: Vec<String>,
108    pub commands: IndexMap<String, Command>,
109}
110
111impl Plugin {
112    fn _add_cmd(
113        slf: &mut Self,
114        current_path: String,
115        current_cmd: &mut CommandTOML,
116        parent_cmd: Option<&Command>,
117    ) -> Result<bool> {
118        if let Some(c) = Command::from_toml_cmd(
119            current_cmd,
120            CmdSrc::Plugin(slf.name.to_owned(), current_path.to_string()),
121            parent_cmd,
122        )? {
123            if let Some(ref mut sub_cmds) = current_cmd.subcommand {
124                for mut sub in sub_cmds {
125                    Self::_add_cmd(
126                        slf,
127                        format!("{}.{}", current_path, &sub.name),
128                        &mut sub,
129                        Some(&c),
130                    )?;
131                }
132            }
133            slf.commands.insert(current_path.clone(), c);
134            Ok(true)
135        } else {
136            Ok(false)
137        }
138    }
139
140    pub fn new(name: &str, path: PathBuf, exts: &mut Extensions) -> Result<Self> {
141        let mut slf = Self {
142            name: name.to_string(),
143            root: path,
144            top_commands: vec![],
145            commands: IndexMap::new(),
146        };
147
148        use origen_metal::_utility::file_utils::preprocess_as_template;
149        let commands_toml = slf.root.join("commands.toml*");
150        let contents = preprocess_as_template(&commands_toml)?;
151        match contents {
152            Some(content) => {
153                let command_config: CommandsToml = match toml::from_str(&content) {
154                    Ok(x) => x,
155                    Err(e) => {
156                        bail!("Malformed commands.toml: {}", e);
157                    }
158                };
159
160                if let Some(commands) = command_config.command {
161                    for mut cmd in commands {
162                        if Self::_add_cmd(&mut slf, cmd.name.to_owned(), &mut cmd, None)? {
163                            slf.top_commands.push(cmd.name.to_owned());
164                        }
165                    }
166                }
167
168                if let Some(extensions) = command_config.extension {
169                    for ext in extensions {
170                        match exts.add_from_pl_toml(&slf, ext) {
171                            Ok(_) => {}
172                            Err(e) => log_error!(
173                                "Failed to add extensions from plugin '{}': {}",
174                                slf.name,
175                                e
176                            ),
177                        }
178                    }
179                }
180            }
181            None => log_trace!("No commands.toml file found in plugin '{}'", name),
182        };
183
184        Ok(slf)
185    }
186
187    pub fn dispatch(
188        &self,
189        cmd: &clap::ArgMatches,
190        mut app: &clap::Command,
191        exts: &crate::Extensions,
192        plugins: Option<&crate::Plugins>,
193    ) -> Result<()> {
194        if cmd.subcommand().is_some() {
195            let path = build_path(&cmd)?;
196
197            let mut matches = cmd;
198            let mut path_pieces: Vec<String> = vec![];
199            let mut overrides = IndexMap::new();
200            app = app
201                .find_subcommand("plugin")
202                .unwrap()
203                .find_subcommand(&self.name)
204                .unwrap();
205            while matches.subcommand_name().is_some() {
206                let n = matches.subcommand_name().unwrap();
207                matches = matches.subcommand_matches(&n).unwrap();
208                app = app.find_subcommand(n).unwrap();
209                path_pieces.push(n.to_string());
210            }
211
212            launch_as(
213                "_plugin_dispatch_",
214                Some(&path_pieces),
215                matches,
216                app,
217                exts.get_pl_ext(&self.name, &path),
218                plugins,
219                Some({
220                    overrides.insert(
221                        "dispatch_root".to_string(),
222                        Some(format!("r'{}/commands'", &self.root.display())),
223                    );
224                    overrides.insert(
225                        "dispatch_src".to_string(),
226                        Some(format!("r'{}'", &self.name)),
227                    );
228                    overrides
229                }),
230            );
231
232            Ok(())
233        } else {
234            // This case shouldn't happen as any non-valid command should be
235            // caught previously by clap and a non-command invocation should
236            // print the help message.
237            unreachable!("Expected a plugin name but none was found!");
238        }
239    }
240}