1pub use phf::phf_map;
3pub use phf;
4use std::hash::Hash;
5use std::io::Cursor;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use thiserror::Error;
9
10
11#[derive(Debug, Error)]
13pub enum Error {
14 #[error("Failed to decode file contents: {source}")]
15 DecodeError {
16 #[from]
17 source: std::string::FromUtf8Error,
18 },
19 #[error("File not found")]
20 NotFound,
21 #[error("I/O error: {source}")]
22 IoError {
23 #[from]
24 source: std::io::Error,
25 },
26}
27
28
29#[derive(Debug)]
31pub struct EmbedEntry {
32 pub path: &'static str,
33 pub contents: &'static [u8],
34 pub size: usize,
35 pub modified: u64,
36}
37
38#[derive(Copy, Clone, Debug)]
40struct EmbedFile {
41 inner: &'static EmbedEntry,
42}
43
44impl EmbedFile {
45 pub fn path(&self) -> &Path {
47 Path::new(self.inner.path)
48 }
49}
50
51#[derive(Debug, Clone)]
53enum FileKind {
54 Embed(EmbedFile),
55 Dyn(DynFile),
56}
57
58#[derive(Debug, Clone)]
60pub struct File {
61 inner: FileKind,
62}
63
64impl File {
65 pub fn reader(&self) -> Result<FileReader, Error> {
67 match &self.inner {
68 FileKind::Embed(embed) => Ok(FileReader::Embed(Cursor::new(embed.inner.contents))),
69 FileKind::Dyn(dyn_file) => Ok(FileReader::Dyn(std::fs::File::open(
70 dyn_file.absolute_path(),
71 )?)),
72 }
73 }
74
75 pub fn path(&self) -> &Path {
77 match &self.inner {
78 FileKind::Embed(embed) => embed.path(),
79 FileKind::Dyn(dyn_file) => dyn_file.path(),
80 }
81 }
82
83 pub fn is_embedded(&self) -> bool {
85 matches!(self.inner, FileKind::Embed(_))
86 }
87
88 pub fn absolute_path(&self) -> Option<&Path> {
90 match &self.inner {
91 FileKind::Embed(_) => None,
92 FileKind::Dyn(dyn_file) => Some(dyn_file.absolute_path()),
93 }
94 }
95
96 pub fn extension(&self) -> Option<&str> {
98 self.path().extension().and_then(|s| s.to_str())
99 }
100}
101
102impl PartialEq for File {
104 fn eq(&self, other: &Self) -> bool {
105 self.path() == other.path()
106 }
107}
108
109impl Hash for File {
111 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
112 self.path().hash(state);
113 }
114}
115
116impl Eq for File {}
117
118
119
120#[derive(Debug, Clone)]
122struct EmbedSilo {
123 map: &'static phf::Map<&'static str, EmbedEntry>,
124 root: &'static str,
125}
126
127impl EmbedSilo {
128 pub const fn new(map: &'static phf::Map<&'static str, EmbedEntry>, root: &'static str) -> Self {
130 Self { map, root }
131 }
132
133 pub fn get_file(&self, path: &str) -> Option<EmbedFile> {
136 self.map.get(path).map(|entry| EmbedFile { inner: entry })
137 }
138
139 pub fn iter(&self) -> impl Iterator<Item = File> + '_ {
141 self.map.values().map(|entry| File {
142 inner: FileKind::Embed(EmbedFile { inner: entry }),
143 })
144 }
145}
146
147#[derive(Debug, Clone)]
149struct DynFile {
150 rel_path: Arc<str>,
151 full_path: Arc<str>,
152}
153
154impl DynFile {
155 pub fn new<S: AsRef<str>>(full_path: S, rel_path: S) -> Self {
159 Self {
160 rel_path: Arc::from(rel_path.as_ref()),
161 full_path: Arc::from(full_path.as_ref()),
162 }
163 }
164
165 pub fn path(&self) -> &Path {
167 Path::new(&*self.rel_path)
168 }
169
170 pub fn absolute_path(&self) -> &Path {
172 Path::new(&*self.full_path)
173 }
174}
175
176#[derive(Debug, Clone)]
178struct DynSilo {
179 root: &'static str,
180}
181
182
183impl DynSilo {
184 pub const fn new(root: &'static str) -> Self {
186 Self { root }
187 }
188
189 pub fn get_file(&self, path: &str) -> Option<DynFile> {
191 let pathbuff = Path::new(&*self.root).join(path);
192 if pathbuff.is_file() {
193 Some(DynFile::new(Arc::from(pathbuff.to_str()?), Arc::from(path)))
194 } else {
195 None
196 }
197 }
198
199 pub fn iter(&self) -> impl Iterator<Item = File> {
201 let root_path = PathBuf::from(&*self.root);
202 walkdir::WalkDir::new(&root_path)
203 .into_iter()
204 .filter_map(move |entry| {
205 let entry = entry.ok()?;
206 if entry.file_type().is_file() {
207 let relative_path = entry.path().strip_prefix(&root_path).ok()?;
208 Some(File {
209 inner: FileKind::Dyn(DynFile::new(
210 Arc::from(entry.path().to_str()?),
211 Arc::from(relative_path.to_str()?),
212 )),
213 })
214 } else {
215 None
216 }
217 })
218 }
219}
220
221#[derive(Debug, Clone)]
223enum InnerSilo {
224 Embed(EmbedSilo),
225 Dyn(DynSilo),
226}
227
228#[derive(Debug, Clone)]
230pub struct Silo {
231 inner: InnerSilo,
232}
233
234impl Silo {
235
236 pub const fn from_embedded(phf_map: &'static phf::Map<&'static str, EmbedEntry>, root: &'static str) -> Self {
238 Self {
239 inner: InnerSilo::Embed(EmbedSilo::new(phf_map, root)),
240 }
241 }
242
243 pub const fn from_path(path: &'static str) -> Self {
245 Self {
246 inner: InnerSilo::Dyn(DynSilo::new(path)),
247 }
248 }
249
250 pub fn into_dynamic(self) -> Self {
252 match self.inner {
253 InnerSilo::Embed(emb_silo) => Self::from_path(&*emb_silo.root),
254 InnerSilo::Dyn(_) => self,
255 }
256 }
257
258 pub fn auto_dynamic(self) -> Self {
262 if cfg!(debug_assertions) {
263 return self.into_dynamic();
264 } else {
265 return self;
266 }
267 }
268
269 pub fn is_dynamic(&self) -> bool {
271 matches!(self.inner, InnerSilo::Dyn(_))
272 }
273
274 pub fn is_embedded(&self) -> bool {
276 matches!(self.inner, InnerSilo::Embed(_))
277 }
278
279 pub fn get_file(&self, path: &str) -> Option<File> {
281 match &self.inner {
282 InnerSilo::Embed(embed) => embed.get_file(path).map(|f| File {
283 inner: FileKind::Embed(f),
284 }),
285 InnerSilo::Dyn(dyn_silo) => dyn_silo.get_file(path).map(|f| File {
286 inner: FileKind::Dyn(f),
287 }),
288 }
289 }
290
291 pub fn iter(&self) -> Box<dyn Iterator<Item = File> + '_> {
293 match &self.inner {
294 InnerSilo::Embed(embd) => Box::new(embd.iter()),
295 InnerSilo::Dyn(dynm) => Box::new(dynm.iter()),
296 }
297 }
298
299}
300
301
302
303#[derive(Debug, Clone)]
306pub struct SiloSet {
307 pub silos: Vec<Silo>,
309}
310
311impl SiloSet {
312 pub fn new(dirs: Vec<Silo>) -> Self {
316 Self { silos: dirs }
317 }
318
319
320 pub fn get_file(&self, name: &str) -> Option<File> {
324 for silo in self.silos.iter().rev() {
325 if let Some(file) = silo.get_file(name) {
326 return Some(file);
327 }
328 }
329 None
330 }
331
332 pub fn iter(&self) -> impl Iterator<Item = File> + '_ {
336 self.silos.iter().rev().flat_map(|silo| silo.iter())
337 }
338
339 pub fn iter_override(&self) -> impl Iterator<Item = File> + '_ {
343 let mut history = std::collections::HashSet::new();
344 self.iter().filter(move |file| history.insert(file.clone()) )
345 }
346}
347
348
349pub enum FileReader {
351 Embed(std::io::Cursor<&'static [u8]>),
352 Dyn(std::fs::File),
353}
354
355impl std::io::Read for FileReader {
357 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
358 match self {
359 FileReader::Embed(c) => c.read(buf),
360 FileReader::Dyn(f) => f.read(buf),
361 }
362 }
363}