From caecf9edd1e1f233ff333ce756db6707fa5fd19e Mon Sep 17 00:00:00 2001
From: Erica Marigold <hi@devcomp.xyz>
Date: Sun, 21 Jan 2024 01:39:29 +0530
Subject: [PATCH] feat: buffer support for @lune/fs

* fs.readFile now returns a buffer, instead of a string
* fs.writeFile now accepts a string or a buffer for the contents
  argument
---
 src/lune/builtins/fs/mod.rs | 47 +++++++++++++++++++++++++++++++++----
 1 file changed, 43 insertions(+), 4 deletions(-)

diff --git a/src/lune/builtins/fs/mod.rs b/src/lune/builtins/fs/mod.rs
index 210608d..0d1fd8f 100644
--- a/src/lune/builtins/fs/mod.rs
+++ b/src/lune/builtins/fs/mod.rs
@@ -14,6 +14,19 @@ use copy::copy;
 use metadata::FsMetadata;
 use options::FsWriteOptions;
 
+const BYTES_TO_BUF_IMPL: &str = r#"
+    local tbl = select(1, ...)
+    local buf = buffer.create(#tbl * 4) -- Each u32 is 4 bytes
+
+    for offset, byte in tbl do
+        buffer.writeu32(buf, offset, byte)
+    end
+
+    return buf
+"#;
+
+const BUF_TO_STR_IMPL: &str = "return buffer.tostring(select(1, ...))";
+
 pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
     TableBuilder::new(lua)?
         .with_async_function("readFile", fs_read_file)?
@@ -30,9 +43,24 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
         .build_readonly()
 }
 
-async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaString> {
+fn create_lua_buffer(lua: &Lua, bytes: impl AsRef<[u8]>) -> LuaResult<LuaValue> {
+    let lua_bytes = bytes.as_ref().into_lua(lua)?;
+
+    let buf_constructor = lua.load(BYTES_TO_BUF_IMPL).into_function()?;
+
+    buf_constructor.call::<_, LuaValue>(lua_bytes)
+}
+
+fn buf_to_str(lua: &Lua, buf: LuaValue<'_>) -> LuaResult<String> {
+    let str_constructor = lua.load(BUF_TO_STR_IMPL).into_function()?;
+
+    str_constructor.call(buf)
+}
+
+async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
     let bytes = fs::read(&path).await.into_lua_err()?;
-    lua.create_string(bytes)
+
+    create_lua_buffer(lua, bytes)
 }
 
 async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
@@ -64,8 +92,19 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
     Ok(dir_strings_no_prefix)
 }
 
-async fn fs_write_file(_: &Lua, (path, contents): (String, LuaString<'_>)) -> LuaResult<()> {
-    fs::write(&path, &contents.as_bytes()).await.into_lua_err()
+async fn fs_write_file(lua: &Lua, (path, contents): (String, LuaValue<'_>)) -> LuaResult<()> {
+    let contents_str = match contents {
+        LuaValue::String(str) => Ok(str.to_str()?.to_string()),
+        LuaValue::UserData(inner) => Ok(buf_to_str(lua, LuaValue::UserData(inner))?),
+        other => Err(LuaError::runtime(format!(
+            "Expected type string or buffer, got {}",
+            other.type_name()
+        ))),
+    }?;
+
+    fs::write(&path, contents_str.as_bytes())
+        .await
+        .into_lua_err()
 }
 
 async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> {