use std::ffi::OsString; use std::fs::File; use std::path::{Path, PathBuf}; use super::osstring::os_string_lib; use crate::facade::system::{IntoSystem, System}; use crate::foreign::atom::Atomic; use crate::foreign::cps_box::CPSBox; use crate::foreign::error::ExternResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; use crate::gen::tpl; use crate::gen::traits::Gen; use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::{Clause, Expr}; use crate::libs::io::instances::io_error_handler; use crate::libs::io::{Sink, Source}; use crate::libs::scheduler::system::{SeqScheduler, SharedHandle}; use crate::libs::std::runtime_error::RuntimeError; use crate::utils::combine::Combine; use crate::virt_fs::DeclTree; #[derive(Debug, Clone)] struct ReadFileCmd(OsString); impl InertPayload for ReadFileCmd { const TYPE_STR: &'static str = "readfile command"; } #[derive(Debug, Clone)] struct ReadDirCmd(OsString); impl InertPayload for ReadDirCmd { const TYPE_STR: &'static str = "readdir command"; } #[derive(Debug, Clone)] struct WriteFile { name: OsString, append: bool, } impl InertPayload for WriteFile { const TYPE_STR: &'static str = "writefile command"; } #[must_use] fn read_file(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { let (ReadFileCmd(name), succ, fail, cont) = cmd.unpack3(); let name = name.clone(); let cancel = sched.run_orphan( move |_| File::open(name), |file, _| match file { Err(e) => vec![io_error_handler(e, fail)], Ok(f) => { let source_handle = SharedHandle::wrap(Source::new(Box::new(f))); let tpl = tpl::A(tpl::Slot, tpl::V(Inert(source_handle))); vec![tpl.template(nort_gen(succ.location()), [succ])] }, }, ); let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); tpl.template(nort_gen(cont.location()), [cont]) } #[must_use] fn read_dir(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { let (ReadDirCmd(name), succ, fail, cont) = cmd.unpack3(); let name = name.clone(); let cancel = sched.run_orphan( move |_| { Path::new(&name) .read_dir()? .map(|r| r.and_then(|e| Ok((e.file_name(), e.file_type()?.is_dir())))) .collect() }, |items: std::io::Result>, _| match items { Err(e) => vec![io_error_handler(e, fail)], Ok(os_namev) => { let converted = (os_namev.into_iter()) .map(|(n, d)| { Ok((Inert(n).atom_expr(succ.location()), Inert(d).atom_expr(succ.location()))) }) .collect::, Clause>>(); match converted { Err(e) => { let e = e.to_expr(fail.location()); let tpl = tpl::A(tpl::Slot, tpl::Slot); vec![tpl.template(nort_gen(fail.location()), [fail, e])] }, Ok(names) => { let names = names.to_expr(succ.location()); let tpl = tpl::A(tpl::Slot, tpl::Slot); vec![tpl.template(nort_gen(succ.location()), [succ, names])] }, } }, }, ); let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); tpl.template(nort_gen(cont.location()), [cont]) } #[must_use] fn write_file(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { let (cmd, succ, fail, cont) = cmd.unpack3(); let cmd = cmd.clone(); let cancel = sched.run_orphan( move |_| File::options().write(true).append(cmd.append).open(&cmd.name), |file, _| match file { Err(e) => vec![io_error_handler(e, fail)], Ok(f) => { let sink_handle = SharedHandle::wrap(Box::new(f) as Sink); let tpl = tpl::A(tpl::Slot, tpl::V(Inert(sink_handle))); vec![tpl.template(nort_gen(succ.location()), [succ])] }, }, ); let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); tpl.template(nort_gen(cont.location()), [cont]) } fn open_file_read_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadFileCmd(name)) } fn read_dir_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadDirCmd(name)) } fn open_file_write_cmd(name: OsString) -> CPSBox { CPSBox::new(3, WriteFile { name, append: false }) } fn open_file_append_cmd(name: OsString) -> CPSBox { CPSBox::new(3, WriteFile { name, append: true }) } fn join_paths(root: OsString, sub: OsString) -> OsString { let mut path = PathBuf::from(root); path.push(sub); path.into_os_string() } fn pop_path(path: Inert) -> Option<(Inert, Inert)> { let mut path = PathBuf::from(path.0); let sub = path.file_name()?.to_owned(); debug_assert!(path.pop(), "file_name above returned Some"); Some((Inert(path.into_os_string()), Inert(sub))) } /// A rudimentary system to read and write files. #[derive(Clone)] pub struct DirectFS { scheduler: SeqScheduler, } impl DirectFS { /// Create a new instance of the system. pub fn new(scheduler: SeqScheduler) -> Self { Self { scheduler } } } impl IntoSystem<'static> for DirectFS { fn into_system(self) -> System<'static> { let mut handlers = HandlerTable::new(); let sched = self.scheduler.clone(); handlers.register(move |cmd| read_file(&sched, cmd)); let sched = self.scheduler.clone(); handlers.register(move |cmd| read_dir(&sched, cmd)); let sched = self.scheduler; handlers.register(move |cmd| write_file(&sched, cmd)); System { name: "system::directfs", code: DeclTree::empty(), prelude: Vec::new(), lexer_plugins: vec![], line_parsers: vec![], constants: ConstTree::ns("system::fs", [ConstTree::tree([ xfn_ent("read_file", [open_file_read_cmd]), xfn_ent("read_dir", [read_dir_cmd]), xfn_ent("write_file", [open_file_write_cmd]), xfn_ent("append_file", [open_file_append_cmd]), xfn_ent("join_paths", [join_paths]), xfn_ent("pop_path", [pop_path]), atom_ent("cwd", [Unstable::new(|_| -> ExternResult<_> { let path = std::env::current_dir().map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?; Ok(Inert(path.into_os_string())) })]), ])]) .combine(os_string_lib()) .expect("os_string library and directfs conflict"), handlers, } } }