scarab_plugin_api/
plugin.rs1use crate::{
4 context::PluginContext,
5 error::Result,
6 menu::MenuItem,
7 types::{Action, ModalItem},
8};
9use async_trait::async_trait;
10
11#[async_trait]
16pub trait Plugin: Send + Sync {
17 fn metadata(&self) -> &PluginMetadata;
19
20 fn get_menu(&self) -> Vec<MenuItem> {
40 Vec::new()
41 }
42
43 fn get_commands(&self) -> Vec<ModalItem> {
45 Vec::new()
46 }
47
48 async fn on_load(&mut self, _ctx: &mut PluginContext) -> Result<()> {
52 Ok(())
53 }
54
55 async fn on_unload(&mut self) -> Result<()> {
59 Ok(())
60 }
61
62 async fn on_output(&mut self, _line: &str, _ctx: &PluginContext) -> Result<Action> {
66 Ok(Action::Continue)
67 }
68
69 async fn on_input(&mut self, _input: &[u8], _ctx: &PluginContext) -> Result<Action> {
73 Ok(Action::Continue)
74 }
75
76 async fn on_pre_command(&mut self, _command: &str, _ctx: &PluginContext) -> Result<Action> {
78 Ok(Action::Continue)
79 }
80
81 async fn on_post_command(
83 &mut self,
84 _command: &str,
85 _exit_code: i32,
86 _ctx: &PluginContext,
87 ) -> Result<()> {
88 Ok(())
89 }
90
91 async fn on_resize(&mut self, _cols: u16, _rows: u16, _ctx: &PluginContext) -> Result<()> {
93 Ok(())
94 }
95
96 async fn on_attach(&mut self, _client_id: u64, _ctx: &PluginContext) -> Result<()> {
98 Ok(())
99 }
100
101 async fn on_detach(&mut self, _client_id: u64, _ctx: &PluginContext) -> Result<()> {
103 Ok(())
104 }
105
106 async fn on_remote_command(&mut self, _id: &str, _ctx: &PluginContext) -> Result<()> {
110 Ok(())
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct PluginMetadata {
117 pub name: String,
119 pub version: String,
121 pub description: String,
123 pub author: String,
125 pub homepage: Option<String>,
127 pub api_version: String,
129 pub min_scarab_version: String,
131 pub emoji: Option<String>,
133 pub color: Option<String>,
135 pub catchphrase: Option<String>,
137}
138
139impl PluginMetadata {
140 pub fn new(
142 name: impl Into<String>,
143 version: impl Into<String>,
144 description: impl Into<String>,
145 author: impl Into<String>,
146 ) -> Self {
147 Self {
148 name: name.into(),
149 version: version.into(),
150 description: description.into(),
151 author: author.into(),
152 homepage: None,
153 api_version: crate::API_VERSION.to_string(),
154 min_scarab_version: "0.1.0".to_string(),
155 emoji: None,
156 color: None,
157 catchphrase: None,
158 }
159 }
160
161 pub fn with_homepage(mut self, homepage: impl Into<String>) -> Self {
163 self.homepage = Some(homepage.into());
164 self
165 }
166
167 pub fn with_api_version(mut self, version: impl Into<String>) -> Self {
169 self.api_version = version.into();
170 self
171 }
172
173 pub fn with_min_scarab_version(mut self, version: impl Into<String>) -> Self {
175 self.min_scarab_version = version.into();
176 self
177 }
178
179 pub fn with_emoji(mut self, emoji: impl Into<String>) -> Self {
181 self.emoji = Some(emoji.into());
182 self
183 }
184
185 pub fn with_color(mut self, color: impl Into<String>) -> Self {
187 self.color = Some(color.into());
188 self
189 }
190
191 pub fn with_catchphrase(mut self, catchphrase: impl Into<String>) -> Self {
193 self.catchphrase = Some(catchphrase.into());
194 self
195 }
196
197 pub fn display_name(&self) -> String {
199 if let Some(emoji) = &self.emoji {
200 format!("{} {}", emoji, self.name)
201 } else {
202 self.name.clone()
203 }
204 }
205
206 pub fn is_compatible(&self, current_api_version: &str) -> bool {
208 use semver::Version;
209
210 let Ok(plugin_version) = Version::parse(&self.api_version) else {
211 return false;
212 };
213
214 let Ok(current_version) = Version::parse(current_api_version) else {
215 return false;
216 };
217
218 plugin_version.major == current_version.major
220 && plugin_version.minor <= current_version.minor
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_version_compatibility() {
230 let meta = PluginMetadata::new("test", "1.0.0", "Test plugin", "Test Author")
231 .with_api_version("0.1.0");
232
233 assert!(meta.is_compatible("0.1.0"));
234 assert!(meta.is_compatible("0.2.0"));
235 assert!(!meta.is_compatible("1.0.0"));
236 assert!(!meta.is_compatible("0.0.1"));
237 }
238
239 #[test]
240 fn test_display_name_with_emoji() {
241 let meta =
242 PluginMetadata::new("awesome-plugin", "1.0.0", "Cool plugin", "Dev").with_emoji("🚀");
243
244 assert_eq!(meta.display_name(), "🚀 awesome-plugin");
245 }
246
247 #[test]
248 fn test_display_name_without_emoji() {
249 let meta = PluginMetadata::new("plain-plugin", "1.0.0", "Plain plugin", "Dev");
250
251 assert_eq!(meta.display_name(), "plain-plugin");
252 }
253}