From 1fdc6d088c41200aa1b7da132150804735e2f0cd Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 23 Feb 2023 00:00:48 +0100 Subject: [PATCH] Implement API for moving / renaming files and / or directories --- CHANGELOG.md | 3 +++ docs/luneTypes.d.luau | 23 +++++++++++++++++++++++ packages/lib/src/globals/fs.rs | 27 ++++++++++++++++++++++++++- packages/lib/src/lua/fs/mod.rs | 3 +++ packages/lib/src/lua/fs/options.rs | 30 ++++++++++++++++++++++++++++++ packages/lib/src/lua/mod.rs | 1 + packages/lib/src/lua/net/config.rs | 12 ++++++++---- 7 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 packages/lib/src/lua/fs/mod.rs create mode 100644 packages/lib/src/lua/fs/options.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d8d45..21f3e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Implemented a new task scheduler which resolves several long-standing issues: + - Issues with yielding across the c-call/metamethod boundary no longer occur when calling certain async APIs that Lune provides. - Ordering of interleaved calls to `task.spawn/task.defer` is now completely deterministic, defer is now guaranteed to run last even in these cases. - The minimum wait time possible when using `task.wait` and minimum delay time using `task.delay` are now much smaller, and only limited by the underlying OS implementation. For most systems this means `task.wait` and `task.delay` are now accurate down to about 5 milliseconds or less. +- Added a new function `fs.move` to move / rename a file or directory from one path to another. + ### Changed - Type definitions are now bundled as part of the Lune executable, meaning they no longer need to be downloaded. diff --git a/docs/luneTypes.d.luau b/docs/luneTypes.d.luau index ff1dbf6..a4ec16e 100644 --- a/docs/luneTypes.d.luau +++ b/docs/luneTypes.d.luau @@ -1,3 +1,7 @@ +export type FsWriteOptions = { + overwrite: boolean, +} + --[=[ @class fs @@ -124,6 +128,25 @@ declare fs: { @return If the path is a directory or not ]=] isDir: (path: string) -> boolean, + --[=[ + @within fs + + Moves a file or directory to a new path. + + Throws an error if a file or directory already exists at the target path. + This can be bypassed by passing `true` as the third argument, or a table of options with `overwrite` set to `true.` + + An error will be thrown in the following situations: + + * The current process lacks permissions to read at `from` or `to`. + * The new path exists on a different mount point. + * Some other I/O error occurred. + + @param from The path to move from + @param to The path to move to + @param overwriteOrOptions Options the target path, such as if should be overwritten if it already exists + ]=] + move: (from: string, to: string, overwriteOrOptions: boolean | FsWriteOptions) -> (), } type NetMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" diff --git a/packages/lib/src/globals/fs.rs b/packages/lib/src/globals/fs.rs index abb426d..4aa19f1 100644 --- a/packages/lib/src/globals/fs.rs +++ b/packages/lib/src/globals/fs.rs @@ -3,7 +3,7 @@ use std::path::{PathBuf, MAIN_SEPARATOR}; use mlua::prelude::*; use tokio::fs; -use crate::lua::table::TableBuilder; +use crate::lua::{fs::FsWriteOptions, table::TableBuilder}; pub fn create(lua: &'static Lua) -> LuaResult { TableBuilder::new(lua)? @@ -15,6 +15,7 @@ pub fn create(lua: &'static Lua) -> LuaResult { .with_async_function("removeDir", fs_remove_dir)? .with_async_function("isFile", fs_is_file)? .with_async_function("isDir", fs_is_dir)? + .with_async_function("move", fs_move)? .build_readonly() } @@ -93,3 +94,27 @@ async fn fs_is_dir(_: &'static Lua, path: String) -> LuaResult { Ok(false) } } + +async fn fs_move( + _: &'static Lua, + (from, to, options): (String, String, FsWriteOptions), +) -> LuaResult<()> { + let path_from = PathBuf::from(from); + if !path_from.exists() { + return Err(LuaError::RuntimeError(format!( + "No file or directory exists at the path '{}'", + path_from.display() + ))); + } + let path_to = PathBuf::from(to); + if !options.overwrite && path_to.exists() { + return Err(LuaError::RuntimeError(format!( + "A file or directory alreadys exists at the path '{}'", + path_to.display() + ))); + } + fs::rename(path_from, path_to) + .await + .map_err(LuaError::external)?; + Ok(()) +} diff --git a/packages/lib/src/lua/fs/mod.rs b/packages/lib/src/lua/fs/mod.rs new file mode 100644 index 0000000..fcdd352 --- /dev/null +++ b/packages/lib/src/lua/fs/mod.rs @@ -0,0 +1,3 @@ +mod options; + +pub use options::FsWriteOptions; diff --git a/packages/lib/src/lua/fs/options.rs b/packages/lib/src/lua/fs/options.rs new file mode 100644 index 0000000..bd58715 --- /dev/null +++ b/packages/lib/src/lua/fs/options.rs @@ -0,0 +1,30 @@ +use mlua::prelude::*; + +pub struct FsWriteOptions { + pub(crate) overwrite: bool, +} + +impl<'lua> FromLua<'lua> for FsWriteOptions { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + Ok(match value { + LuaValue::Nil => Self { overwrite: false }, + LuaValue::Boolean(b) => Self { overwrite: b }, + LuaValue::Table(t) => { + let overwrite: Option = t.get("overwrite")?; + Self { + overwrite: overwrite.unwrap_or(false), + } + } + _ => { + return Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "FsWriteOptions", + message: Some(format!( + "Invalid write options - expected boolean or table, got {}", + value.type_name() + )), + }) + } + }) + } +} diff --git a/packages/lib/src/lua/mod.rs b/packages/lib/src/lua/mod.rs index 0e29aaa..90f2572 100644 --- a/packages/lib/src/lua/mod.rs +++ b/packages/lib/src/lua/mod.rs @@ -1,6 +1,7 @@ mod create; pub mod async_ext; +pub mod fs; pub mod net; pub mod process; pub mod stdio; diff --git a/packages/lib/src/lua/net/config.rs b/packages/lib/src/lua/net/config.rs index 981af09..f03ab5a 100644 --- a/packages/lib/src/lua/net/config.rs +++ b/packages/lib/src/lua/net/config.rs @@ -79,10 +79,14 @@ impl<'lua> FromLua<'lua> for RequestConfig<'lua> { }); }; // Anything else is invalid - Err(LuaError::RuntimeError(format!( - "Invalid request config - expected string or table, got {}", - value.type_name() - ))) + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "RequestConfig", + message: Some(format!( + "Invalid request config - expected string or table, got {}", + value.type_name() + )), + }) } }