diff --git a/Cargo.lock b/Cargo.lock
index f322cf7..501366d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 4
+[[package]]
+name = "addr2line"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
[[package]]
name = "anstream"
version = "0.6.21"
@@ -52,18 +67,27 @@ dependencies = [
"windows-sys 0.60.2",
]
-[[package]]
-name = "anyhow"
-version = "1.0.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
-
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+[[package]]
+name = "backtrace"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-link",
+]
+
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -80,6 +104,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
[[package]]
name = "cc"
version = "1.2.42"
@@ -136,6 +166,33 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
+[[package]]
+name = "color-eyre"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -164,6 +221,68 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "facet"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2eff8d6840932e1fbd7c4fc80a0521ab303323ea31ec341ea185384c3b1383"
+dependencies = [
+ "facet-core",
+ "facet-macros",
+ "static_assertions",
+]
+
+[[package]]
+name = "facet-core"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c80a6e22351f448828529adf1be52f3037c99ac831bcf3fb9a0df2d5c273df8a"
+dependencies = [
+ "bitflags",
+ "impls",
+]
+
+[[package]]
+name = "facet-macros"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8c1da45a93b10c5829601e19846c7f13d8cde6e0b65740fee5ea51f96d7d7e"
+dependencies = [
+ "facet-core",
+ "facet-macros-emit",
+]
+
+[[package]]
+name = "facet-macros-emit"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "561cd90d2074ce22cb0ea0a4c4395779efd03f9b4d1a2712369b0a63ddd5cfbf"
+dependencies = [
+ "facet-macros-parse",
+ "quote",
+]
+
+[[package]]
+name = "facet-macros-parse"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88ddf668029af6fec4d3e3baaedc82f63667416a0e4b2487226212f70b53af9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unsynn",
+]
+
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
@@ -174,20 +293,48 @@ checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
name = "frork"
version = "0.1.0"
dependencies = [
- "anyhow",
"clap",
+ "color-eyre",
+ "facet",
"mlua",
"thiserror",
"tracing",
"tracing-subscriber",
]
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gimli"
+version = "0.32.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
+
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "impls"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc"
+
+[[package]]
+name = "indenter"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -252,6 +399,15 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
[[package]]
name = "mlua"
version = "0.11.4"
@@ -280,6 +436,12 @@ dependencies = [
"pkg-config",
]
+[[package]]
+name = "mutants"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
+
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -298,6 +460,15 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "object"
+version = "0.37.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -310,6 +481,12 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "owo-colors"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -372,6 +549,12 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "rustc-demangle"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -432,6 +615,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "shadow_counted"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65da48d447333cebe1aadbdd3662f3ba56e76e67f53bc46f3dd5f67c74629d6b"
+
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -453,6 +642,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -472,18 +667,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.69"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.69"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@@ -531,6 +726,16 @@ dependencies = [
"valuable",
]
+[[package]]
+name = "tracing-error"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
[[package]]
name = "tracing-log"
version = "0.2.0"
@@ -562,6 +767,18 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
+[[package]]
+name = "unsynn"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940603a9e25cf11211cc43b81f4fcad2b8ab4df291ca855f32c40e1ac22d5bc"
+dependencies = [
+ "fxhash",
+ "mutants",
+ "proc-macro2",
+ "shadow_counted",
+]
+
[[package]]
name = "utf8parse"
version = "0.2.2"
diff --git a/Cargo.toml b/Cargo.toml
index 43381c4..43c7ced 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,9 +4,10 @@ version = "0.1.0"
edition = "2024"
[dependencies]
-anyhow = "1.0"
-clap = { version = "4.5", features = ["derive"] }
-mlua = { version = "0.11", features = ["lua54", "vendored"] }
-thiserror = "1.0"
-tracing = "0.1"
-tracing-subscriber = "0.3"
+color-eyre = "*"
+clap = { version = "*", features = ["derive"] }
+facet = "*"
+mlua = { version = "*", features = ["lua54", "vendored"] }
+thiserror = "*"
+tracing = "*"
+tracing-subscriber = "*"
diff --git a/src/main.rs b/src/main.rs
index 3235e75..b3ea4cd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
-use anyhow::{Result, anyhow, bail};
use clap::{Parser, Subcommand};
+use color_eyre::{Result, eyre};
+use eyre::WrapErr;
use mlua::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -23,14 +24,26 @@ enum Commands {
Check { script: String },
}
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
pub enum FrorkError {
#[error("No operation specified")]
NoOperation,
#[error("Unknown operation: {operation}")]
UnknownOperation { operation: String },
- #[error("Symlink requires exactly 2 arguments: target and source")]
- InvalidSymlinkArgs,
+ #[error("Unknown assertion type: {assertion_type}")]
+ UnknownAssertionType { assertion_type: String },
+ #[error("Invalid arguments: {0}")]
+ InvalidArguments(String),
+ #[error("Lua error: {0}")]
+ Lua(String),
+ #[error("Assertion error: {0}")]
+ Assertion(String),
+}
+
+impl From<LuaError> for FrorkError {
+ fn from(err: LuaError) -> Self {
+ FrorkError::Lua(err.to_string())
+ }
}
fn expand_tilde(path: &str) -> String {
@@ -52,7 +65,7 @@ enum Status {
}
trait AssertionType {
- fn check(&self) -> Status;
+ fn status(&self) -> Result<Status>;
fn display(&self) -> String;
}
@@ -77,15 +90,13 @@ impl Registry {
.insert(name.to_string(), Box::new(factory));
}
- fn create(
- &self,
- assertion_type: &str,
- args: LuaMultiValue,
- ) -> Result<Option<Box<dyn AssertionType>>> {
- match self.assertion_types.get(assertion_type) {
- Some(factory) => factory(args).map(Some),
- None => Ok(None),
- }
+ fn create(&self, assertion_type: &str, args: LuaMultiValue) -> Result<Box<dyn AssertionType>> {
+ let factory = self.assertion_types.get(assertion_type).ok_or_else(|| {
+ FrorkError::UnknownAssertionType {
+ assertion_type: assertion_type.to_string(),
+ }
+ })?;
+ factory(args)
}
}
@@ -96,49 +107,57 @@ struct Symlink {
impl Symlink {
fn new(args: LuaMultiValue) -> Result<Self> {
- let args_vec: Vec<LuaValue> = args.into_iter().collect();
+ let args_vec: Vec<LuaValue> = args.into_vec();
if args_vec.len() != 2 {
- bail!(FrorkError::InvalidSymlinkArgs);
+ return Err(FrorkError::InvalidArguments(format!(
+ "Symlink requires exactly 2 arguments, got {}",
+ args_vec.len()
+ ))
+ .into());
}
- let target = args_vec[0]
- .to_string()
- .map_err(|_| anyhow!("Target must be a string"))?;
- let source = args_vec[1]
- .to_string()
- .map_err(|_| anyhow!("Source must be a string"))?;
-
- Ok(Self {
- target: expand_tilde(&target),
- source: expand_tilde(&source),
- })
+ let strings: Vec<String> = args_vec
+ .into_iter()
+ .map(|val| {
+ val.to_string().map(|s| expand_tilde(&s)).map_err(|_| {
+ FrorkError::InvalidArguments("Arguments must be strings".to_string())
+ })
+ })
+ .collect::<Result<_, _>>()?;
+
+ let [target, source]: [String; 2] = strings.try_into().unwrap();
+
+ Ok(Self { target, source })
}
}
impl AssertionType for Symlink {
- fn check(&self) -> Status {
- if Path::new(&self.target).exists() {
- // Check if it's a symlink pointing to the correct source
- if let Ok(link_target) = fs::read_link(&self.target) {
- if link_target == Path::new(&self.source) {
- Status::Ok
- } else {
- unimplemented!(
- "symlink {} {} points to wrong target",
- self.target,
- self.source
- );
- }
+ 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 {
unimplemented!(
- "symlink {} {} target exists but is not a symlink",
- self.target,
- self.source
+ "{}",
+ FrorkError::Assertion(format!(
+ "symlink {} {} points to wrong target",
+ self.target, self.source
+ ))
);
}
} else {
- Status::Missing
+ unimplemented!(
+ "{}",
+ FrorkError::Assertion(format!(
+ "symlink {} {} target exists but is not a symlink",
+ self.target, self.source
+ ))
+ );
}
}
@@ -164,15 +183,17 @@ impl FennelAssertion {
}
impl AssertionType for FennelAssertion {
- fn check(&self) -> Status {
- // Call the Lua function with args and convert result
- match self.status_fn.call::<String>(self.args.clone()) {
- Ok(result) => match result.as_str() {
- "ok" => Status::Ok,
- "missing" => Status::Missing,
- _ => Status::Ok, // Default fallback
- },
- Err(_) => Status::Ok, // Default fallback on error
+ fn status(&self) -> Result<Status> {
+ let result = self
+ .status_fn
+ .call::<String>(self.args.clone())
+ .map_err(FrorkError::from)?;
+ match result.as_str() {
+ "ok" => Ok(Status::Ok),
+ "missing" => Ok(Status::Missing),
+ _ => {
+ Err(FrorkError::Assertion(format!("Invalid status returned: '{}'", result)).into())
+ }
}
}
@@ -188,51 +209,52 @@ impl AssertionType for FennelAssertion {
}
struct Frork {
- registry: Rc<RefCell<Registry>>,
+ // RefCell needed for interior mutability - register() method needs to add
+ // new assertion types at runtime when called from Lua/Fennel code
+ registry: RefCell<Registry>,
}
impl Frork {
fn new() -> Self {
let mut registry = Registry::new();
- registry.register("symlink", |args| Ok(Box::new(Symlink::new(args)?)));
+ registry.register("symlink", |args| {
+ Symlink::new(args).map(|s| Box::new(s) as Box<dyn AssertionType>)
+ });
Self {
- registry: Rc::new(RefCell::new(registry)),
+ registry: RefCell::new(registry),
}
}
fn ok(&self, args: LuaMultiValue) -> Result<()> {
if args.is_empty() {
- bail!(FrorkError::NoOperation);
+ return Err(FrorkError::NoOperation.into());
}
let mut args_iter = args.into_iter();
- let operation = args_iter
+ let assertion_type = args_iter
.next()
.and_then(|v| v.to_string().ok())
- .ok_or_else(|| anyhow!("First argument must be operation name"))?;
+ .ok_or_else(|| {
+ FrorkError::InvalidArguments("First argument must be assertion type".to_string())
+ })?;
let assertion_args: LuaMultiValue = args_iter.collect();
- match self.registry.borrow().create(&operation, assertion_args)? {
- Some(assertion) => {
- let status = assertion.check();
- match status {
- Status::Ok => println!("ok: {}", assertion.display()),
- Status::Missing => println!("missing: {}", assertion.display()),
- }
- Ok(())
- }
- None => bail!(FrorkError::UnknownOperation {
- operation: operation.to_string()
- }),
+ let assertion = self
+ .registry
+ .borrow()
+ .create(&assertion_type, assertion_args)?;
+ let status = assertion.status()?;
+ match status {
+ Status::Ok => println!("ok: {}", assertion.display()),
+ Status::Missing => println!("missing: {}", assertion.display()),
}
+ Ok(())
}
- fn register(&self, name: &str, table: LuaTable) -> Result<()> {
- let status_fn: LuaFunction = table
- .get("status")
- .map_err(|e| anyhow!("Failed to get status function: {}", e))?;
+ fn register(&self, name: &str, table: LuaTable) -> Result<(), FrorkError> {
+ let status_fn: LuaFunction = table.get("status")?;
let name_clone = name.to_string();
self.registry.borrow_mut().register(name, move |args| {
@@ -270,42 +292,35 @@ impl Frork {
}
}
-fn run(script_path: &str) -> Result<()> {
+fn run(script_path: &str) -> Result<(), FrorkError> {
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| anyhow!("Failed to load Fennel: {}", e))?;
- lua.register_module("fennel", fennel_module)
- .map_err(|e| anyhow!("Failed to register Fennel module: {}", e))?;
+ let fennel_module = lua.load(fennel_code).eval::<LuaValue>()?;
+ lua.register_module("fennel", fennel_module)?;
let frork = Frork::new();
- let frork_module = frork
- .bind(&lua)
- .map_err(|e| anyhow!("Failed to create Frork module: {}", e))?;
- lua.register_module("frork", frork_module)
- .map_err(|e| anyhow!("Failed to register Frork module: {}", e))?;
+ let frork_module = frork.bind(&lua)?;
+ lua.register_module("frork", frork_module)?;
lua.load(format!(
r#"require("fennel").install().dofile("{}")"#,
script_path
))
- .exec()
- .map_err(|e| anyhow!("Failed to execute script '{}': {}", script_path, e))?;
+ .exec()?;
Ok(())
}
-fn main() -> Result<()> {
+fn main() -> color_eyre::Result<()> {
+ color_eyre::install()?;
tracing_subscriber::fmt::init();
let cli = Cli::parse();
match &cli.command {
Commands::Check { script } => {
- run(script)?;
+ run(script).wrap_err("Failed to run script")?;
}
}