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;//TODO: Move to having a common interface so IO can share the same bindingpub 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)        }    })? )?;    //Deprecated for path:create_dir    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())    })? )?;    //Deprecated for path:read_dir    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)    })?)?;    ////Deprecated for fs:read    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)    })?)?;    //Probably deprecate for path:exists    module.set("exists", lua.create_function( |_, path: String| {        Ok(::std::path::Path::new(&path).exists())    })?)?;    //Probably deprecate for path:is_file    module.set("is_file", lua.create_function( |_, path: String| {        Ok(::std::path::Path::new(&path).is_file())    })?)?;    //Probably deprecate for path:is_dir    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)    })?)?;    //Probably deprecate for path:remove    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)        }    })?)?;    //TODO: Rename to something suitable other than touch    //Probably deprecate for path:create_file    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)	})?)?;	// This binding has a known side effect that this doesn't copy .git directory	module.set("copy_dir", lua.create_function(|_, (src, dest): (String, String)| {		recursive_copy(src, dest).map_err(LuaError::external) 	})?)?;     //Deprecated for fs:metadata    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())?;                // TODO: Unix permissions when in Unix                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(())}//TODO: Have it set to use either `syslink_file` or `syslink_dir` depending on if the endpoint is a file or directory in the `src_path`//      Probably move functions into path binding.#[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();		// don't copy .git directory		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();    }}