diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..8658c76
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,33 @@
+use mlua::prelude::*;
+use thiserror::Error;
+
+#[derive(Error, Debug, Clone)]
+pub enum FrorkError {
+ #[error("No operation specified")]
+ NoOperation,
+ #[error("Unknown assertion type: {assertion_type}")]
+ UnknownAssertionType { assertion_type: String },
+ #[error("Invalid arguments: {0}")]
+ InvalidArguments(String),
+ #[error("Missing assertion type")]
+ MissingAssertionType,
+ #[error("Lua error: {0}")]
+ Lua(String),
+}
+
+// TODO does this actually work the way I think it does? write a test to find out
+impl From<LuaError> for FrorkError {
+ fn from(err: LuaError) -> Self {
+ match err {
+ LuaError::CallbackError { cause, .. } => {
+ if let Some(frork_err) = cause.downcast_ref::<FrorkError>() {
+ frork_err.clone()
+ } else {
+ FrorkError::Lua(format!("Callback error: {}", cause))
+ }
+ }
+ _ => FrorkError::Lua(err.to_string()),
+ }
+ }
+}
+
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ba8b069
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod errors;
+pub mod utils;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 031e202..a7582b3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,18 +1,18 @@
+mod errors;
+mod utils;
+
use clap::{Parser, Subcommand};
use color_eyre::{Result, eyre::eyre};
+use errors::FrorkError;
use mlua::prelude::*;
-use regex::Regex;
use serde::Deserialize;
use std::cell::RefCell;
use std::collections::HashMap;
-use std::env;
use std::fs;
use std::path::Path;
-use std::process::Command;
use std::rc::Rc;
-use std::sync::LazyLock;
-use thiserror::Error;
use tracing::{debug, error, info};
+use utils::Utils;
#[derive(Parser)]
#[command(name = "frork")]
@@ -30,38 +30,6 @@ enum Commands {
Satisfy { script: String },
}
-#[derive(Error, Debug, Clone)]
-pub enum FrorkError {
- #[error("No operation specified")]
- NoOperation,
- #[error("Unknown operation: {operation}")]
- UnknownOperation { operation: String },
- #[error("Unknown assertion type: {assertion_type}")]
- UnknownAssertionType { assertion_type: String },
- #[error("Invalid arguments: {0}")]
- InvalidArguments(String),
- #[error("Missing assertion type")]
- MissingAssertionType,
- #[error("Lua error: {0}")]
- Lua(String),
-}
-
-// TODO does this actually work the way I think it does? write a test to find out
-impl From<LuaError> for FrorkError {
- fn from(err: LuaError) -> Self {
- match err {
- LuaError::CallbackError { cause, .. } => {
- if let Some(frork_err) = cause.downcast_ref::<FrorkError>() {
- frork_err.clone()
- } else {
- FrorkError::Lua(format!("Callback error: {}", cause))
- }
- }
- _ => FrorkError::Lua(err.to_string()),
- }
- }
-}
-
#[derive(Debug, Deserialize)]
struct Conflict {
expected: String,
@@ -487,107 +455,6 @@ where
}
}
-struct Utils;
-
-impl Utils {
- fn chomp(s: &str) -> String {
- s.trim_end_matches('\n').trim_end_matches('\r').to_string()
- }
-
- fn dirname(path: &str) -> Option<String> {
- let expanded_path = Self::expand_path(path);
- let parent = Path::new(&expanded_path).parent()?;
- parent.to_str().map(|s| s.to_string())
- }
-
- fn expand_path(path: &str) -> String {
- static ENV_VAR_REGEX: LazyLock<Regex> =
- LazyLock::new(|| Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").unwrap());
-
- let mut expanded = path.to_string();
-
- // Expand tilde
- if expanded.starts_with('~')
- && let Ok(home) = env::var("HOME")
- {
- expanded = expanded.replacen('~', &home, 1);
- }
-
- // Expand environment variables using regex
- expanded = ENV_VAR_REGEX
- .replace_all(&expanded, |caps: ®ex::Captures| {
- let var_name = caps.get(1).unwrap().as_str();
- env::var(var_name).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_string())
- })
- .to_string();
-
- expanded
- }
-
- fn sh(cmd: &str, args: &[String]) -> Result<(String, i32)> {
- debug!("Executing command: {} with args: {:?}", cmd, args);
-
- let output = Command::new(cmd)
- .args(args)
- .output()
- .map_err(|e| eyre!("Failed to execute command '{}': {}", cmd, e))?;
-
- let stdout = String::from_utf8_lossy(&output.stdout).to_string();
- let status = output
- .status
- .code()
- .ok_or_else(|| eyre!("Command '{}' terminated by signal", cmd))?;
- debug!(
- "Command '{}' completed with status {}, stdout: {:?}",
- cmd, status, stdout
- );
- Ok((stdout, status))
- }
-}
-
-impl IntoLua for Utils {
- fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
- let utils_table = lua.create_table()?;
-
- utils_table.set(
- "expand_path",
- lua.create_function(|_lua, path: String| Ok(Utils::expand_path(&path)))?,
- )?;
- utils_table.set(
- "dirname",
- lua.create_function(|_lua, path: String| Ok(Utils::dirname(&path)))?,
- )?;
- utils_table.set(
- "chomp",
- lua.create_function(|_lua, s: String| Ok(Utils::chomp(&s)))?,
- )?;
- utils_table.set(
- "sh",
- lua.create_function(|lua, args: LuaMultiValue| {
- let all_args: Vec<String> = args
- .into_iter()
- .map(|arg| String::from_lua(arg, lua))
- .collect::<Result<Vec<_>, _>>()?;
-
- if all_args.is_empty() {
- return Err(LuaError::external(FrorkError::InvalidArguments(
- "sh requires at least a command".to_string(),
- )));
- }
-
- let (cmd, cmd_args) = all_args.split_first().unwrap();
-
- let result = Utils::sh(cmd, cmd_args)
- .map(|(stdout, status)| (Some(stdout), status))
- .unwrap_or((None, -1));
- Ok(result)
- })?,
- )?;
-
- Ok(LuaValue::Table(utils_table))
- }
-}
-
fn setup_lua(
handle_status: impl Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
) -> Result<(Lua, LuaTable, LuaTable)> {
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..617b33d
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,111 @@
+use color_eyre::{Result, eyre::eyre};
+use mlua::prelude::*;
+use regex::Regex;
+use std::env;
+use std::path::Path;
+use std::process::Command;
+use std::sync::LazyLock;
+use tracing::debug;
+
+use crate::errors::FrorkError;
+
+pub struct Utils;
+
+impl Utils {
+ pub fn chomp(s: &str) -> String {
+ s.trim_end_matches('\n').trim_end_matches('\r').to_string()
+ }
+
+ pub fn dirname(path: &str) -> Option<String> {
+ let expanded_path = Self::expand_path(path);
+ let parent = Path::new(&expanded_path).parent()?;
+ parent.to_str().map(|s| s.to_string())
+ }
+
+ pub fn expand_path(path: &str) -> String {
+ static ENV_VAR_REGEX: LazyLock<Regex> =
+ LazyLock::new(|| Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").unwrap());
+
+ let mut expanded = path.to_string();
+
+ // Expand tilde
+ if expanded.starts_with('~')
+ && let Ok(home) = env::var("HOME")
+ {
+ expanded = expanded.replacen('~', &home, 1);
+ }
+
+ // Expand environment variables using regex
+ expanded = ENV_VAR_REGEX
+ .replace_all(&expanded, |caps: ®ex::Captures| {
+ let var_name = caps.get(1).unwrap().as_str();
+ env::var(var_name).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_string())
+ })
+ .to_string();
+
+ expanded
+ }
+
+ pub fn sh(cmd: &str, args: &[String]) -> Result<(String, i32)> {
+ debug!("Executing command: {} with args: {:?}", cmd, args);
+
+ let output = Command::new(cmd)
+ .args(args)
+ .output()
+ .map_err(|e| eyre!("Failed to execute command '{}': {}", cmd, e))?;
+
+ let stdout = String::from_utf8_lossy(&output.stdout).to_string();
+ let status = output
+ .status
+ .code()
+ .ok_or_else(|| eyre!("Command '{}' terminated by signal", cmd))?;
+ debug!(
+ "Command '{}' completed with status {}, stdout: {:?}",
+ cmd, status, stdout
+ );
+ Ok((stdout, status))
+ }
+}
+
+impl IntoLua for Utils {
+ fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
+ let utils_table = lua.create_table()?;
+
+ utils_table.set(
+ "expand_path",
+ lua.create_function(|_lua, path: String| Ok(Utils::expand_path(&path)))?,
+ )?;
+ utils_table.set(
+ "dirname",
+ lua.create_function(|_lua, path: String| Ok(Utils::dirname(&path)))?,
+ )?;
+ utils_table.set(
+ "chomp",
+ lua.create_function(|_lua, s: String| Ok(Utils::chomp(&s)))?,
+ )?;
+ utils_table.set(
+ "sh",
+ lua.create_function(|lua, args: LuaMultiValue| {
+ let all_args: Vec<String> = args
+ .into_iter()
+ .map(|arg| String::from_lua(arg, lua))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ if all_args.is_empty() {
+ return Err(LuaError::external(FrorkError::InvalidArguments(
+ "sh requires at least a command".to_string(),
+ )));
+ }
+
+ let (cmd, cmd_args) = all_args.split_first().unwrap();
+
+ let result = Utils::sh(cmd, cmd_args)
+ .map(|(stdout, status)| (Some(stdout), status))
+ .unwrap_or((None, -1));
+ Ok(result)
+ })?,
+ )?;
+
+ Ok(LuaValue::Table(utils_table))
+ }
+}