Skip to main content

care_ef_lua/
lib.rs

1use anyhow::{bail, Result};
2use fn_error_context::context;
3use mlua::prelude::{Lua, LuaMultiValue, LuaValue};
4use path_slash::PathExt as _;
5
6use std::path::Path;
7
8pub struct Effector {
9    lua: Lua,
10}
11
12impl Effector {
13    fn load_embedded_packages(&self) {
14        self.load_pkg("effectors.winfs", include_str!("../winfs.lua"));
15        self.load_pkg("effectors.winhome", include_str!("../winhome.lua"));
16        self.load_pkg("effectors.winpath", include_str!("../winpath.lua"));
17        self.load_pkg("effectors.posixfiles", include_str!("../posixfiles.lua"));
18        self.load_pkg("effectors.posixdirs", include_str!("../posixdirs.lua"));
19        self.load_pkg("effectors.posixfs", include_str!("../posixfs.lua"));
20        self.load_pkg("effectors.systemctl", include_str!("../systemctl.lua"));
21    }
22
23    fn load_pkg(&self, name: &str, code: &str) {
24        let lua = &self.lua;
25        // Load, parse, and evaluate `code` into Lua.
26        let lib_: LuaValue = lua.load(code).set_name(name).eval().unwrap();
27        // Expect the result of the evaluation to be a Lua table.
28        let LuaValue::Table(ref lib) = lib_ else {
29            panic!("*lua {name} expected to return a table, but got: {lib_:?}");
30        };
31        // Assign the table into `package.loaded[$name]` in Lua
32        let package_: LuaValue = lua.globals().get("package").unwrap();
33        let LuaValue::Table(ref package) = package_ else {
34            panic!("*lua failed to find _G.package table");
35        };
36        let loaded_: LuaValue = package.get("loaded").unwrap();
37        let LuaValue::Table(ref loaded) = loaded_ else {
38            panic!("*lua failed to find _G.package.loaded table");
39        };
40        loaded.set(name, lib.clone()).unwrap();
41    }
42
43    // Initialize a Lua effector package. Runs a Lua script:
44    // `_G._MANA = require($name).init(table.unpack($args))`
45    #[context("*lua initializing effector {name:?}")]
46    fn init_pkg(&self, name: &str, args: std::env::Args) -> Result<()> {
47        let lua = &self.lua;
48        let g = lua.globals();
49        let require_: LuaValue = g.get("require").unwrap();
50        let LuaValue::Function(ref require) = require_ else {
51            panic!("*lua failed to find _G.require function");
52        };
53        let lib_: LuaValue = require.call(name)?;
54        let LuaValue::Table(ref lib) = lib_ else {
55            bail!("*lua expected a table from `require({name:?})`, got: {lib_:?}");
56        };
57        let init_: LuaValue = lib.get("init")?;
58        let LuaValue::Function(ref init) = init_ else {
59            bail!("*lua expected a function at `require({name:?}).init`, got: {init_:?}");
60        };
61        // collect args to pass to init()
62        let args_: LuaMultiValue = args
63            .into_iter()
64            .map(|a| LuaValue::String(lua.create_string(a).unwrap()))
65            .collect();
66        // call `init($args...)`
67        let obj_: LuaValue = init.call(args_)?;
68        let LuaValue::Table(_) = obj_ else {
69            bail!("*lua expected a table from `require({name:?}).init(...)`, got: {obj_:?}");
70        };
71        // store result in `_G._MANA`
72        let _ = g.set(MANA_GLOBAL, obj_)?;
73        Ok(())
74    }
75
76    fn call_method<'a, Ret: mlua::FromLuaMulti<'a>>(
77        &'a self,
78        method: &str,
79        args: impl mlua::IntoLuaMulti<'a>,
80    ) -> Result<Ret> {
81        let lua = &self.lua;
82        let obj_: LuaValue = lua.globals().get(MANA_GLOBAL)?;
83        let LuaValue::Table(ref obj) = obj_ else {
84            bail!("*lua expected a table at `_G.{MANA_GLOBAL}`, got: {obj_:?}");
85        };
86        let func_: LuaValue = obj.get(method)?;
87        let LuaValue::Function(ref func) = func_ else {
88            bail!("*lua expected a function at `_G.{MANA_GLOBAL}.{method}`, got: {func_:?}");
89        };
90        // FIXME: change .unwrap() to .ok_or_else(...)
91        let ret: Ret = func.call(args)?;
92        Ok(ret)
93    }
94}
95
96const MANA_GLOBAL: &str = "_MANA";
97
98impl effectors::Callee for Effector {
99    fn start(mut args: std::env::Args) -> Result<Self> {
100        let f = Self { lua: Lua::new() };
101        f.load_embedded_packages();
102        let pkg = args.next();
103        let Some(pkg) = pkg else {
104            bail!("*lua requires an argument: name of a Lua-based effector package");
105        };
106        f.init_pkg(&pkg, args)?;
107        Ok(f)
108    }
109
110    fn detect(&mut self, path: &Path) -> Result<bool> {
111        // FIXME: `path` should be slash'ed on input here
112        self.call_method("exists", path.to_slash())
113    }
114
115    fn gather(&mut self, path: &Path, shadow_prefix: &Path) -> Result<()> {
116        let shadow_path = shadow_prefix.join(path);
117        // FIXME: `path` should be slash'ed on input here
118        let path = path.to_slash();
119        self.call_method("query", (path, shadow_path.to_str().unwrap()))
120    }
121
122    fn affect(&mut self, path: &Path, shadow_prefix: &Path) -> Result<()> {
123        let shadow_path = shadow_prefix.join(path);
124        // FIXME: `path` should be slash'ed on input here
125        let path = path.to_slash();
126        self.call_method("apply", (path, shadow_path.to_str().unwrap()))
127    }
128}