ezlua 0.5.4

Ergonomic, efficient and Zero-cost rust bindings to Lua5.4
Documentation
use crate::prelude::*;
use alloc::string::String;
use std::{
    fs::{self, FileTimes, Metadata},
    path::Path,
    time::{Duration, SystemTime},
};

pub fn open(lua: &LuaState) -> LuaResult<LuaTable> {
    let module = lua.new_table()?;

    module.set_closure("chdir", std::env::set_current_dir::<&str>)?;
    module.set_closure("currentdir", std::env::current_dir)?;

    module.set_closure("dir", |dir: &str| {
        fs::read_dir(dir).map(|iter| StaticIter::new(iter.flatten().map(|e| e.file_name())))
    })?;

    module.set_closure("link", |old: &str, new: &str, symbol: Option<bool>| {
        #[allow(deprecated)]
        if symbol.unwrap_or(false) {
            fs::soft_link(old, new)
        } else {
            fs::hard_link(old, new)
        }
        .lua_result()
    })?;

    module.set_closure("readlink", |path: &str| NilError(std::fs::read_link(path)))?;
    module.set_closure("copy", std::fs::copy::<&str, &str>)?;
    module.set_closure("rename", std::fs::rename::<&str, &str>)?;
    module.set_closure("removedir", std::fs::remove_dir::<&str>)?;
    module.set_closure("remove", std::fs::remove_file::<&str>)?;

    module.set_closure("mkdir", std::fs::create_dir::<&str>)?;
    module.set_closure("mkdirs", std::fs::create_dir_all::<&str>)?;
    module.set_closure("rmdir", std::fs::remove_dir::<&str>)?;
    module.set_closure("rmdir_all", std::fs::remove_dir_all::<&str>)?;

    module.set(
        "attributes",
        lua.new_closure2(|lua, path: &Path, arg2: Option<LuaValue>| {
            if !path.exists() {
                return LuaResult::Ok(LuaValue::Nil);
            }

            let arg2 = arg2.unwrap_or(LuaValue::Nil);
            let mut res = None;
            let mut key = None;
            match arg2 {
                LuaValue::Nil => {}
                LuaValue::Table(t) => {
                    res.replace(t);
                }
                _ => {
                    key.replace(arg2);
                }
            }
            let meta = fs::metadata(path).lua_result()?;
            let res = lua_attribute(lua, res, path, meta)?;
            if let Some(key) = key {
                res.get(key).map(ValRef::into_value)
            } else {
                Ok(LuaValue::Table(res))
            }
        })?,
    )?;

    module.set(
        "symlinkattributes",
        lua.new_closure(|lua, path: &Path, arg2: Option<LuaString>| {
            if !path.exists() {
                return LuaResult::Ok(LuaValue::Nil);
            }

            let meta = fs::symlink_metadata(path).lua_result()?;
            let res = lua_attribute(lua, None, path, meta)?;
            res.set(
                "target",
                fs::read_link(path)
                    .map(|p| p.to_string_lossy().into_owned())
                    .unwrap_or_default(),
            )?;

            if let Some(key) = arg2 {
                res.get(key).map(ValRef::into_value)
            } else {
                LuaResult::Ok(LuaValue::Table(res))
            }
        })?,
    )?;

    module.set_closure("touch", |file: &str, t: Option<f64>, a: Option<f64>| {
        let file = fs::File::open(file).lua_result()?;
        file.set_times(
            FileTimes::new()
                .set_modified(
                    t.map(|t| SystemTime::UNIX_EPOCH + Duration::from_secs_f64(t))
                        .unwrap_or(SystemTime::now()),
                )
                .set_accessed(
                    a.map(|t| SystemTime::UNIX_EPOCH + Duration::from_secs_f64(t))
                        .unwrap_or(SystemTime::now()),
                ),
        )
        .lua_result()
    })?;

    Ok(module)
}

fn lua_attribute<'a>(
    lua: &'a LuaState,
    res: Option<LuaTable<'a>>,
    path: &Path,
    meta: Metadata,
) -> LuaResult<LuaTable<'a>> {
    let res = if let Some(res) = res {
        res
    } else {
        lua.new_table()?
    };
    res.set(
        "access",
        meta.accessed()
            .lua_result()?
            .duration_since(SystemTime::UNIX_EPOCH)
            .map(|x| x.as_secs())
            .unwrap_or(0),
    )?;
    res.set(
        "modification",
        meta.modified()
            .lua_result()?
            .duration_since(SystemTime::UNIX_EPOCH)
            .map(|x| x.as_secs())
            .unwrap_or(0),
    )?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;

        res.set("uid", meta.uid())?;
        res.set("gid", meta.gid())?;
        res.set("dev", meta.dev())?;
        res.set("ino", meta.ino())?;
        res.set("rdev", meta.rdev())?;
        res.set("blocks", meta.blocks())?;
    }

    #[cfg(windows)]
    {
        res.set(
            "created",
            meta.created()
                .lua_result()?
                .duration_since(SystemTime::UNIX_EPOCH)
                .map(|x| x.as_secs())
                .unwrap_or(0),
        )?;
    }

    res.set("readonly", meta.permissions().readonly())?;

    res.set("size", meta.len())?;

    let mut perms = [b'-'; 9];
    #[cfg(windows)]
    res.set("st_mode", unsafe {
        use alloc::vec::Vec;
        use std::os::windows::ffi::OsStrExt;

        let mut path = path.as_os_str().encode_wide().collect::<Vec<_>>();
        path.push(0);
        let mut st = std::mem::zeroed();
        let mode = if libc::wstat(path.as_ptr(), &mut st) == 0 {
            st.st_mode as i32
        } else {
            0
        };

        if mode & libc::S_IREAD != 0 {
            perms[0] = b'r';
            perms[3] = b'r';
            perms[6] = b'r';
        }
        if mode & libc::S_IWRITE != 0 {
            perms[1] = b'w';
            perms[4] = b'w';
            perms[7] = b'w';
        }
        if mode & libc::S_IEXEC != 0 {
            perms[2] = b'x';
            perms[5] = b'x';
            perms[8] = b'x';
        }
        mode
    })?;
    #[cfg(unix)]
    res.set("st_mode", {
        use std::os::unix::fs::PermissionsExt;

        let mode = meta.permissions().mode() as libc::mode_t;
        if mode & libc::S_IRUSR != 0 {
            perms[0] = b'r';
        }
        if mode & libc::S_IWUSR != 0 {
            perms[1] = b'w';
        }
        if mode & libc::S_IXUSR != 0 {
            perms[2] = b'x';
        }
        if mode & libc::S_IRGRP != 0 {
            perms[3] = b'r';
        }
        if mode & libc::S_IWGRP != 0 {
            perms[4] = b'w';
        }
        if mode & libc::S_IXGRP != 0 {
            perms[5] = b'x';
        }
        if mode & libc::S_IROTH != 0 {
            perms[6] = b'r';
        }
        if mode & libc::S_IWOTH != 0 {
            perms[7] = b'w';
        }
        if mode & libc::S_IXOTH != 0 {
            perms[8] = b'x';
        }
        mode
    })?;
    res.set("permissions", String::from_utf8_lossy(&perms[..]))?;

    if meta.file_type().is_dir() {
        res.set("mode", "directory")?;
    }
    if meta.file_type().is_file() {
        res.set("mode", "file")?;
    }
    if meta.file_type().is_symlink() {
        res.set("mode", "link")?;
    }

    Ok(res)
}