pub mod kpathsea;
use std::collections::hash_map::Entry;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::RwLock;
use kpathsea::Kpathsea;
use ahash::HashMap;
use crate::engine::filesystem::kpathsea::KpseResult;
use crate::engine::mouth::string_source::StringSource;
use crate::engine::state::State;
use crate::tex::catcodes::CategoryCode;
use crate::tex::token::{BaseToken, Token};
use crate::utils::errors::{OtherError, TeXError};
use crate::utils::Ptr;
use crate::utils::strings::CharType;
pub trait File<Char:CharType>:Clone {
fn path(&self) -> &PathBuf;
fn exists(&self) -> bool;
fn content_string(&self) -> Vec<u8>;
fn open_out(&self);
fn open_in(&self);
fn close_out(&self);
fn close_in(&self);
fn write(&self,string:&str);
fn eof(&self) -> bool;
fn read<T:Token<Char=Char>,S:State<T>>(&self,state:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>>;
}
pub trait FileSystem<Char:CharType>:'static {
type F:File<Char>;
fn new(pwd:PathBuf) -> Self;
fn get(&mut self,path:&str) -> Self::F;
fn set_pwd(&mut self, pwd:PathBuf) -> PathBuf;
}
pub struct PhysicalFile<Char:CharType> {path:PathBuf,contents:Vec<u8>,phantom:PhantomData<Char>}
impl<Char:CharType> PhysicalFile<Char> {
pub fn new(path:PathBuf) -> Self {
PhysicalFile {
contents: {
std::fs::read(&path).ok().unwrap_or(vec!())
},
phantom: PhantomData,
path
}
}
}
impl<Char:CharType> File<Char> for Ptr<PhysicalFile<Char>> {
fn path(&self) -> &PathBuf { &self.path }
fn exists(&self) -> bool { self.path.exists() }
fn content_string(&self) -> Vec<u8> {
self.contents.clone()
}
fn open_out(&self) {
todo!("Physical file system not implemented yet")
}
fn close_in(&self) {
todo!("Physical file system not implemented yet")
}
fn close_out(&self) {
todo!("Physical file system not implemented yet")
}
fn eof(&self) -> bool {
todo!("Physical file system not implemented yet")
}
fn open_in(&self) {
todo!("Physical file system not implemented yet")
}
fn write(&self,_:&str) {
todo!("Physical file system not implemented yet")
}
fn read<T:Token,S:State<T>>(&self,_:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
todo!("Physical file system not implemented yet")
}
}
pub struct VirtualFile<Char:CharType> {path:PathBuf,contents:RwLock<Option<Vec<u8>>>,open:RwLock<Option<StringSource<Char>>>}
impl<Char:CharType> File<Char> for Ptr<VirtualFile<Char>> {
fn path(&self) -> &PathBuf { &self.path }
fn exists(&self) -> bool { self.contents.read().unwrap().is_some() }
fn content_string(&self) -> Vec<u8> {
match &*self.contents.read().unwrap() {
Some(s) => s.clone(),
None => vec!()
}
}
fn close_out(&self) {}
fn open_out(&self) {
let mut w = self.contents.write().unwrap();
*w = Some(vec!());
}
fn open_in(&self) {
let w = self.contents.read().unwrap();
let mut open = self.open.write().unwrap();
let v = match &*w {
None => vec!(),
Some(v) => v.clone()
};
*open = Some(StringSource::new(v,Some(Ptr::new(self.path.to_str().unwrap().to_string()))));
}
fn close_in(&self) {
let mut open = self.open.write().unwrap();
*open = None;
}
fn eof(&self) -> bool {
let mut open = self.open.write().unwrap();
open.as_mut().unwrap().preview().is_empty() }
fn read<T: Token<Char=Char>, S: State<T>>(&self, state: &S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
let mut open = self.open.write().unwrap();
match &mut *open {
None => Err(OtherError{msg:"File not open".to_string(),source:None,cause:None}.into()),
Some(m) => {
let line = m.line();
let mut ret: Vec<T> = vec!();
let mut ingroups = 0;
loop {
if ingroups != 0 || m.line() == line {
match m.get_next::<T>(state.get_catcode_scheme(),state.get_endlinechar())? {
None =>
return Ok(ret),
Some(tk) => {
match tk.base() {
BaseToken::Char(_,CategoryCode::BeginGroup) => ingroups += 1,
BaseToken::Char(_,CategoryCode::EndGroup) => ingroups -= 1,
_ => () }
ret.push(tk)
}
}
} else { return Ok(ret) }
}
}
}
}
fn write(&self, string: &str) {
let mut write = self.contents.write().unwrap();
let mut v = write.as_mut().unwrap();
v.extend(string.as_bytes());
v.push(b'\n');
}
}
pub struct KpsePhysicalFileSystem<Char:CharType> {kpathsea:Kpathsea,phantom:PhantomData<Char>}
impl<Char:CharType> FileSystem<Char> for KpsePhysicalFileSystem<Char> {
type F = Ptr<PhysicalFile<Char>>;
fn new(pwd:PathBuf) -> Self { KpsePhysicalFileSystem {kpathsea:Kpathsea::new(pwd),phantom:PhantomData} }
fn get(&mut self, _path: &str) -> Self::F {
todo!("Physical file system not implemented yet")
}
fn set_pwd(&mut self, _pwd: PathBuf) -> PathBuf {
todo!("Physical file system not implemented yet")
}
}
pub struct KpseVirtualFileSystem<Char:CharType> {pwd:PathBuf,kpathsea:Kpathsea,files:HashMap<PathBuf,Ptr<VirtualFile<Char>>>}
impl<Char:CharType> KpsePhysicalFileSystem<Char> {
pub fn kpsewhich(&self, path: &str) -> KpseResult {
self.kpathsea.kpsewhich(path)
}
}
impl<Char:CharType> KpseVirtualFileSystem<Char> {
pub fn kpsewhich(&self, path: &str) -> KpseResult {
self.kpathsea.kpsewhich(path)
}
}
impl<Char:CharType> FileSystem<Char> for KpseVirtualFileSystem<Char> {
type F = Ptr<VirtualFile<Char>>;
fn new(pwd:PathBuf) -> Self { KpseVirtualFileSystem {
pwd:pwd.clone(),
kpathsea:Kpathsea::new(pwd),
files:HashMap::default()
} }
fn set_pwd(&mut self, pwd: PathBuf) -> PathBuf {
let old = std::mem::replace(&mut self.kpathsea, Kpathsea::new(pwd));
old.pwd
}
fn get(&mut self, path: &str) -> Self::F {
let ret = self.kpathsea.kpsewhich(path);
match self.files.entry(ret.path) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let s: Option<Vec<u8>> = self.kpathsea.get(&self.pwd,e.key());
let ret = Ptr::new(VirtualFile{
path:e.key().clone(),
contents:RwLock::new(s),
open:RwLock::new(None)
});
e.insert(ret.clone());
ret
}
}
}
}