use std::cell::RefCell;
use std::fs::File;
use std::process::{Command, Stdio};
use std::rc::Rc;
use super::os::tmpname;
use super::StdLib;
use crate::error::Result;
use crate::value::{ChildMode, FileDesc, FileHandle, FileMode, FuncBuiltin, Table, UserData};
use crate::{Error, LuaError, Value, VM};
pub(super) fn module(stdlib: &mut StdLib) -> Result<()> {
fn stdio(desc: FileDesc) -> Result<Value> {
Ok(Value::UserData(UserData::File(super::file::_create(
desc,
Rc::new(RefCell::new(Table::default())),
Rc::new(RefCell::new(Table::default())),
)?)))
}
stdlib
.module("io")
.func("close", close)?
.func("flush", flush)?
.func("input", input)?
.func("lines", lines)?
.func("open", open)?
.func("output", output)?
.func("popen", popen)?
.func("read", read)?
.cons("stdin", stdio(FileDesc::StdIn)?)?
.cons("stdout", stdio(FileDesc::StdOut)?)?
.cons("stderr", stdio(FileDesc::StdErr)?)?
.func("tmpfile", tmpfile)?
.func("type", r#type)?
.func("write", write)?;
Ok(())
}
fn close(vm: &mut VM) -> Result<Value> {
Ok(super::file::_close_helper(
vm.arg_opt(0)
.as_ref()
.map(Value::to_file)
.transpose()?
.unwrap_or_else(|| vm.out.clone()),
))
}
fn flush(vm: &mut VM) -> Result<Value> {
if vm.out.borrow().is_closed() {
err!(LuaError::FileDefaultOutputClosed)
} else {
Ok(super::file::_flush_helper(vm.out.clone()))
}
}
fn input(vm: &mut VM) -> Result<Value> {
match vm.arg_or_nil(0) {
Value::Nil => {}
Value::String(str) => {
let filename = String::from_utf8_lossy(&str).into_owned();
match File::options().read(true).open(&filename) {
Ok(file) => vm.inp = super::file::create(vm, FileDesc::file(file, FileMode::Read))?,
Err(e) => return err!(LuaError::FileOpen(filename, e.kind().to_string())),
}
}
file => vm.inp = file.to_file().map_err(|e| vm.arg_error(0, e))?,
}
Ok(Value::UserData(UserData::File(vm.inp.clone())))
}
fn lines(vm: &mut VM) -> Result<Value> {
let filename = vm.arg_opt(0);
let args = vm.arg_split(1);
if args.len() > 250 {
return err!(LuaError::ArgumentTooMany);
}
let file = match &filename {
None | Some(Value::Nil) => vm.inp.clone(),
Some(v) => {
let filename = String::from_utf8_lossy(v.to_string()?).into_owned();
let file = FileDesc::file(File::options().read(true).open(&filename)?, FileMode::Read);
super::file::create(vm, file)?
}
};
let to_close = !matches!(filename, None | Some(Value::Nil));
let file_func = file.clone();
let func = Value::Func(vm.alloc_builtin(FuncBuiltin {
module: "io",
name: "__lines",
func: Rc::new(move |_| __lines(file_func.clone(), args.clone(), to_close)),
}));
let mut ret = vec![func, Value::Nil, Value::Nil];
if to_close {
ret.push(Value::UserData(UserData::File(file)));
}
Ok(Value::Mult(ret))
}
fn __lines(file: Rc<RefCell<FileHandle>>, args: Vec<Value>, to_close: bool) -> Result<Value> {
if file.borrow().is_closed() {
err!(LuaError::FileAlreadyClosed)
} else {
let res = super::file::_read_helper(file.clone(), args)?;
if res.to_single() == &Value::Nil && to_close {
super::file::_close_helper(file);
}
Ok(res)
}
}
fn open(vm: &mut VM) -> Result<Value> {
let mode = match vm.arg_or_nil(1) {
Value::Nil => vec![b'r'],
v => v.into_string()?,
};
let mode = String::from_utf8(mode)
.ok()
.as_ref()
.map(String::as_str)
.and_then(FileMode::from_str)
.ok_or_else(|| Error::from_lua(LuaError::FileOpenMode))?;
let filename = vm.arg(0)?;
let filename = String::from_utf8_lossy(filename.to_string()?);
let file = mode.options().open(filename.into_owned());
Ok(match file {
Ok(file) => Value::UserData(UserData::File(super::file::create(
vm,
FileDesc::file(file, mode),
)?)),
Err(e) => e.into(),
})
}
fn output(vm: &mut VM) -> Result<Value> {
match vm.arg_or_nil(0) {
Value::Nil => {}
Value::String(str) => {
let filename = String::from_utf8_lossy(&str).into_owned();
match File::options()
.write(true)
.truncate(true)
.create(true)
.open(&filename)
{
Ok(file) => {
vm.out = super::file::create(vm, FileDesc::file(file, FileMode::Write))?
}
Err(e) => return err!(LuaError::FileOpen(filename, e.kind().to_string())),
}
}
file => vm.out = file.to_file()?,
}
Ok(Value::UserData(UserData::File(vm.out.clone())))
}
fn popen(vm: &mut VM) -> Result<Value> {
let prog = vm.arg_string_coerce(0)?;
let prog = String::from_utf8_lossy(&prog).into_owned();
let mode = match vm.arg_opt(1) {
Some(mode) => {
let mode = mode.to_string_coerce()?;
match &mode[..] {
&[b'r'] => ChildMode::Read,
&[b'w'] => ChildMode::Write,
_ => return err!(LuaError::FileOpenMode),
}
}
None => ChildMode::Read,
};
let child = Command::new("/bin/sh")
.arg("-c")
.arg(prog)
.stdin(match mode {
ChildMode::Read => Stdio::inherit(),
ChildMode::Write => Stdio::piped(),
})
.stdout(match mode {
ChildMode::Read => Stdio::piped(),
ChildMode::Write => Stdio::inherit(),
})
.stderr(Stdio::inherit())
.spawn()?;
Ok(Value::UserData(UserData::File(super::file::create(
vm,
FileDesc::Child(Some(child), mode),
)?)))
}
fn read(vm: &mut VM) -> Result<Value> {
if vm.inp.borrow().is_closed() {
err!(LuaError::FileDefaultInputClosed)
} else {
super::file::_read_helper(vm.inp.clone(), vm.arg_split(0))
}
}
fn tmpfile(vm: &mut VM) -> Result<Value> {
let name = tmpname(vm)?;
vm.frames.last_mut().unwrap().varargs = vec![name, Value::str("w+")];
open(vm)
}
fn r#type(vm: &mut VM) -> Result<Value> {
Ok(match vm.arg_or_nil(0) {
Value::UserData(UserData::File(file)) => match file.borrow().desc() {
FileDesc::File(None, ..) => Value::str("closed file"),
_ => Value::str("file"),
},
_ => Value::Nil,
})
}
fn write(vm: &mut VM) -> Result<Value> {
if vm.out.borrow().is_closed() {
err!(LuaError::FileDefaultOutputClosed)
} else {
let args = (0..vm.arg_len())
.map(|idx| vm.arg_string_coerce(idx))
.collect::<Result<Vec<_>>>()?;
Ok(super::file::_write_helper(vm.out.clone(), args))
}
}