assertions
change rqvktkryxvtsskusyysukotypktqlvvr
commit 3a0fd07af408fa3c31a4a5572a70a3ac3b913709
author Alpha Chen <alpha@kejadlen.dev>
date
parent zxpxxwwq
diff --git a/src/assertions.rs b/src/assertions.rs
new file mode 100644
index 0000000..0da4315
--- /dev/null
+++ b/src/assertions.rs
@@ -0,0 +1,308 @@
+use color_eyre::{Result, eyre::eyre};
+use mlua::prelude::*;
+use serde::Deserialize;
+use std::fs;
+use std::path::Path;
+use tracing::{debug, error, info};
+
+use crate::errors::FrorkError;
+use crate::utils::Utils;
+
+#[derive(Debug, Deserialize)]
+pub struct Conflict {
+    pub expected: String,
+    pub actual: String,
+}
+
+impl FromLua for Conflict {
+    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
+        lua.from_value(value)
+    }
+}
+
+#[derive(Debug)]
+pub enum Status {
+    Ok,
+    Missing,
+    ConflictUpgrade(Conflict),
+}
+
+impl FromLuaMulti for Status {
+    fn from_lua_multi(values: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
+        // Try two values: string and conflict for conflict-upgrade
+        if let Ok((status_str, conflict)) =
+            <(String, Conflict)>::from_lua_multi(values.clone(), lua)
+        {
+            return match status_str.as_str() {
+                "conflict-upgrade" => Ok(Status::ConflictUpgrade(conflict)),
+                _ => Err(LuaError::FromLuaConversionError {
+                    from: "multivalue",
+                    to: "Status".to_string(),
+                    message: Some(
+                        "String + conflict combination only supported for conflict-upgrade"
+                            .to_string(),
+                    ),
+                }),
+            };
+        }
+
+        // Try single string
+        if let Ok(status_str) = String::from_lua_multi(values, lua) {
+            return match status_str.as_str() {
+                "ok" => Ok(Status::Ok),
+                "missing" => Ok(Status::Missing),
+                _ => Err(LuaError::FromLuaConversionError {
+                    from: "string",
+                    to: "Status".to_string(),
+                    message: Some(format!("Invalid status string: '{}'", status_str)),
+                }),
+            };
+        }
+
+        Err(LuaError::FromLuaConversionError {
+            from: "multivalue",
+            to: "Status".to_string(),
+            message: Some("Expected single string or conflict-upgrade with table".to_string()),
+        })
+    }
+}
+
+pub trait AssertionType: std::fmt::Display {
+    fn status(&self) -> Result<Status>;
+    fn install(&self) -> Result<()>;
+}
+
+#[derive(Clone)]
+pub struct LuaAssertionType {
+    pub display_fn: Option<LuaFunction>,
+    pub status_fn: LuaFunction,
+    pub install_fn: LuaFunction,
+}
+
+impl FromLua for LuaAssertionType {
+    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
+        let table = LuaTable::from_lua(value, lua)?;
+
+        let display_fn: Option<LuaFunction> = table.get("display").ok();
+        let status_fn: LuaFunction = table.get("status")?;
+        let install_fn: LuaFunction = table.get("install")?;
+
+        Ok(Self {
+            display_fn,
+            status_fn,
+            install_fn,
+        })
+    }
+}
+
+pub struct Symlink {
+    pub target: String,
+    pub source: String,
+}
+
+impl FromLuaMulti for Symlink {
+    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
+        let (target, source) = <(String, String)>::from_lua_multi(args, lua)?;
+        Ok(Self {
+            target: Utils::expand_path(&target).map_err(LuaError::external)?,
+            source: Utils::expand_path(&source).map_err(LuaError::external)?,
+        })
+    }
+}
+
+impl std::fmt::Display for Symlink {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "symlink {} {}", self.target, self.source)
+    }
+}
+
+impl AssertionType for Symlink {
+    fn status(&self) -> Result<Status> {
+        if !Path::new(&self.target).exists() {
+            return Ok(Status::Missing);
+        }
+
+        if let Ok(link_target) = fs::read_link(&self.target) {
+            if link_target == Path::new(&self.source) {
+                Ok(Status::Ok)
+            } else {
+                todo!(
+                    "{}",
+                    format!(
+                        "symlink {} {} points to wrong target",
+                        self.target, self.source
+                    )
+                );
+            }
+        } else {
+            todo!(
+                "{}",
+                format!(
+                    "symlink {} {} target exists but is not a 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);
+        Ok(())
+    }
+}
+
+pub struct Directory {
+    pub path: String,
+}
+
+impl FromLuaMulti for Directory {
+    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
+        let path = String::from_lua_multi(args, lua)?;
+        Ok(Self {
+            path: Utils::expand_path(&path).map_err(LuaError::external)?,
+        })
+    }
+}
+
+impl std::fmt::Display for Directory {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "directory {}", self.path)
+    }
+}
+
+impl AssertionType for Directory {
+    fn status(&self) -> Result<Status> {
+        let path = Path::new(&self.path);
+        if path.is_dir() {
+            Ok(Status::Ok)
+        } else if path.exists() {
+            todo!(
+                "{}",
+                format!("directory {} exists but is not a directory", self.path)
+            );
+        } else {
+            Ok(Status::Missing)
+        }
+    }
+
+    fn install(&self) -> Result<()> {
+        std::fs::create_dir_all(&self.path)
+            .map_err(|e| eyre!("Failed to create directory: {}", e))?;
+        debug!("created: {}", self);
+        Ok(())
+    }
+}
+
+pub struct Debug {
+    pub status_fn: Option<LuaFunction>,
+    pub install_fn: Option<LuaFunction>,
+}
+
+impl FromLuaMulti for Debug {
+    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
+        let table = LuaTable::from_lua_multi(args, lua)?;
+        let status_fn: Option<LuaFunction> = table.get("status").ok();
+        let install_fn: Option<LuaFunction> = table.get("install").ok();
+        Ok(Self {
+            status_fn,
+            install_fn,
+        })
+    }
+}
+
+impl std::fmt::Display for Debug {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "debug")
+    }
+}
+
+impl AssertionType for Debug {
+    fn status(&self) -> Result<Status> {
+        if let Some(ref status_fn) = self.status_fn {
+            let result = status_fn
+                .call::<Status>(LuaMultiValue::new())
+                .map_err(|e| eyre!("Debug status function failed: {}", e))?;
+            Ok(result)
+        } else {
+            Ok(Status::Ok)
+        }
+    }
+
+    fn install(&self) -> Result<()> {
+        info!("debug: installing");
+        let install_fn = self
+            .install_fn
+            .as_ref()
+            .ok_or_else(|| eyre!("Install not implemented for debug assertion"))?;
+        install_fn
+            .call::<()>(LuaMultiValue::new())
+            .map_err(|e| eyre!("Debug install function failed: {}", e))?;
+        Ok(())
+    }
+}
+
+pub struct LuaAssertion {
+    pub name: String,
+    pub args: LuaMultiValue,
+    pub assertion_type: LuaAssertionType,
+}
+
+impl LuaAssertion {
+    pub fn new(name: &str, args: LuaMultiValue, assertion_type: LuaAssertionType) -> Self {
+        Self {
+            name: name.to_string(),
+            args,
+            assertion_type,
+        }
+    }
+}
+
+impl std::fmt::Display for LuaAssertion {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let default_display = || {
+            let args_str = self
+                .args
+                .iter()
+                .map(|v| v.to_string().unwrap_or_else(|_| "?".to_string()))
+                .collect::<Vec<_>>()
+                .join(" ");
+            format!("{} {}", self.name, args_str)
+        };
+
+        if let Some(ref display_fn) = self.assertion_type.display_fn {
+            let result = display_fn
+                .call::<String>(self.args.clone())
+                .unwrap_or_else(|err| {
+                    error!("Display function failed for {}: {}", self.name, err);
+                    default_display()
+                });
+            write!(f, "{}", result)
+        } else {
+            write!(f, "{}", default_display())
+        }
+    }
+}
+
+impl AssertionType for LuaAssertion {
+    fn status(&self) -> Result<Status> {
+        let result = self
+            .assertion_type
+            .status_fn
+            .call::<Status>(self.args.clone())
+            .map_err(FrorkError::from)?;
+        Ok(result)
+    }
+
+    fn install(&self) -> Result<()> {
+        self.assertion_type
+            .install_fn
+            .call::<()>(self.args.clone())
+            .map_err(FrorkError::from)?;
+        debug!("installed: {}", self);
+        Ok(())
+    }
+}
+
diff --git a/src/lib.rs b/src/lib.rs
index ba8b069..e3c1930 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,2 +1,4 @@
+pub mod assertions;
 pub mod errors;
-pub mod utils;
\ No newline at end of file
+pub mod utils;
+
diff --git a/src/main.rs b/src/main.rs
index 2a4b320..e48f5ed 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,17 +1,18 @@
+mod assertions;
 mod errors;
 mod utils;
 
+use assertions::{
+    AssertionType, Debug, Directory, LuaAssertion, LuaAssertionType, Status, Symlink,
+};
 use clap::{Parser, Subcommand};
 use color_eyre::{Result, eyre::eyre};
 use errors::FrorkError;
 use mlua::prelude::*;
-use serde::Deserialize;
 use std::cell::RefCell;
 use std::collections::HashMap;
-use std::fs;
-use std::path::Path;
 use std::rc::Rc;
-use tracing::{debug, error, info};
+use tracing::info;
 use utils::Utils;
 
 #[derive(Parser)]
@@ -30,93 +31,6 @@ enum Commands {
     Satisfy { script: String },
 }
 
-#[derive(Debug, Deserialize)]
-struct Conflict {
-    expected: String,
-    actual: String,
-}
-
-impl FromLua for Conflict {
-    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
-        lua.from_value(value)
-    }
-}
-
-#[derive(Debug)]
-enum Status {
-    Ok,
-    Missing,
-    ConflictUpgrade(Conflict),
-}
-
-impl FromLuaMulti for Status {
-    fn from_lua_multi(values: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
-        // Try two values: string and conflict for conflict-upgrade
-        if let Ok((status_str, conflict)) =
-            <(String, Conflict)>::from_lua_multi(values.clone(), lua)
-        {
-            return match status_str.as_str() {
-                "conflict-upgrade" => Ok(Status::ConflictUpgrade(conflict)),
-                _ => Err(LuaError::FromLuaConversionError {
-                    from: "multivalue",
-                    to: "Status".to_string(),
-                    message: Some(
-                        "String + conflict combination only supported for conflict-upgrade"
-                            .to_string(),
-                    ),
-                }),
-            };
-        }
-
-        // Try single string
-        if let Ok(status_str) = String::from_lua_multi(values, lua) {
-            return match status_str.as_str() {
-                "ok" => Ok(Status::Ok),
-                "missing" => Ok(Status::Missing),
-                _ => Err(LuaError::FromLuaConversionError {
-                    from: "string",
-                    to: "Status".to_string(),
-                    message: Some(format!("Invalid status string: '{}'", status_str)),
-                }),
-            };
-        }
-
-        Err(LuaError::FromLuaConversionError {
-            from: "multivalue",
-            to: "Status".to_string(),
-            message: Some("Expected single string or conflict-upgrade with table".to_string()),
-        })
-    }
-}
-
-trait AssertionType: std::fmt::Display {
-    fn status(&self) -> Result<Status>;
-    fn install(&self) -> Result<()>;
-}
-
-#[derive(Clone)]
-struct LuaAssertionType {
-    display_fn: Option<LuaFunction>,
-    status_fn: LuaFunction,
-    install_fn: LuaFunction,
-}
-
-impl FromLua for LuaAssertionType {
-    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
-        let table = LuaTable::from_lua(value, lua)?;
-
-        let display_fn: Option<LuaFunction> = table.get("display").ok();
-        let status_fn: LuaFunction = table.get("status")?;
-        let install_fn: LuaFunction = table.get("install")?;
-
-        Ok(Self {
-            display_fn,
-            status_fn,
-            install_fn,
-        })
-    }
-}
-
 #[derive(Default)]
 struct Registry {
     lua_assertion_types: HashMap<String, LuaAssertionType>,
@@ -162,217 +76,6 @@ impl Registry {
     }
 }
 
-struct Symlink {
-    target: String,
-    source: String,
-}
-
-impl FromLuaMulti for Symlink {
-    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
-        let (target, source) = <(String, String)>::from_lua_multi(args, lua)?;
-        Ok(Self {
-            target: Utils::expand_path(&target).map_err(LuaError::external)?,
-            source: Utils::expand_path(&source).map_err(LuaError::external)?,
-        })
-    }
-}
-
-impl std::fmt::Display for Symlink {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "symlink {} {}", self.target, self.source)
-    }
-}
-
-impl AssertionType for Symlink {
-    fn status(&self) -> Result<Status> {
-        if !Path::new(&self.target).exists() {
-            return Ok(Status::Missing);
-        }
-
-        if let Ok(link_target) = fs::read_link(&self.target) {
-            if link_target == Path::new(&self.source) {
-                Ok(Status::Ok)
-            } else {
-                todo!(
-                    "{}",
-                    format!(
-                        "symlink {} {} points to wrong target",
-                        self.target, self.source
-                    )
-                );
-            }
-        } else {
-            todo!(
-                "{}",
-                format!(
-                    "symlink {} {} target exists but is not a 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);
-        Ok(())
-    }
-}
-
-struct Directory {
-    path: String,
-}
-
-impl FromLuaMulti for Directory {
-    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
-        let path = String::from_lua_multi(args, lua)?;
-        Ok(Self {
-            path: Utils::expand_path(&path).map_err(LuaError::external)?,
-        })
-    }
-}
-
-impl std::fmt::Display for Directory {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "directory {}", self.path)
-    }
-}
-
-impl AssertionType for Directory {
-    fn status(&self) -> Result<Status> {
-        let path = Path::new(&self.path);
-        if path.is_dir() {
-            Ok(Status::Ok)
-        } else if path.exists() {
-            todo!(
-                "{}",
-                format!("directory {} exists but is not a directory", self.path)
-            );
-        } else {
-            Ok(Status::Missing)
-        }
-    }
-
-    fn install(&self) -> Result<()> {
-        std::fs::create_dir_all(&self.path)
-            .map_err(|e| eyre!("Failed to create directory: {}", e))?;
-        debug!("created: {}", self);
-        Ok(())
-    }
-}
-
-struct Debug {
-    status_fn: Option<LuaFunction>,
-    install_fn: Option<LuaFunction>,
-}
-
-impl FromLuaMulti for Debug {
-    fn from_lua_multi(args: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
-        let table = LuaTable::from_lua_multi(args, lua)?;
-        let status_fn: Option<LuaFunction> = table.get("status").ok();
-        let install_fn: Option<LuaFunction> = table.get("install").ok();
-        Ok(Self {
-            status_fn,
-            install_fn,
-        })
-    }
-}
-
-impl std::fmt::Display for Debug {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "debug")
-    }
-}
-
-impl AssertionType for Debug {
-    fn status(&self) -> Result<Status> {
-        if let Some(ref status_fn) = self.status_fn {
-            let result = status_fn
-                .call::<Status>(LuaMultiValue::new())
-                .map_err(|e| eyre!("Debug status function failed: {}", e))?;
-            Ok(result)
-        } else {
-            Ok(Status::Ok)
-        }
-    }
-
-    fn install(&self) -> Result<()> {
-        info!("debug: installing");
-        let install_fn = self
-            .install_fn
-            .as_ref()
-            .ok_or_else(|| eyre!("Install not implemented for debug assertion"))?;
-        install_fn
-            .call::<()>(LuaMultiValue::new())
-            .map_err(|e| eyre!("Debug install function failed: {}", e))?;
-        Ok(())
-    }
-}
-
-struct LuaAssertion {
-    name: String,
-    args: LuaMultiValue,
-    assertion_type: LuaAssertionType,
-}
-
-impl LuaAssertion {
-    fn new(name: &str, args: LuaMultiValue, assertion_type: LuaAssertionType) -> Self {
-        Self {
-            name: name.to_string(),
-            args,
-            assertion_type,
-        }
-    }
-}
-
-impl std::fmt::Display for LuaAssertion {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let default_display = || {
-            let args_str = self
-                .args
-                .iter()
-                .map(|v| v.to_string().unwrap_or_else(|_| "?".to_string()))
-                .collect::<Vec<_>>()
-                .join(" ");
-            format!("{} {}", self.name, args_str)
-        };
-
-        if let Some(ref display_fn) = self.assertion_type.display_fn {
-            let result = display_fn
-                .call::<String>(self.args.clone())
-                .unwrap_or_else(|err| {
-                    error!("Display function failed for {}: {}", self.name, err);
-                    default_display()
-                });
-            write!(f, "{}", result)
-        } else {
-            write!(f, "{}", default_display())
-        }
-    }
-}
-
-impl AssertionType for LuaAssertion {
-    fn status(&self) -> Result<Status> {
-        let result = self
-            .assertion_type
-            .status_fn
-            .call::<Status>(self.args.clone())
-            .map_err(FrorkError::from)?;
-        Ok(result)
-    }
-
-    fn install(&self) -> Result<()> {
-        self.assertion_type
-            .install_fn
-            .call::<()>(self.args.clone())
-            .map_err(FrorkError::from)?;
-        debug!("installed: {}", self);
-        Ok(())
-    }
-}
-
 struct Frork<F> {
     // RefCell needed for interior mutability - register() method needs to add
     // new assertion types at runtime when called from Lua/Fennel code