tex_engine/engine/
filesystem.rs

1/*! A TeX [`Engine`](crate::engine::Engine) needs to read and write files, and find them
2    in the using simple file names relative to e.g. the `TEXMF` tree.
3
4    This module provides [`FileSystem`](FileSystem) traits and implementations for
5    [`PhysicalFile`](PhysicalFile) and [`VirtualFile`](VirtualFile) types. The latter
6    do not actually modify the local file system.
7
8    For retrieval, a bare bones implementation of [`Kpathsea`](Kpathsea) is provided.
9*/
10
11pub mod kpathsea;
12
13use std::collections::hash_map::Entry;
14use std::marker::PhantomData;
15use std::path::PathBuf;
16use std::sync::RwLock;
17use kpathsea::Kpathsea;
18use ahash::HashMap;
19use crate::engine::filesystem::kpathsea::KpseResult;
20use crate::engine::mouth::string_source::StringSource;
21use crate::engine::state::State;
22use crate::tex::catcodes::CategoryCode;
23use crate::tex::token::{BaseToken, Token};
24use crate::utils::errors::{OtherError, TeXError};
25use crate::utils::Ptr;
26use crate::utils::strings::CharType;
27
28pub trait File<Char:CharType>:Clone {
29    fn path(&self) -> &PathBuf;
30    fn exists(&self) -> bool;
31    fn content_string(&self) -> Vec<u8>;
32    fn open_out(&self);
33    fn open_in(&self);
34    fn close_out(&self);
35    fn close_in(&self);
36    fn write(&self,string:&str);
37    fn eof(&self) -> bool;
38    fn read<T:Token<Char=Char>,S:State<T>>(&self,state:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>>;
39}
40
41pub trait FileSystem<Char:CharType>:'static {
42    type F:File<Char>;
43    fn new(pwd:PathBuf) -> Self;
44    fn get(&mut self,path:&str) -> Self::F;
45    fn set_pwd(&mut self, pwd:PathBuf) -> PathBuf;
46}
47
48pub struct PhysicalFile<Char:CharType> {path:PathBuf,contents:Vec<u8>,phantom:PhantomData<Char>}
49impl<Char:CharType> PhysicalFile<Char> {
50    pub fn new(path:PathBuf) -> Self {
51        PhysicalFile {
52            contents: {
53                std::fs::read(&path).ok().unwrap_or(vec!())
54            },
55            phantom: PhantomData,
56            path
57        }
58    }
59}
60impl<Char:CharType> File<Char> for Ptr<PhysicalFile<Char>> {
61    fn path(&self) -> &PathBuf { &self.path }
62    fn exists(&self) -> bool { self.path.exists() }
63    fn content_string(&self) -> Vec<u8> {
64        self.contents.clone()
65    }
66    fn open_out(&self) {
67        todo!("Physical file system not implemented yet")
68    }
69    fn close_in(&self) {
70        todo!("Physical file system not implemented yet")
71    }
72    fn close_out(&self) {
73        todo!("Physical file system not implemented yet")
74    }
75    fn eof(&self) -> bool {
76        todo!("Physical file system not implemented yet")
77    }
78    fn open_in(&self) {
79        todo!("Physical file system not implemented yet")
80    }
81    fn write(&self,_:&str) {
82        todo!("Physical file system not implemented yet")
83    }
84    fn read<T:Token,S:State<T>>(&self,_:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
85        todo!("Physical file system not implemented yet")
86    }
87}
88
89
90pub struct VirtualFile<Char:CharType> {path:PathBuf,contents:RwLock<Option<Vec<u8>>>,open:RwLock<Option<StringSource<Char>>>}
91impl<Char:CharType> File<Char> for Ptr<VirtualFile<Char>> {
92    fn path(&self) -> &PathBuf { &self.path }
93    fn exists(&self) -> bool { self.contents.read().unwrap().is_some() }
94    fn content_string(&self) -> Vec<u8> {
95        match &*self.contents.read().unwrap() {
96            Some(s) => s.clone(),
97            None => vec!()
98        }
99    }
100    fn close_out(&self) {}
101    fn open_out(&self) {
102        let mut w = self.contents.write().unwrap();
103        *w = Some(vec!());
104    }
105    fn open_in(&self) {
106        let w = self.contents.read().unwrap();
107        let mut open = self.open.write().unwrap();
108        let v = match &*w {
109            None => vec!(),
110            Some(v) => v.clone()
111        };
112        *open = Some(StringSource::new(v,Some(Ptr::new(self.path.to_str().unwrap().to_string()))));
113    }
114    fn close_in(&self) {
115        let mut open = self.open.write().unwrap();
116        *open = None;
117    }
118    fn eof(&self) -> bool {
119        let mut open = self.open.write().unwrap();
120        open.as_mut().unwrap().preview().is_empty()//.peek().is_none()
121    }
122    fn read<T: Token<Char=Char>, S: State<T>>(&self, state: &S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
123        let mut open = self.open.write().unwrap();
124        match &mut *open {
125            None => Err(OtherError{msg:"File not open".to_string(),source:None,cause:None}.into()),
126            Some(m) => {
127                let line = m.line();
128                let mut ret: Vec<T> = vec!();
129                let mut ingroups = 0;
130                loop {
131                    if ingroups != 0 || m.line() == line {
132                        match m.get_next::<T>(state.get_catcode_scheme(),state.get_endlinechar())? {
133                            None =>
134                                return Ok(ret),
135                            Some(tk) => {
136                                match tk.base() {
137                                    BaseToken::Char(_,CategoryCode::BeginGroup) => ingroups += 1,
138                                    BaseToken::Char(_,CategoryCode::EndGroup) => ingroups -= 1,
139                                    _ => () // TODO negative ingroups values?
140                                }
141                                ret.push(tk)
142                            }
143                        }
144                    } else { return Ok(ret) }
145                }
146            }
147        }
148    }
149    fn write(&self, string: &str) {
150        let mut write = self.contents.write().unwrap();
151        let mut v = write.as_mut().unwrap();
152        v.extend(string.as_bytes());
153        v.push(b'\n');
154    }
155}
156
157pub struct KpsePhysicalFileSystem<Char:CharType> {kpathsea:Kpathsea,phantom:PhantomData<Char>}
158impl<Char:CharType> FileSystem<Char> for KpsePhysicalFileSystem<Char> {
159    type F = Ptr<PhysicalFile<Char>>;
160    fn new(pwd:PathBuf) -> Self { KpsePhysicalFileSystem {kpathsea:Kpathsea::new(pwd),phantom:PhantomData} }
161    fn get(&mut self, _path: &str) -> Self::F {
162        todo!("Physical file system not implemented yet")
163    }
164    fn set_pwd(&mut self, _pwd: PathBuf) -> PathBuf {
165        todo!("Physical file system not implemented yet")
166    }
167}
168
169pub struct KpseVirtualFileSystem<Char:CharType> {pwd:PathBuf,kpathsea:Kpathsea,files:HashMap<PathBuf,Ptr<VirtualFile<Char>>>}
170impl<Char:CharType> KpsePhysicalFileSystem<Char> {
171    pub fn kpsewhich(&self, path: &str) -> KpseResult {
172        self.kpathsea.kpsewhich(path)
173    }
174}
175impl<Char:CharType> KpseVirtualFileSystem<Char> {
176    pub fn kpsewhich(&self, path: &str) -> KpseResult {
177        self.kpathsea.kpsewhich(path)
178    }
179}
180impl<Char:CharType> FileSystem<Char> for KpseVirtualFileSystem<Char> {
181    type F = Ptr<VirtualFile<Char>>;
182    fn new(pwd:PathBuf) -> Self { KpseVirtualFileSystem {
183        pwd:pwd.clone(),
184        kpathsea:Kpathsea::new(pwd),
185        files:HashMap::default()
186    } }
187    fn set_pwd(&mut self, pwd: PathBuf) -> PathBuf {
188        let old = std::mem::replace(&mut self.kpathsea, Kpathsea::new(pwd));
189        old.pwd
190    }
191    fn get(&mut self, path: &str) -> Self::F {
192        let ret = self.kpathsea.kpsewhich(path);
193        match self.files.entry(ret.path) {
194            Entry::Occupied(e) => e.get().clone(),
195            Entry::Vacant(e) => {
196                let s: Option<Vec<u8>> = self.kpathsea.get(&self.pwd,e.key());
197                let ret = Ptr::new(VirtualFile{
198                    path:e.key().clone(),
199                    contents:RwLock::new(s),
200                    open:RwLock::new(None)
201                });
202                e.insert(ret.clone());
203                ret
204            }
205        }
206    }
207}