expand_path requires env vars
change xttyszmxqkoutnszkqwqrlwsskkruusp
commit 12f241f55f7b853d644ab2641d81aee040ca63e1
author Alpha Chen <alpha@kejadlen.dev>
date
parent vmlwvxkm
diff --git a/src/main.rs b/src/main.rs
index a7582b3..2a4b320 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -171,8 +171,8 @@ 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),
-            source: Utils::expand_path(&source),
+            target: Utils::expand_path(&target).map_err(LuaError::external)?,
+            source: Utils::expand_path(&source).map_err(LuaError::external)?,
         })
     }
 }
@@ -229,7 +229,7 @@ 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),
+            path: Utils::expand_path(&path).map_err(LuaError::external)?,
         })
     }
 }
diff --git a/src/utils.rs b/src/utils.rs
index 617b33d..1598633 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -16,34 +16,47 @@ impl Utils {
         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 dirname(path: &str) -> Result<Option<String>> {
+        let expanded_path = Self::expand_path(path)?;
+        let parent = Path::new(&expanded_path).parent();
+        Ok(parent.and_then(|p| p.to_str().map(|s| s.to_string())))
     }
 
-    pub fn expand_path(path: &str) -> String {
+    pub fn expand_path(path: &str) -> Result<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")
-        {
+        if expanded.starts_with('~') {
+            let home = env::var("HOME").map_err(|_| eyre!("HOME environment variable not set"))?;
             expanded = expanded.replacen('~', &home, 1);
         }
 
-        // Expand environment variables using regex
+        // Expand environment variables
+        let mut missing = Vec::new();
         expanded = ENV_VAR_REGEX
             .replace_all(&expanded, |caps: &regex::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())
+                match env::var(var_name) {
+                    Ok(value) => value,
+                    Err(_) => {
+                        missing.push(var_name.to_string());
+                        caps.get(0).unwrap().as_str().to_string()
+                    }
+                }
             })
             .to_string();
 
-        expanded
+        if !missing.is_empty() {
+            return Err(eyre!(
+                "Environment variables not found: {}",
+                missing.join(", ")
+            ));
+        }
+
+        Ok(expanded)
     }
 
     pub fn sh(cmd: &str, args: &[String]) -> Result<(String, i32)> {
@@ -73,11 +86,15 @@ impl IntoLua for Utils {
 
         utils_table.set(
             "expand_path",
-            lua.create_function(|_lua, path: String| Ok(Utils::expand_path(&path)))?,
+            lua.create_function(|_lua, path: String| {
+                Utils::expand_path(&path).map_err(LuaError::external)
+            })?,
         )?;
         utils_table.set(
             "dirname",
-            lua.create_function(|_lua, path: String| Ok(Utils::dirname(&path)))?,
+            lua.create_function(|_lua, path: String| {
+                Utils::dirname(&path).map_err(LuaError::external)
+            })?,
         )?;
         utils_table.set(
             "chomp",
@@ -109,3 +126,123 @@ impl IntoLua for Utils {
         Ok(LuaValue::Table(utils_table))
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::env;
+
+    #[test]
+    fn test_expand_path_no_variables() {
+        let result = Utils::expand_path("/path/to/file");
+        assert!(result.is_ok());
+        assert_eq!(result.unwrap(), "/path/to/file");
+    }
+
+    #[test]
+    fn test_expand_path_with_tilde() {
+        let home = env::var("HOME").unwrap();
+        let result = Utils::expand_path("~/file");
+        assert!(result.is_ok());
+        assert_eq!(result.unwrap(), format!("{}/file", home));
+    }
+
+    #[test]
+    fn test_expand_path_with_multiple_env_vars() {
+        unsafe {
+            env::set_var("TEST_VAR1", "value1");
+            env::set_var("TEST_VAR2", "value2");
+        }
+        let result = Utils::expand_path("/$TEST_VAR1/path/$TEST_VAR2/file");
+        assert!(result.is_ok());
+        assert_eq!(result.unwrap(), "/value1/path/value2/file");
+        unsafe {
+            env::remove_var("TEST_VAR1");
+            env::remove_var("TEST_VAR2");
+        }
+    }
+
+    #[test]
+    fn test_expand_path_with_multiple_missing_env_vars() {
+        let result = Utils::expand_path("/$MISSING1/path/$MISSING2/file");
+        assert!(result.is_err());
+        let error = result.unwrap_err();
+        let error_str = error.to_string();
+        assert!(error_str.contains("Environment variables not found:"));
+        assert!(error_str.contains("MISSING1"));
+        assert!(error_str.contains("MISSING2"));
+    }
+
+    #[test]
+    fn test_expand_path_mixed_existing_and_missing() {
+        unsafe {
+            env::set_var("EXISTING_VAR", "exists");
+        }
+        let result = Utils::expand_path("/$EXISTING_VAR/path/$MISSING_VAR/file");
+        assert!(result.is_err());
+        let error = result.unwrap_err();
+        assert!(
+            error
+                .to_string()
+                .contains("Environment variables not found: MISSING_VAR")
+        );
+        unsafe {
+            env::remove_var("EXISTING_VAR");
+        }
+    }
+
+    #[test]
+    fn test_expand_path_tilde_and_env_var() {
+        let home = env::var("HOME").unwrap();
+        unsafe {
+            env::set_var("TEST_VAR", "test");
+        }
+        let result = Utils::expand_path("~/$TEST_VAR/file");
+        assert!(result.is_ok());
+        assert_eq!(result.unwrap(), format!("{}/test/file", home));
+        unsafe {
+            env::remove_var("TEST_VAR");
+        }
+    }
+
+    #[test]
+    fn test_sh_through_lua() {
+        let lua = mlua::Lua::new();
+        let utils = Utils {};
+        let utils_table = utils.into_lua(&lua).unwrap();
+
+        lua.globals().set("utils", utils_table).unwrap();
+
+        // Test successful command
+        let result: (Option<String>, i32) = lua
+            .load(r#"return utils.sh("echo", "hello", "world")"#)
+            .eval()
+            .unwrap();
+
+        assert_eq!(result.0.unwrap().trim(), "hello world");
+        assert_eq!(result.1, 0);
+
+        // Test command with non-zero exit
+        let result: (Option<String>, i32) = lua.load(r#"return utils.sh("false")"#).eval().unwrap();
+
+        assert_eq!(result.1, 1);
+    }
+
+    #[test]
+    fn test_sh_through_lua_invalid_arguments() {
+        let lua = mlua::Lua::new();
+        let utils = Utils {};
+        let utils_table = utils.into_lua(&lua).unwrap();
+
+        lua.globals().set("utils", utils_table).unwrap();
+
+        // Test with no arguments - should fail
+        let result = lua
+            .load(r#"return utils.sh()"#)
+            .eval::<(Option<String>, i32)>();
+        assert!(result.is_err());
+
+        let error = result.unwrap_err();
+        assert!(error.to_string().contains("sh requires at least a command"));
+    }
+}