Skip to main content

algocline_engine/
variant_pkg.rs

1use std::path::{Path, PathBuf};
2
3use mlua_pkg::{
4    resolvers::{FsResolver, PrefixResolver},
5    Registry, Resolver,
6};
7
8use crate::resolver_factory::make_resolver;
9
10/// A variant-scoped package pinned to an explicit `(name, pkg_dir)` mapping.
11///
12/// Sourced from `alc.local.toml` (worktree-scoped, gitignored). Unlike the
13/// global `~/.algocline/packages/` layout — where `FsResolver(parent_dir)`
14/// implicitly maps `require("X")` to `<parent>/X/init.lua` via directory
15/// names — variant entries declare the require name explicitly so the on-disk
16/// directory name does not have to match.
17///
18/// Resolution rules (built by [`register_variant_pkgs`]):
19/// - `require("{name}")`        → `{pkg_dir}/init.lua`
20/// - `require("{name}.{sub}")`  → `{pkg_dir}/{sub}.lua` or `{pkg_dir}/{sub}/init.lua`
21#[derive(Clone, Debug)]
22pub struct VariantPkg {
23    pub name: String,
24    pub pkg_dir: PathBuf,
25}
26
27impl VariantPkg {
28    pub fn new(name: impl Into<String>, pkg_dir: impl Into<PathBuf>) -> Self {
29        Self {
30            name: name.into(),
31            pkg_dir: pkg_dir.into(),
32        }
33    }
34}
35
36/// Resolver for `require("{name}")` — the package root of a variant pkg.
37///
38/// `resolve()` reads `{init_path}` from disk each time it fires. Within a
39/// single Lua session, `package.loaded` caches the first result, so this
40/// resolver runs at most once per name per session. Freshness across edits
41/// comes from the fact that each `alc_run` builds a fresh session VM: the
42/// disk read at resolve time guarantees the new session sees the current
43/// `init.lua` content. Submodule lookups (`{name}.{sub}`) are delegated to
44/// a separate `PrefixResolver(name, FsResolver(pkg_dir))`.
45struct VariantRootResolver {
46    name: String,
47    init_path: PathBuf,
48}
49
50impl Resolver for VariantRootResolver {
51    fn resolve(&self, lua: &mlua::Lua, req: &str) -> Option<mlua::Result<mlua::Value>> {
52        if req != self.name {
53            return None;
54        }
55        let content = match std::fs::read_to_string(&self.init_path) {
56            Ok(c) => c,
57            Err(e) => {
58                // `{:?}` preserves non-UTF-8 bytes in the path for debugging;
59                // `display()` would replace them with U+FFFD and obscure the
60                // actual offending filename when the OS locale is misaligned.
61                return Some(Err(mlua::Error::external(format!(
62                    "variant pkg '{}': failed to read {:?}: {e}",
63                    self.name, self.init_path
64                ))));
65            }
66        };
67        Some(
68            lua.load(content)
69                .set_name(self.init_path.display().to_string())
70                .eval(),
71        )
72    }
73}
74
75/// Build a sandboxed `FsResolver` for a variant pkg's submodule lookups.
76///
77/// Delegates to the crate-level [`make_resolver`] factory so the sandbox
78/// policy (default `SymlinkAwareSandbox`, strict under `ALC_PKG_STRICT=1`)
79/// stays identical to the resolvers `Executor` and `alc.fork` construct.
80fn make_submodule_resolver(pkg_dir: &Path) -> Option<FsResolver> {
81    make_resolver(pkg_dir)
82}
83
84/// Register both the root resolver and the submodule prefix resolver for
85/// each variant pkg. Variant pkgs should be inserted before any global
86/// library resolvers so that `alc.local.toml` overrides win over
87/// `~/.algocline/packages/`.
88pub(crate) fn register_variant_pkgs(reg: &mut Registry, variant_pkgs: &[VariantPkg]) {
89    for vp in variant_pkgs {
90        let init_path = vp.pkg_dir.join("init.lua");
91        reg.add(VariantRootResolver {
92            name: vp.name.clone(),
93            init_path,
94        });
95        match make_submodule_resolver(&vp.pkg_dir) {
96            Some(inner) => {
97                reg.add(PrefixResolver::new(vp.name.clone(), inner));
98            }
99            None => {
100                tracing::warn!(
101                    "variant pkg '{}': sandbox init failed for {}, submodules disabled",
102                    vp.name,
103                    vp.pkg_dir.display()
104                );
105            }
106        }
107    }
108}