use rlua::prelude::*;
use std::{
sync::Arc,
env,
fs::{self, OpenOptions},
io::{self, SeekFrom, prelude::*},
path::Path
};
use serde_json;
use rlua_serde;
use crate::bindings::system::LuaMetadata;
use regex::Regex;
pub struct LuaFile(fs::File);
pub fn fs_open(_: &Lua, (path, mode): (String, Option<String>)) -> Result<LuaFile, LuaError> {
let mut option = OpenOptions::new();
if let Some(mode) = mode {
match mode.as_ref() {
"r" => option.read(true).write(false),
"w" => option.create(true).read(false).write(true),
"w+" => option.create(true).read(true).write(true).truncate(true),
"a" => option.append(true),
"rw" | _ => option.create(true).read(true).write(true),
};
} else {
option.create(true).read(true).write(true);
}
option.open(path)
.map(LuaFile)
.map_err(LuaError::external)
}
impl LuaUserData for LuaFile {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method_mut("read", |_, this: &mut LuaFile, len: Option<usize>|{
let bytes = match len {
Some(len) => {
let mut bytes = vec![0u8; len];
this.0.read(&mut bytes).map_err(LuaError::external)?;
bytes
},
None => {
let mut bytes = vec![];
this.0.read_to_end(&mut bytes).map_err(LuaError::external)?;
bytes
}
};
Ok(bytes)
});
methods.add_method_mut("read_to_string", |_, this: &mut LuaFile, _: ()|{
let mut data = String::new();
this.0.read_to_string(&mut data).map_err(LuaError::external)?;
Ok(data)
});
methods.add_method_mut("write", |_, this: &mut LuaFile, bytes: Vec<u8>|{
Ok(this.0.write(bytes.as_slice()).map_err(LuaError::external)?)
});
methods.add_method_mut("write", |_, this: &mut LuaFile, str: String|{
Ok(this.0.write(str.as_bytes()).map_err(LuaError::external)?)
});
methods.add_method_mut("flush", |_, this: &mut LuaFile, _: ()|{
Ok(this.0.flush().map_err(LuaError::external)?)
});
methods.add_method_mut("sync_all", |_, this: &mut LuaFile, _: ()|{
Ok(this.0.sync_all().map_err(LuaError::external)?)
});
methods.add_method_mut("sync_data", |_, this: &mut LuaFile, _: ()|{
Ok(this.0.sync_data().map_err(LuaError::external)?)
});
methods.add_method("metadata", |_, this: &LuaFile, _: ()| {
Ok(LuaMetadata(this.0.metadata().map_err(LuaError::external)?))
});
methods.add_method_mut("seek", |_, this: &mut LuaFile, (pos, size): (Option<String>, Option<usize>)| {
let size = size.unwrap_or(0);
let seekfrom = pos.and_then(|s_pos| {
Some(match s_pos.as_ref() {
"start" => SeekFrom::Start(size as u64),
"end" => SeekFrom::End(size as i64),
"current" | _ => SeekFrom::Current(size as i64),
})
}).unwrap_or(SeekFrom::Current(size as i64));
Ok(this.0.seek(seekfrom).map_err(LuaError::external)?)
});
}
}
pub fn init(lua: &Lua) -> crate::Result<()> {
let module = lua.create_table()?;
module.set("open", lua.create_function( fs_open)? )?;
module.set("canonicalize", lua.create_function( |lua, path: String| {
match fs::canonicalize(path).map_err(|err| LuaError::external(err)) {
Ok(i) => Ok(Some(lua.create_string(&i.to_str().unwrap()).unwrap())),
_ => Ok(None)
}
})? )?;
module.set("create_dir", lua.create_function( |_, (path, all): (String, Option<bool>)| {
let result = match all {
Some(true) => fs::create_dir_all(path),
_ => fs::create_dir(path)
};
Ok(result.is_ok())
})? )?;
module.set("entries", lua.create_function( |lua, path: String| {
match fs::read_dir(path) {
Ok(iter) => {
let mut arc_iter = Arc::new(Some(iter));
let f = move |_, _: ()| {
let result = match Arc::get_mut(&mut arc_iter).expect("entries iterator is mutably borrowed") {
Some(iter) => match iter.next() {
Some(Ok(entry)) => Some(entry.file_name().into_string().unwrap()),
_ => None
},
None => None
};
if result.is_none() { *Arc::get_mut(&mut arc_iter).unwrap() = None; }
Ok(result)
};
Ok(lua.create_function_mut(f)?)
}, Err(err) => Err(LuaError::ExternalError(Arc::new(::failure::Error::from_boxed_compat(Box::new(err)))))
}
})? )?;
module.set("read_dir", lua.create_function( |lua, path: String| {
let mut _list: Vec<String> = Vec::new();
for entry in fs::read_dir(path).map_err(|err| LuaError::external(err))? {
let entry = entry.map_err(|err| LuaError::external(err))?;
_list.push(entry.path().file_name().unwrap_or_default().to_string_lossy().to_string());
}
let list_value: serde_json::Value = serde_json::to_value(_list).map_err(|err| LuaError::external(err) )?;
let lua_value = rlua_serde::to_value(lua, &list_value)?;
Ok(lua_value)
})?)?;
module.set("read_file", lua.create_function( |lua, path: String| {
let data = fs::read(path).map_err(|err| LuaError::external(err))?;
Ok(lua.create_string(&String::from_utf8_lossy(&data[..]).to_owned().to_string())?)
})?)?;
module.set("chdir", lua.create_function(|_, path: String| {
env::set_current_dir(path).map_err(LuaError::external)
})?)?;
module.set("current_dir", lua.create_function(|_, _:()| {
env::current_dir().map(|path| path.to_str().map(|s| s.to_string())).map_err(LuaError::external)
})?)?;
module.set("exists", lua.create_function( |_, path: String| {
Ok(::std::path::Path::new(&path).exists())
})?)?;
module.set("is_file", lua.create_function( |_, path: String| {
Ok(::std::path::Path::new(&path).is_file())
})?)?;
module.set("is_dir", lua.create_function( |_, path: String| {
Ok(::std::path::Path::new(&path).is_dir())
})?)?;
module.set("symlink", lua.create_function( |_, (src_path, symlink_dest): (String, String)| {
create_symlink(src_path, symlink_dest).map_err(LuaError::external)
})?)?;
module.set("remove_dir", lua.create_function( |_, (path, all): (String, Option<bool>)| {
match all {
Some(true) => fs::remove_dir_all(&path).map_err(LuaError::external),
_ => fs::remove_dir(&path).map_err(LuaError::external)
}
})?)?;
module.set("touch", lua.create_function( |_, path: String| {
fs::OpenOptions::new()
.write(true)
.create(true)
.open(&path)
.map(|_| ())
.map_err(LuaError::external)
})?)?;
module.set("copy_file", lua.create_function(|_, (src, dest): (String, String)| {
copy_file(src, dest)
})?)?;
module.set("copy_dir", lua.create_function(|_, (src, dest): (String, String)| {
recursive_copy(src, dest).map_err(LuaError::external)
})?)?;
module.set("metadata", lua.create_function( |lua, path: String| {
match fs::metadata(path) {
Ok(md) => {
let table = lua.create_table()?;
table.set("type", {
let file_type = md.file_type();
if file_type.is_file() { "file" }
else if file_type.is_dir() { "directory" }
else { unreachable!() }
})?;
table.set("size", md.len())?;
table.set("readonly", md.permissions().readonly())?;
table.set("created", md.created().map(|time| time.duration_since(::std::time::SystemTime::UNIX_EPOCH).map(|s| s.as_secs()).unwrap_or(0)).ok())?;
table.set("accessed", md.accessed().map(|time| time.duration_since(::std::time::SystemTime::UNIX_EPOCH).map(|s| s.as_secs()).unwrap_or(0)).ok())?;
table.set("modified", md.modified().map(|time| time.duration_since(::std::time::SystemTime::UNIX_EPOCH).map(|s| s.as_secs()).unwrap_or(0)).ok())?;
Ok(Some(table))
},
_ => Ok(None)
}
})? )?;
lua.globals().set("fs", module)?;
Ok(())
}
#[cfg(target_family = "windows")]
fn create_symlink(src_path: String, dest: String) -> std::io::Result<()> {
use std::os::windows::fs::symlink_file;
symlink_file(src_path, dest)
}
#[cfg(target_family = "unix")]
fn create_symlink(src_path: String, dest: String) -> std::io::Result<()> {
use std::os::unix::fs::symlink;
symlink(src_path, dest)
}
fn copy_file<S: AsRef<Path>, D: AsRef<Path>>(src: S, dest: D) -> LuaResult<()> {
let mut dest = dest.as_ref().to_path_buf();
if dest.is_dir() {
let file_name = src.as_ref()
.file_name()
.map(|s| s.to_string_lossy().to_string())
.ok_or(LuaError::external(io::Error::from(io::ErrorKind::InvalidInput)))?;
dest.push(file_name);
};
fs::copy(src, dest).map(|_| ())
.map_err(LuaError::external)
}
fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(src: A, dest: B) -> io::Result<()> {
let path = src.as_ref();
if !src.as_ref().exists() {
return Err(io::Error::from(io::ErrorKind::NotFound));
}
if !dest.as_ref().exists() {
fs::create_dir(&dest)?;
}
for entry in path.read_dir()? {
let src = entry.map(|e| e.path())?;
let src_name = match src.file_name().map(|s| s.to_string_lossy().to_string()) {
Some(s) => s,
None => return Err(io::Error::from(io::ErrorKind::InvalidData))
};
let re = Regex::new(r"^\.git").unwrap();
if re.is_match(&src_name) {
continue;
}
let dest = dest.as_ref().join(src_name);
if src.is_file() {
fs::copy(src, &dest)?;
}
else {
fs::create_dir_all(&dest)?;
recursive_copy(src, &dest)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lua_fs () {
let lua = Lua::new();
init(&lua).unwrap();
lua.exec::<_, ()>(r#"
for entry in fs.entries("./") do
local md = fs.metadata(entry)
print(md.type .. ": " .. entry)
end
assert(fs.canonicalize("."), "expected path")
assert(fs.canonicalize("/no/such/path/here") == nil, "expected nil")
"#, None).unwrap();
}
}