use std::{
cell::RefCell,
fmt,
fs::{File, OpenOptions},
hash::{Hash, Hasher},
io::{
stderr, stdin, stdout, BufReader, BufWriter, Cursor, LineWriter, Read, Seek, SeekFrom,
Stderr, Stdin, Stdout, Write,
},
os::fd::AsRawFd,
process::Child,
rc::Rc,
};
use super::Table;
#[derive(Clone, Debug)]
pub enum UserData {
C(*const u8),
File(Rc<RefCell<FileHandle>>),
}
pub struct FileHandle {
file: FileDesc,
meta: Option<Rc<RefCell<Table>>>,
}
pub enum FileDesc {
Buffer(Rc<RefCell<Cursor<Vec<u8>>>>),
Child(Option<Child>, ChildMode),
File(Option<LuaFile>, FileMode),
StdIn,
StdOut,
StdErr,
}
pub enum FileBufMode {
None,
Full(Option<usize>),
Line(Option<usize>),
}
pub struct LuaFile {
file: File,
read: Option<Box<dyn Read>>,
write: Option<Box<dyn Write>>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ChildMode {
Read,
Write,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FileMode {
Read,
Write,
Append,
ReadUpdate,
WriteUpdate,
AppendUpdate,
}
impl UserData {
pub fn as_ptr(&self) -> *const u8 {
match self {
UserData::C(ptr) => *ptr,
UserData::File(file) => file.borrow().file.as_ptr(),
}
}
pub fn get_meta(&self) -> Option<Rc<RefCell<Table>>> {
match self {
UserData::C(..) => None,
UserData::File(file) => file.borrow().get_meta().clone(),
}
}
}
impl fmt::Display for UserData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UserData::C(ptr) => write!(f, "userdata: {:p}", ptr),
UserData::File(file) => {
if file.borrow().is_closed() {
write!(f, "file (closed)")
} else {
write!(f, "file ({:p})", self.as_ptr())
}
}
}
}
}
impl PartialEq for UserData {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(UserData::C(p1), UserData::C(p2)) => p1 == p2,
(UserData::File(f1), UserData::File(f2)) => Rc::as_ptr(f1) == Rc::as_ptr(f2),
_ => false,
}
}
}
impl Hash for UserData {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
UserData::C(ptr) => ptr.hash(state),
UserData::File(file) => file.borrow().file.hash(state),
}
}
}
impl FileHandle {
pub fn new(file: FileDesc, meta: Option<Rc<RefCell<Table>>>) -> Self {
Self { file, meta }
}
pub fn take_buffer(self) -> Option<Vec<u8>> {
match self.file {
FileDesc::Buffer(buf) => Rc::into_inner(buf)
.map(RefCell::into_inner)
.map(Cursor::into_inner),
_ => None,
}
}
pub fn desc(&self) -> &FileDesc {
&self.file
}
pub fn desc_mut(&mut self) -> &mut FileDesc {
&mut self.file
}
pub fn get_meta(&self) -> &Option<Rc<RefCell<Table>>> {
&self.meta
}
pub fn set_meta(&mut self, meta: Option<Rc<RefCell<Table>>>) {
self.meta = meta;
}
pub fn is_closed(&self) -> bool {
matches!(self.file, FileDesc::File(None, _))
}
pub fn set_buffering(&mut self, mode: FileBufMode) -> bool {
match &mut self.file {
FileDesc::File(Some(file), _) => {
file.read = match mode {
FileBufMode::None | FileBufMode::Line(_) => None,
FileBufMode::Full(cap) => match file.file.try_clone() {
Ok(file) => Some(Box::new(match cap {
Some(cap) => BufReader::with_capacity(cap, file),
None => BufReader::new(file),
})),
Err(_) => return false,
},
};
file.write = match mode {
FileBufMode::None => None,
FileBufMode::Full(cap) => match file.file.try_clone() {
Ok(file) => Some(Box::new(match cap {
Some(cap) => BufWriter::with_capacity(cap, file),
None => BufWriter::new(file),
})),
Err(_) => return false,
},
FileBufMode::Line(cap) => match file.file.try_clone() {
Ok(file) => Some(Box::new(match cap {
Some(cap) => LineWriter::with_capacity(cap, file),
None => LineWriter::new(file),
})),
Err(_) => return false,
},
};
true
}
_ => false,
}
}
}
impl fmt::Debug for FileHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.file)
}
}
impl Read for FileHandle {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.file.read(buf)
}
}
impl Seek for FileHandle {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.file.seek(pos)
}
}
impl Write for FileHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.flush()
}
}
impl FileDesc {
pub fn file(file: File, mode: FileMode) -> FileDesc {
Self::File(
Some(LuaFile {
file,
read: None,
write: None,
}),
mode,
)
}
pub fn as_ptr(&self) -> *const u8 {
match self {
FileDesc::Buffer(buf) => Rc::as_ptr(buf) as *const u8,
FileDesc::Child(child, _) => match child {
Some(child) => child as *const Child as *const u8,
None => 0 as *const u8,
},
FileDesc::File(file, _) => match file {
Some(file) => &file.file as *const File as *const u8,
None => 0 as *const u8,
},
FileDesc::StdIn => &stdin() as *const Stdin as *const u8,
FileDesc::StdOut => &stdout() as *const Stdout as *const u8,
FileDesc::StdErr => &stderr() as *const Stderr as *const u8,
}
}
}
impl fmt::Debug for FileDesc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FileDesc::Buffer(buf) => write!(f, "Write({:p})", Rc::as_ptr(buf)),
FileDesc::Child(child, mode) => write!(
f,
"Child({}, {:?})",
match child {
Some(child) => child.id().to_string(),
None => "closed".to_string(),
},
mode
),
FileDesc::File(file, mode) => write!(
f,
"File({}, {:?})",
match file {
Some(file) => file.as_raw_fd().to_string(),
None => "closed".to_string(),
},
mode
),
FileDesc::StdIn => write!(f, "StdIn"),
FileDesc::StdOut => write!(f, "StdOut"),
FileDesc::StdErr => write!(f, "StdErr"),
}
}
}
impl Hash for FileDesc {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
FileDesc::Buffer(buf) => Rc::as_ptr(buf).hash(state),
_ => {}
}
}
}
const BAD_FILE_DESCRIPTOR: &str = "bad file descriptor";
impl FileMode {
pub fn from_str(str: &str) -> Option<FileMode> {
match str {
"r" | "rb" => Some(FileMode::Read),
"w" | "wb" => Some(FileMode::Write),
"a" | "ab" => Some(FileMode::Append),
"r+" | "r+b" => Some(FileMode::ReadUpdate),
"w+" | "w+b" => Some(FileMode::WriteUpdate),
"a+" | "a+b" => Some(FileMode::AppendUpdate),
_ => None,
}
}
pub fn options(&self) -> OpenOptions {
File::options()
.read(self.can_read())
.write(self.can_write())
.append(self.is_append())
.truncate(self.does_truncate())
.create(self.can_write())
.clone()
}
fn can_read(&self) -> bool {
!matches!(self, FileMode::Write | FileMode::Append)
}
fn can_write(&self) -> bool {
!matches!(self, FileMode::Read)
}
fn is_append(&self) -> bool {
matches!(self, FileMode::Append | FileMode::AppendUpdate)
}
fn does_truncate(&self) -> bool {
matches!(self, FileMode::Write | FileMode::WriteUpdate)
}
}
impl Read for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
FileDesc::Child(Some(child), ChildMode::Read) if child.stdout.is_some() => {
child.stdout.as_mut().unwrap().read(buf)
}
FileDesc::File(Some(file), mode) if mode.can_read() => file.read(buf),
FileDesc::StdIn => stdin().read(buf),
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
BAD_FILE_DESCRIPTOR,
)),
}
}
}
impl Seek for FileDesc {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
match self {
FileDesc::File(Some(file), _) => file.seek(pos),
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
"illegal seek",
)),
}
}
}
impl Write for FileDesc {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
FileDesc::Buffer(rc) => rc.borrow_mut().write(buf),
FileDesc::Child(Some(child), ChildMode::Write) if child.stdin.is_some() => {
child.stdin.as_mut().unwrap().write(buf)
}
FileDesc::File(Some(file), mode) if mode.can_write() => file.write(buf),
FileDesc::StdOut => stdout().write(buf),
FileDesc::StdErr => stderr().write(buf),
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
BAD_FILE_DESCRIPTOR,
)),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
FileDesc::Buffer(rc) => rc.borrow_mut().flush(),
FileDesc::Child(Some(child), ChildMode::Write) if child.stdin.is_some() => {
child.stdin.as_mut().unwrap().flush()
}
FileDesc::File(Some(file), mode) if mode.can_write() => file.flush(),
FileDesc::StdOut => stdout().flush(),
FileDesc::StdErr => stderr().flush(),
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
BAD_FILE_DESCRIPTOR,
)),
}
}
}
impl AsRawFd for LuaFile {
fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
self.file.as_raw_fd()
}
}
impl Read for LuaFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.read {
Some(read) => read.read(buf),
None => self.file.read(buf),
}
}
}
impl Seek for LuaFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.file.seek(pos)
}
}
impl Write for LuaFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match &mut self.write {
Some(write) => write.write(buf),
None => self.file.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
if let Some(write) = &mut self.write {
write.flush()?;
}
self.file.flush()
}
}