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