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 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 unreachable!("Expected a plugin name but none was found!");
238 }
239 }
240}