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 Dynamic(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::Dynamic(dyn_file) => Ok(FileReader::Dynamic(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::Dynamic(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::Dynamic(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
176fn get_file_for_root(root: &str, path: &str) -> Option<DynFile> {
178 let pathbuff = Path::new(&*root).join(path);
179 if pathbuff.is_file() {
180 Some(DynFile::new(Arc::from(pathbuff.to_str()?), Arc::from(path)))
181 } else {
182 None
183 }
184}
185
186fn iter_root(root: &str) -> impl Iterator<Item = File> {
188 let root_path = PathBuf::from(&*root);
189 walkdir::WalkDir::new(&root_path)
190 .into_iter()
191 .filter_map(move |entry| {
192 let entry = entry.ok()?;
193 if entry.file_type().is_file() {
194 let relative_path = entry.path().strip_prefix(&root_path).ok()?;
195 Some(File {
196 inner: FileKind::Dynamic(DynFile::new(
197 Arc::from(entry.path().to_str()?),
198 Arc::from(relative_path.to_str()?),
199 )),
200 })
201 } else {
202 None
203 }
204 })
205}
206
207#[derive(Debug, Clone)]
209struct DynamicSilo {
210 root: Arc<str>,
211}
212
213impl DynamicSilo {
214 pub fn new(root: &str) -> Self {
217 Self { root: Arc::from(root) }
218 }
219
220 pub fn get_file(&self, path: &str) -> Option<DynFile> {
223 get_file_for_root(self.root.as_ref(), path)
224 }
225
226 pub fn iter(&self) -> impl Iterator<Item = File> {
229 iter_root(self.root.as_ref())
230 }
231}
232
233#[derive(Debug, Clone)]
236struct StaticSilo {
237 root: &'static str,
238}
239
240impl StaticSilo {
241 pub const fn new(root: &'static str) -> Self {
243 Self { root }
244 }
245
246 pub fn get_file(&self, path: &str) -> Option<DynFile> {
249 get_file_for_root(self.root, path)
250 }
251
252 pub fn iter(&self) -> impl Iterator<Item = File> {
255 iter_root(self.root)
256 }
257}
258
259#[derive(Debug, Clone)]
261enum InnerSilo {
262 Embed(EmbedSilo),
263 Static(StaticSilo),
264 Dynamic(DynamicSilo),
265}
266
267#[derive(Debug, Clone)]
269pub struct Silo {
270 inner: InnerSilo,
271}
272
273impl Silo {
274
275 #[doc(hidden)]
276 pub const fn from_embedded(phf_map: &'static phf::Map<&'static str, EmbedEntry>, root: &'static str) -> Self {
278 Self {
279 inner: InnerSilo::Embed(EmbedSilo::new(phf_map, root)),
280 }
281 }
282
283 #[doc(hidden)]
284 pub const fn from_static(path: &'static str) -> Self {
286 Self {
287 inner: InnerSilo::Static(StaticSilo::new(path)),
288 }
289 }
290
291 pub fn new(path: &str) -> Self {
293 Self {
294 inner: InnerSilo::Dynamic(DynamicSilo::new(path)),
295 }
296 }
297
298 pub fn into_dynamic(self) -> Self {
301 match self.inner {
302 InnerSilo::Embed(emb_silo) => Self::from_static(&*emb_silo.root),
303 InnerSilo::Static(_) => self,
304 InnerSilo::Dynamic(_) => self,
305 }
306 }
307
308 pub fn auto_dynamic(self) -> Self {
312 if cfg!(debug_assertions) {
313 self.into_dynamic()
314 } else {
315 self
316 }
317 }
318
319 pub fn is_dynamic(&self) -> bool {
321 matches!(self.inner, InnerSilo::Static(_) | InnerSilo::Dynamic(_))
322 }
323
324 pub fn is_embedded(&self) -> bool {
326 matches!(self.inner, InnerSilo::Embed(_))
327 }
328
329 pub fn get_file(&self, path: &str) -> Option<File> {
332 match &self.inner {
333 InnerSilo::Embed(embed) => embed.get_file(path).map(|f| File {
334 inner: FileKind::Embed(f),
335 }),
336 InnerSilo::Static(dyn_silo) => dyn_silo.get_file(path).map(|f| File {
337 inner: FileKind::Dynamic(f),
338 }),
339 InnerSilo::Dynamic(dyn_silo) => dyn_silo.get_file(path).map(|f| File {
340 inner: FileKind::Dynamic(f),
341 }),
342 }
343 }
344
345 pub fn iter(&self) -> Box<dyn Iterator<Item = File> + '_> {
348 match &self.inner {
349 InnerSilo::Embed(embd) => Box::new(embd.iter()),
350 InnerSilo::Static(dynm) => Box::new(dynm.iter()),
351 InnerSilo::Dynamic(dynm) => Box::new(dynm.iter()),
352 }
353 }
354}
355
356
357
358#[derive(Debug, Clone)]
361pub struct SiloSet {
362 pub silos: Vec<Silo>,
364}
365
366impl SiloSet {
367 pub fn new(dirs: Vec<Silo>) -> Self {
371 Self { silos: dirs }
372 }
373
374
375 pub fn get_file(&self, name: &str) -> Option<File> {
379 for silo in self.silos.iter().rev() {
380 if let Some(file) = silo.get_file(name) {
381 return Some(file);
382 }
383 }
384 None
385 }
386
387 pub fn iter(&self) -> impl Iterator<Item = File> + '_ {
391 self.silos.iter().rev().flat_map(|silo| silo.iter())
392 }
393
394 pub fn iter_override(&self) -> impl Iterator<Item = File> + '_ {
398 let mut history = std::collections::HashSet::new();
399 self.iter().filter(move |file| history.insert(file.clone()) )
400 }
401}
402
403
404pub enum FileReader {
406 Embed(std::io::Cursor<&'static [u8]>),
407 Dynamic(std::fs::File),
408}
409
410impl std::io::Read for FileReader {
412 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
413 match self {
414 FileReader::Embed(c) => c.read(buf),
415 FileReader::Dynamic(f) => f.read(buf),
416 }
417 }
418}