lune_std_fs/
lib.rs

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/**
24    Returns a string containing type definitions for the `fs` standard library.
25*/
26#[must_use]
27pub fn typedefs() -> String {
28    TYPEDEFS.to_string()
29}
30
31/**
32    Creates the `fs` standard library module.
33
34    # Errors
35
36    Errors when out of memory.
37*/
38pub 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}