1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct PluginManifest {
8 pub name: String,
9 pub version: String,
10 pub description: String,
11 pub author: String,
12 pub provides: Vec<PluginCapability>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18pub enum PluginCapability {
19 Game { game_id: String },
21 Installer { format: String },
23 Diagnostic,
25}
26
27pub fn load_manifest(path: &Path) -> crate::error::Result<PluginManifest> {
29 let content = std::fs::read_to_string(path)?;
30 let manifest: PluginManifest = toml::from_str(&content)?;
31 Ok(manifest)
32}
33
34pub fn scan_plugins() -> Vec<(PathBuf, PluginManifest)> {
36 let plugin_dir = crate::paths::data_dir().join("plugins");
37 if !plugin_dir.exists() {
38 return Vec::new();
39 }
40
41 let mut plugins = Vec::new();
42 if let Ok(entries) = std::fs::read_dir(&plugin_dir) {
43 for entry in entries.flatten() {
44 let manifest_path = entry.path().join("plugin.toml");
45 if manifest_path.exists() {
46 if let Ok(manifest) = load_manifest(&manifest_path) {
47 plugins.push((entry.path(), manifest));
48 }
49 }
50 }
51 }
52 plugins
53}
54
55pub fn list_capabilities() -> Vec<(String, PluginCapability)> {
57 scan_plugins()
58 .into_iter()
59 .flat_map(|(_, manifest)| {
60 let name = manifest.name.clone();
61 manifest
62 .provides
63 .into_iter()
64 .map(move |cap| (name.clone(), cap))
65 })
66 .collect()
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn test_parse_manifest() {
75 let toml_str = r#"
76name = "skyrim-support"
77version = "1.0.0"
78description = "Adds Skyrim SE game support"
79author = "modde-contrib"
80
81[[provides]]
82Game = { game_id = "skyrim-se" }
83
84[[provides]]
85Diagnostic = {}
86"#;
87 let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
88 assert_eq!(manifest.name, "skyrim-support");
89 assert_eq!(manifest.version, "1.0.0");
90 assert_eq!(manifest.description, "Adds Skyrim SE game support");
91 assert_eq!(manifest.author, "modde-contrib");
92 assert_eq!(manifest.provides.len(), 2);
93 }
94
95 #[test]
96 fn test_plugin_capabilities() {
97 let game_cap = PluginCapability::Game {
98 game_id: "cyberpunk2077".to_string(),
99 };
100 let installer_cap = PluginCapability::Installer {
101 format: "fomod".to_string(),
102 };
103 let diag_cap = PluginCapability::Diagnostic;
104
105 assert_eq!(
107 game_cap,
108 PluginCapability::Game {
109 game_id: "cyberpunk2077".to_string()
110 }
111 );
112 assert_ne!(game_cap, diag_cap);
113 assert_ne!(installer_cap, diag_cap);
114
115 let toml_str = r#"
117name = "test"
118version = "0.1.0"
119description = "test plugin"
120author = "test"
121
122[[provides]]
123Game = { game_id = "cyberpunk2077" }
124
125[[provides]]
126Installer = { format = "fomod" }
127
128[[provides]]
129Diagnostic = {}
130"#;
131 let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
132 assert_eq!(manifest.provides.len(), 3);
133 assert_eq!(manifest.provides[0], game_cap);
134 assert_eq!(manifest.provides[1], installer_cap);
135 assert_eq!(manifest.provides[2], diag_cap);
136 }
137}