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 let lib_: LuaValue = lua.load(code).set_name(name).eval().unwrap();
27 let LuaValue::Table(ref lib) = lib_ else {
29 panic!("*lua {name} expected to return a table, but got: {lib_:?}");
30 };
31 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 #[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 let args_: LuaMultiValue = args
63 .into_iter()
64 .map(|a| LuaValue::String(lua.create_string(a).unwrap()))
65 .collect();
66 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 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 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 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 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 let path = path.to_slash();
126 self.call_method("apply", (path, shadow_path.to_str().unwrap()))
127 }
128}