linesmith_core/runtime/plugins.rs
1//! Runtime plugin discovery + load. Wraps
2//! `PluginRegistry::load_with_xdg` plus the cold-start fast path.
3
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use crate::config::Config;
8use crate::data_context::xdg::{resolve_subdir, XdgEnv, XdgScope};
9use crate::plugins::{build_engine, PluginRegistry};
10use crate::segments::BUILT_IN_SEGMENT_IDS;
11
12/// `$XDG_CONFIG_HOME/linesmith/segments/` (with `$HOME` fallback)
13/// per the cascade in [`crate::data_context::xdg::resolve_subdir`].
14/// `None` when neither env var is populated.
15#[must_use]
16pub fn xdg_segments_dir(env: &XdgEnv) -> Option<PathBuf> {
17 resolve_subdir(env, XdgScope::Config, "segments")
18}
19
20/// Discover + compile plugins from `cfg.plugin_dirs` plus the XDG
21/// segments dir. Returns `None` when neither `cfg.plugin_dirs` is
22/// configured nor an XDG segments directory exists on disk —
23/// cold-start fast path that skips `build_engine` entirely.
24/// (Configured-but-missing entries in `cfg.plugin_dirs` take the
25/// `Some` path and surface as load errors on the registry.)
26/// Callers consume `registry.load_errors()` per their needs: the
27/// driver writes them to stderr; doctor classifies them across the
28/// `plugins.compile` / `plugins.deps_valid` /
29/// `plugins.no_id_collisions` / `plugins.no_builtin_collisions`
30/// check rows.
31#[must_use]
32pub fn load_plugins(
33 cfg: Option<&Config>,
34 xdg_env: &XdgEnv,
35) -> Option<(PluginRegistry, Arc<rhai::Engine>)> {
36 let config_dirs: &[PathBuf] = cfg.map_or(&[], |c| c.plugin_dirs.as_slice());
37 let xdg_dir = xdg_segments_dir(xdg_env);
38
39 // Cold-start fast path: no plugin source means no engine cost.
40 let xdg_present = xdg_dir.as_deref().is_some_and(|p| p.is_dir());
41 if config_dirs.is_empty() && !xdg_present {
42 return None;
43 }
44
45 let engine = build_engine();
46 let registry = PluginRegistry::load_with_xdg(
47 config_dirs,
48 xdg_dir.as_deref(),
49 &engine,
50 BUILT_IN_SEGMENT_IDS,
51 );
52 Some((registry, engine))
53}