Skip to main content

lowfat_plugin/
discovery.rs

1use crate::manifest::PluginManifest;
2use std::collections::HashMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6/// A discovered plugin with its manifest and location.
7#[derive(Debug)]
8pub struct DiscoveredPlugin {
9    pub manifest: PluginManifest,
10    pub base_dir: PathBuf,
11    pub category: String,
12}
13
14/// Scan a plugin directory and discover all plugins with valid lowfat.toml or init.toml.
15///
16/// Directory structure:
17///   plugin_dir/
18///     category/
19///       plugin-name/
20///         lowfat.toml (or init.toml)
21pub fn discover_plugins(plugin_dir: &Path) -> Vec<DiscoveredPlugin> {
22    let mut plugins = Vec::new();
23    scan_plugin_dir(plugin_dir, &mut plugins);
24    plugins
25}
26
27fn scan_plugin_dir(dir: &Path, plugins: &mut Vec<DiscoveredPlugin>) {
28    let entries = match fs::read_dir(dir) {
29        Ok(e) => e,
30        Err(_) => return,
31    };
32
33    for category_entry in entries.flatten() {
34        let category_path = category_entry.path();
35        if !category_path.is_dir() {
36            continue;
37        }
38        let category = category_entry
39            .file_name()
40            .to_string_lossy()
41            .to_string();
42
43        let plugin_entries = match fs::read_dir(&category_path) {
44            Ok(e) => e,
45            Err(_) => continue,
46        };
47
48        for plugin_entry in plugin_entries.flatten() {
49            let plugin_path = plugin_entry.path();
50
51            // Try lowfat.toml first, then init.toml for backwards compat
52            let manifest_path = if plugin_path.join("lowfat.toml").is_file() {
53                plugin_path.join("lowfat.toml")
54            } else if plugin_path.join("init.toml").is_file() {
55                plugin_path.join("init.toml")
56            } else {
57                continue;
58            };
59
60            let content = match fs::read_to_string(&manifest_path) {
61                Ok(c) => c,
62                Err(_) => continue,
63            };
64
65            let manifest = match PluginManifest::parse(&content) {
66                Ok(m) => m,
67                Err(e) => {
68                    eprintln!(
69                        "[lowfat] warning: invalid manifest at {}: {}",
70                        manifest_path.display(),
71                        e
72                    );
73                    continue;
74                }
75            };
76
77            plugins.push(DiscoveredPlugin {
78                manifest,
79                base_dir: plugin_path,
80                category,
81            });
82            break;
83        }
84    }
85}
86
87/// Build a command → plugin mapping. If multiple plugins claim the same command,
88/// the last one wins.
89pub fn resolve_plugins(plugins: &[DiscoveredPlugin]) -> HashMap<String, usize> {
90    let mut map = HashMap::new();
91    for (idx, plugin) in plugins.iter().enumerate() {
92        for cmd in &plugin.manifest.plugin.commands {
93            map.insert(cmd.clone(), idx);
94        }
95    }
96    map
97}