install
change mvtwupupyolyuxqwovpnvpkukywloqwv
commit a0fc9b1e65cd01c4185d047894a6a1b7a9158ff9
author Alpha Chen <alpha@kejadlen.dev>
date
parent rwrvylpx
diff --git a/src/main.rs b/src/main.rs
index 31336ca..84cbcb4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,7 @@ use std::fs;
 use std::path::Path;
 use std::rc::Rc;
 use thiserror::Error;
-use tracing::info;
+use tracing::{debug, info};
 
 #[derive(Parser)]
 #[command(name = "frork")]
@@ -24,6 +24,7 @@ struct Cli {
 #[derive(Subcommand)]
 enum Commands {
     Check { script: String },
+    Satisfy { script: String },
 }
 
 #[derive(Error, Debug, Clone)]
@@ -76,8 +77,9 @@ enum Status {
 }
 
 trait AssertionType {
-    fn status(&self) -> Result<Status>;
     fn display(&self) -> String;
+    fn status(&self) -> Result<Status>;
+    fn install(&self) -> Result<()>;
 }
 
 type AssertionFactory = Box<dyn Fn(LuaMultiValue) -> Result<Box<dyn AssertionType>>>;
@@ -150,6 +152,10 @@ impl Symlink {
 }
 
 impl AssertionType for Symlink {
+    fn display(&self) -> String {
+        format!("symlink {} {}", self.target, self.source)
+    }
+
     fn status(&self) -> Result<Status> {
         if !Path::new(&self.target).exists() {
             return Ok(Status::Missing);
@@ -178,35 +184,52 @@ impl AssertionType for Symlink {
         }
     }
 
-    fn display(&self) -> String {
-        format!("symlink {} {}", self.target, self.source)
+    fn install(&self) -> Result<()> {
+        use std::os::unix::fs;
+        fs::symlink(&self.source, &self.target)
+            .map_err(|e| eyre!("Failed to create symlink: {}", e))?;
+        debug!("created: {}", self.display());
+        Ok(())
     }
 }
 
 struct LuaAssertion {
     name: String,
     args: LuaMultiValue,
-    status_fn: LuaFunction,
     display_fn: Option<LuaFunction>,
+    status_fn: LuaFunction,
+    install_fn: Option<LuaFunction>,
 }
 
 impl LuaAssertion {
     fn new(
         name: &str,
         args: LuaMultiValue,
-        status_fn: LuaFunction,
         display_fn: Option<LuaFunction>,
+        status_fn: LuaFunction,
+        install_fn: LuaFunction,
     ) -> Self {
         Self {
             name: name.to_string(),
             args,
-            status_fn,
             display_fn,
+            status_fn,
+            install_fn: Some(install_fn),
         }
     }
 }
 
 impl AssertionType for LuaAssertion {
+    fn display(&self) -> String {
+        if let Some(ref display_fn) = self.display_fn {
+            display_fn
+                .call::<String>(self.args.clone())
+                .unwrap_or_else(|_| self.default_display())
+        } else {
+            self.default_display()
+        }
+    }
+
     fn status(&self) -> Result<Status> {
         let result = self
             .status_fn
@@ -219,13 +242,18 @@ impl AssertionType for LuaAssertion {
         }
     }
 
-    fn display(&self) -> String {
-        if let Some(ref display_fn) = self.display_fn {
-            display_fn
-                .call::<String>(self.args.clone())
-                .unwrap_or_else(|_| self.default_display())
+    fn install(&self) -> Result<()> {
+        if let Some(ref install_fn) = self.install_fn {
+            install_fn
+                .call::<()>(self.args.clone())
+                .map_err(FrorkError::from)?;
+            debug!("installed: {}", self.display());
+            Ok(())
         } else {
-            self.default_display()
+            Err(eyre!(
+                "Install not implemented for assertion type: {}",
+                self.name
+            ))
         }
     }
 }
@@ -257,23 +285,6 @@ impl Default for Frork {
 }
 
 impl Frork {
-    fn register(&self, name: &str, table: LuaTable) -> LuaResult<()> {
-        let status_fn: LuaFunction = table.get("status")?;
-        let display_fn: Option<LuaFunction> = table.get("display").ok();
-
-        let name_clone = name.to_string();
-        self.registry.borrow_mut().register(name, move |args| {
-            Ok(Box::new(LuaAssertion::new(
-                &name_clone,
-                args,
-                status_fn.clone(),
-                display_fn.clone(),
-            )))
-        });
-        info!("Registered assertion type: {}", name);
-        Ok(())
-    }
-
     fn lua_table(lua: &Lua) -> Result<LuaTable> {
         let frork_table = lua.create_table().map_err(FrorkError::from)?;
         let frork = Rc::new(Self::default());
@@ -297,6 +308,25 @@ impl Frork {
         Ok(frork_table)
     }
 
+    fn register(&self, name: &str, table: LuaTable) -> LuaResult<()> {
+        let display_fn: Option<LuaFunction> = table.get("display").ok();
+        let status_fn: LuaFunction = table.get("status")?;
+        let install_fn: LuaFunction = table.get("install")?;
+
+        let name_clone = name.to_string();
+        self.registry.borrow_mut().register(name, move |args| {
+            Ok(Box::new(LuaAssertion::new(
+                &name_clone,
+                args,
+                display_fn.clone(),
+                status_fn.clone(),
+                install_fn.clone(),
+            )))
+        });
+        info!("Registered assertion type: {}", name);
+        Ok(())
+    }
+
     fn check(&self, args: LuaMultiValue) -> LuaResult<()> {
         if args.is_empty() {
             return Err(LuaError::external(FrorkError::NoOperation));
@@ -317,39 +347,43 @@ impl Frork {
             .map_err(LuaError::external)?;
         let status = assertion.status().map_err(LuaError::external)?;
         match status {
-            Status::Ok => println!("ok: {}", assertion.display()),
-            Status::Missing => println!("missing: {}", assertion.display()),
+            Status::Ok => debug!("ok: {}", assertion.display()),
+            Status::Missing => debug!("missing: {}", assertion.display()),
         }
         Ok(())
     }
 }
 
+fn run_script(script: &str) -> Result<()> {
+    let lua = Lua::new();
+
+    let fennel_code = include_str!("../fennel-1.6.0.lua");
+    let fennel_module = lua
+        .load(fennel_code)
+        .eval::<LuaValue>()
+        .map_err(|e| eyre!("Failed to load Fennel: {}", e))?;
+    lua.register_module("fennel", fennel_module)
+        .map_err(|e| eyre!("Failed to register Fennel module: {}", e))?;
+
+    let frork_module = Frork::lua_table(&lua).wrap_err("Failed to create Frork module")?;
+    lua.register_module("frork", frork_module)
+        .map_err(|e| eyre!("Failed to register Frork module: {}", e))?;
+
+    lua.load(format!(
+        r#"require("fennel").install().dofile("{}")"#,
+        script
+    ))
+    .exec()
+    .map_err(|e| eyre!("Failed to execute script: {}", e))?;
+
+    Ok(())
+}
+
 fn run(command: &Commands) -> Result<()> {
     match command {
-        Commands::Check { script } => {
-            let lua = Lua::new();
-
-            let fennel_code = include_str!("../fennel-1.6.0.lua");
-            let fennel_module = lua
-                .load(fennel_code)
-                .eval::<LuaValue>()
-                .map_err(|e| eyre!("Failed to load Fennel: {}", e))?;
-            lua.register_module("fennel", fennel_module)
-                .map_err(|e| eyre!("Failed to register Fennel module: {}", e))?;
-
-            let frork_module = Frork::lua_table(&lua).wrap_err("Failed to create Frork module")?;
-            lua.register_module("frork", frork_module)
-                .map_err(|e| eyre!("Failed to register Frork module: {}", e))?;
-
-            lua.load(format!(
-                r#"require("fennel").install().dofile("{}")"#,
-                script
-            ))
-            .exec()
-            .map_err(|e| eyre!("Failed to execute script: {}", e))?;
-        }
+        Commands::Check { script } => run_script(script),
+        Commands::Satisfy { script } => run_script(script),
     }
-    Ok(())
 }
 
 fn main() -> Result<()> {
@@ -372,8 +406,12 @@ mod tests {
         let status_fn = lua
             .create_function(|_lua, _args: LuaMultiValue| Ok("ok".to_string()))
             .unwrap();
+        let install_fn = lua
+            .create_function(|_lua, _args: LuaMultiValue| Ok(()))
+            .unwrap();
 
-        let assertion = LuaAssertion::new("test", LuaMultiValue::new(), status_fn, None);
+        let assertion =
+            LuaAssertion::new("test", LuaMultiValue::new(), None, status_fn, install_fn);
         let result = assertion.status().unwrap();
 
         match result {
@@ -389,7 +427,11 @@ mod tests {
             .create_function(|_lua, _args: LuaMultiValue| Ok("missing".to_string()))
             .unwrap();
 
-        let assertion = LuaAssertion::new("test", LuaMultiValue::new(), status_fn, None);
+        let install_fn = lua
+            .create_function(|_lua, _args: LuaMultiValue| Ok(()))
+            .unwrap();
+        let assertion =
+            LuaAssertion::new("test", LuaMultiValue::new(), None, status_fn, install_fn);
         let result = assertion.status().unwrap();
 
         match result {
@@ -405,7 +447,11 @@ mod tests {
             .create_function(|_lua, _args: LuaMultiValue| Ok("invalid".to_string()))
             .unwrap();
 
-        let assertion = LuaAssertion::new("test", LuaMultiValue::new(), status_fn, None);
+        let install_fn = lua
+            .create_function(|_lua, _args: LuaMultiValue| Ok(()))
+            .unwrap();
+        let assertion =
+            LuaAssertion::new("test", LuaMultiValue::new(), None, status_fn, install_fn);
         let result = assertion.status();
 
         assert!(result.is_err());
@@ -421,7 +467,10 @@ mod tests {
         let args = vec![LuaValue::String(lua.create_string("arg1").unwrap())];
         let lua_args = LuaMultiValue::from_vec(args);
 
-        let assertion = LuaAssertion::new("mytest", lua_args, status_fn, None);
+        let install_fn = lua
+            .create_function(|_lua, _args: LuaMultiValue| Ok(()))
+            .unwrap();
+        let assertion = LuaAssertion::new("mytest", lua_args, None, status_fn, install_fn);
         let display = assertion.display();
 
         assert!(display.contains("mytest"));
@@ -448,9 +497,38 @@ mod tests {
         let args = vec![LuaValue::String(lua.create_string("test1").unwrap())];
         let lua_args = LuaMultiValue::from_vec(args);
 
-        let assertion = LuaAssertion::new("mytest", lua_args, status_fn, Some(display_fn));
+        let install_fn = lua
+            .create_function(|_lua, _args: LuaMultiValue| Ok(()))
+            .unwrap();
+        let assertion =
+            LuaAssertion::new("mytest", lua_args, Some(display_fn), status_fn, install_fn);
         let display = assertion.display();
 
         assert_eq!(display, "custom: test1");
     }
+
+    #[test]
+    fn test_fennel_assertion_registration() {
+        let lua = Lua::new();
+
+        let fennel_code = include_str!("../fennel-1.6.0.lua");
+        let fennel_module = lua.load(fennel_code).eval::<LuaValue>().unwrap();
+        lua.globals().set("fennel", fennel_module).unwrap();
+
+        let fennel: LuaTable = lua.globals().get("fennel").unwrap();
+        let eval_fn: LuaFunction = fennel.get("eval").unwrap();
+
+        let assertion_table: LuaTable = eval_fn
+            .call(
+                r#"
+            {:display (fn [args] (.. "test: " (or (. args 1) "default")))
+             :status (fn [args] :ok)
+             :install (fn [args] nil)}
+        "#,
+            )
+            .unwrap();
+
+        let frork = Frork::default();
+        frork.register("test-assertion", assertion_table).unwrap();
+    }
 }