1#![allow(clippy::cargo_common_metadata)]
2
3use std::io::ErrorKind as IoErrorKind;
4use std::path::PathBuf;
5
6use async_fs as fs;
7use bstr::{BString, ByteSlice};
8use futures_lite::prelude::*;
9use mlua::prelude::*;
10
11use lune_utils::TableBuilder;
12
13mod copy;
14mod metadata;
15mod options;
16
17use self::copy::copy;
18use self::metadata::FsMetadata;
19use self::options::FsWriteOptions;
20
21const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
22
23#[must_use]
27pub fn typedefs() -> String {
28 TYPEDEFS.to_string()
29}
30
31pub fn module(lua: Lua) -> LuaResult<LuaTable> {
39 TableBuilder::new(lua)?
40 .with_async_function("readFile", fs_read_file)?
41 .with_async_function("readDir", fs_read_dir)?
42 .with_async_function("writeFile", fs_write_file)?
43 .with_async_function("writeDir", fs_write_dir)?
44 .with_async_function("removeFile", fs_remove_file)?
45 .with_async_function("removeDir", fs_remove_dir)?
46 .with_async_function("metadata", fs_metadata)?
47 .with_async_function("isFile", fs_is_file)?
48 .with_async_function("isDir", fs_is_dir)?
49 .with_async_function("move", fs_move)?
50 .with_async_function("copy", fs_copy)?
51 .build_readonly()
52}
53
54async fn fs_read_file(lua: Lua, path: String) -> LuaResult<LuaString> {
55 let bytes = fs::read(&path).await.into_lua_err()?;
56
57 lua.create_string(bytes)
58}
59
60async fn fs_read_dir(_: Lua, path: String) -> LuaResult<Vec<String>> {
61 let mut dir_strings = Vec::new();
62 let mut dir = fs::read_dir(&path).await.into_lua_err()?;
63 while let Some(dir_entry) = dir.try_next().await.into_lua_err()? {
64 if let Some(dir_name_str) = dir_entry.file_name().to_str() {
65 dir_strings.push(dir_name_str.to_owned());
66 } else {
67 return Err(LuaError::RuntimeError(format!(
68 "File name could not be converted into a string: '{}'",
69 dir_entry.file_name().to_string_lossy()
70 )));
71 }
72 }
73 Ok(dir_strings)
74}
75
76async fn fs_write_file(_: Lua, (path, contents): (String, BString)) -> LuaResult<()> {
77 fs::write(&path, contents.as_bytes()).await.into_lua_err()
78}
79
80async fn fs_write_dir(_: Lua, path: String) -> LuaResult<()> {
81 fs::create_dir_all(&path).await.into_lua_err()
82}
83
84async fn fs_remove_file(_: Lua, path: String) -> LuaResult<()> {
85 fs::remove_file(&path).await.into_lua_err()
86}
87
88async fn fs_remove_dir(_: Lua, path: String) -> LuaResult<()> {
89 fs::remove_dir_all(&path).await.into_lua_err()
90}
91
92async fn fs_metadata(_: Lua, path: String) -> LuaResult<FsMetadata> {
93 match fs::metadata(path).await {
94 Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),
95 Ok(meta) => Ok(FsMetadata::from(meta)),
96 Err(e) => Err(e.into()),
97 }
98}
99
100async fn fs_is_file(_: Lua, path: String) -> LuaResult<bool> {
101 match fs::metadata(path).await {
102 Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
103 Ok(meta) => Ok(meta.is_file()),
104 Err(e) => Err(e.into()),
105 }
106}
107
108async fn fs_is_dir(_: Lua, path: String) -> LuaResult<bool> {
109 match fs::metadata(path).await {
110 Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
111 Ok(meta) => Ok(meta.is_dir()),
112 Err(e) => Err(e.into()),
113 }
114}
115
116async fn fs_move(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
117 let path_from = PathBuf::from(from);
118 if !path_from.exists() {
119 return Err(LuaError::RuntimeError(format!(
120 "No file or directory exists at the path '{}'",
121 path_from.display()
122 )));
123 }
124 let path_to = PathBuf::from(to);
125 if !options.overwrite && path_to.exists() {
126 return Err(LuaError::RuntimeError(format!(
127 "A file or directory already exists at the path '{}'",
128 path_to.display()
129 )));
130 }
131 fs::rename(path_from, path_to).await.into_lua_err()?;
132 Ok(())
133}
134
135async fn fs_copy(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
136 copy(from, to, options).await
137}