1use std::{collections::VecDeque, path::PathBuf};
2
3pub use fs_embed_macros::fs_embed;
4
5pub struct FileMetaData {
6 pub modified: std::time::SystemTime,
8 pub size: u64,
10}
11
12#[derive(Debug, Clone)]
13enum InnerFile {
14 Embed(include_dir::File<'static>),
15 Path {
16 root: std::path::PathBuf,
17 path: std::path::PathBuf,
18 },
19}
20
21impl PartialEq for InnerFile {
22 fn eq(&self, other: &Self) -> bool {
23 self.path() == other.path()
24 }
25}
26
27impl Eq for InnerFile {}
28
29impl std::hash::Hash for InnerFile {
30 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
31 self.path().hash(state);
32 }
33}
34
35impl InnerFile {
36 #[inline(always)]
37 fn absolute_path(&self) -> &std::path::Path {
38 match self {
39 InnerFile::Embed(file) => file.path(),
40 InnerFile::Path { path, .. } => path.as_path(),
41 }
42 }
43
44 #[inline(always)]
45 fn is_embedded(&self) -> bool {
46 matches!(self, InnerFile::Embed(_))
47 }
48
49 #[inline(always)]
50 pub fn path(&self) -> &std::path::Path {
51 match self {
52 InnerFile::Embed(dir) => dir.path(),
53 InnerFile::Path { root, path } => path.strip_prefix(root).unwrap_or(path),
54 }
55 }
56}
57
58
59#[derive(Debug, Clone)]
60enum InnerDir {
61 Embed(include_dir::Dir<'static>, &'static str),
62 Path {
63 root: std::path::PathBuf,
64 path: std::path::PathBuf,
65 },
66}
67
68impl PartialEq for InnerDir {
69 fn eq(&self, other: &Self) -> bool {
70 self.path() == other.path()
71 }
72}
73
74impl Eq for InnerDir {}
75
76impl std::hash::Hash for InnerDir {
77 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
78 self.path().hash(state);
79 }
80}
81
82impl InnerDir {
83
84 fn into_dynamic(self) -> Self {
85 match &self {
86 InnerDir::Embed(dir, path) =>
87 Self::Path { root: PathBuf::from(path), path: PathBuf::from(path).join(dir.path()) },
88 InnerDir::Path { .. } => self,
89 }
90 }
91
92 #[inline(always)]
93 fn is_embedded(&self) -> bool {
94 matches!(self, InnerDir::Embed(..))
95 }
96
97 #[inline(always)]
98 fn path(&self) -> &std::path::Path {
99 match self {
100 InnerDir::Embed(dir, _) => dir.path(),
101 InnerDir::Path { root, path } => path.strip_prefix(root).unwrap_or(path),
102 }
103 }
104
105 #[inline(always)]
106 fn absolute_path(&self) -> &std::path::Path {
107 match self {
108 InnerDir::Embed(dir, _) => dir.path(),
109 InnerDir::Path { path, .. } => path.as_path(),
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
115enum InnerEntry {
116 File(InnerFile),
117 Dir(InnerDir),
118}
119
120impl PartialEq for InnerEntry {
121 fn eq(&self, other: &Self) -> bool {
122 match (self, other) {
123 (InnerEntry::File(a), InnerEntry::File(b)) => a == b,
124 (InnerEntry::Dir(a), InnerEntry::Dir(b)) => a == b,
125 _ => false,
126 }
127 }
128}
129
130impl Eq for InnerEntry {}
131
132impl std::hash::Hash for InnerEntry {
133 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
134 match self {
135 InnerEntry::File(file) => {
136 0u8.hash(state); file.hash(state)
138 }
139 InnerEntry::Dir(dir) => {
140 1u8.hash(state); dir.hash(state)
142 }
143 }
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
148pub struct Dir {
153 inner: InnerDir,
154}
155
156impl Dir {
157 pub const fn from_embedded(dir: include_dir::Dir<'static>, path: &'static str) -> Self {
160 Self {
161 inner: InnerDir::Embed(dir, path),
162 }
163 }
164
165 pub fn from_path(path: &std::path::Path) -> Self {
168 const BASE_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
169 let base_path = std::path::PathBuf::from(BASE_DIR);
170 Self {
171 inner: InnerDir::Path {
172 root: base_path.join(path),
173 path: base_path.join(path),
174 }
175 }
176 }
177
178 pub fn into_dynamic(self) -> Self {
181 Self {
182 inner: self.inner.into_dynamic(),
183 }
184 }
185
186 pub fn auto_dynamic(self) -> Self {
189 if cfg!(debug_assertions) {
190 return self.into_dynamic();
191 } else {
192 return self;
193 }
194 }
195
196 pub fn from_str(path: &'static str) -> Self {
199 Self::from_path(std::path::Path::new(path))
200 }
201
202 pub fn is_embedded(&self) -> bool {
204 self.inner.is_embedded()
205 }
206
207 pub fn path(&self) -> &std::path::Path {
209 self.inner.path()
210 }
211
212 pub fn absolute_path(&self) -> &std::path::Path {
214 self.inner.absolute_path()
215 }
216
217 #[doc(hidden)]
220 pub fn entries(&self) -> Vec<DirEntry> {
221 match &self.inner {
222 InnerDir::Embed(dir, root) => dir
223 .files()
224 .map(|file| DirEntry {
225 inner: InnerEntry::File(InnerFile::Embed(file.clone())),
226 })
227 .chain(dir.dirs().map(|subdir| DirEntry {
228 inner: InnerEntry::Dir(InnerDir::Embed(subdir.clone(), root)),
229 }))
230 .collect(),
231 InnerDir::Path { root, path } => {
232 let mut entries = Vec::new();
233 if let Ok(entries_iter) = std::fs::read_dir(path) {
234 for entry in entries_iter.flatten() {
235 let entry_path = entry.path();
236 if entry_path.is_file() {
237 entries.push(DirEntry {
238 inner: InnerEntry::File(InnerFile::Path {
239 root: root.clone(),
240 path: entry_path,
241 }),
242 });
243 } else if entry_path.is_dir() {
244 entries.push(DirEntry {
245 inner: InnerEntry::Dir(InnerDir::Path {
246 root: root.clone(),
247 path: entry_path,
248 }),
249 });
250 }
251 }
252 }
253 entries
254 }
255 }
256 }
257
258 pub fn get_file(&self, name: &str) -> Option<File> {
261 match &self.inner {
262 InnerDir::Embed(dir, _) => {
263 dir.get_file(dir.path().join(name)).map(|file| File {
264 inner: InnerFile::Embed(file.clone()),
265 })
266 },
267 InnerDir::Path { root, path } => {
268 let new_path = path.join(name);
269 if new_path.is_file() {
270 Some(File {
271 inner: InnerFile::Path {
272 root: root.clone(),
273 path: new_path,
274 },
275 })
276 } else {
277 None
278 }
279 }
280 }
281 }
282
283 pub fn walk(&self) -> impl Iterator<Item = File> {
286 let mut queue: VecDeque<DirEntry> = VecDeque::from_iter(self.entries().into_iter());
287 std::iter::from_fn(move || {
288 while let Some(entry) = queue.pop_front() {
289 match entry.inner {
290 InnerEntry::File(file) => return Some(File { inner: file }),
291 InnerEntry::Dir(dir) => queue.extend(Dir { inner: dir }.entries()),
292 }
293 }
294 None
295 })
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Hash)]
300pub struct File {
303 inner: InnerFile,
304}
305
306impl File {
307 pub fn file_name(&self) -> Option<&str> {
309 self.path().file_name().and_then(|name| name.to_str())
310 }
311
312 pub fn extension(&self) -> Option<&str> {
314 self.path().extension().and_then(|ext| ext.to_str())
315 }
316
317 pub fn absolute_path(&self) -> &std::path::Path {
319 self.inner.absolute_path()
320 }
321
322 pub fn is_embedded(&self) -> bool {
324 self.inner.is_embedded()
325 }
326
327 pub fn path(&self) -> &std::path::Path {
329 self.inner.path()
330 }
331
332 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
334 match &self.inner {
335 InnerFile::Embed(file) => Ok(file.contents().to_vec()),
336 InnerFile::Path { path, .. } => std::fs::read(path),
337 }
338 }
339
340 pub fn read_str(&self) -> std::io::Result<String> {
343 match &self.inner {
344 InnerFile::Embed(file) => std::str::from_utf8(file.contents())
345 .map(str::to_owned)
346 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
347 InnerFile::Path { path, .. } => std::fs::read_to_string(path),
348 }
349 }
350
351 pub fn metadata(&self) -> std::io::Result<FileMetaData> {
353 match &self.inner {
354 InnerFile::Embed(file) => {
355 if let Some(metadata) = file.metadata() {
356 Ok(FileMetaData {
357 modified: metadata.modified(),
358 size: file.contents().len() as u64,
359 })
360 } else {
361 Err(std::io::Error::new(
362 std::io::ErrorKind::Other,
363 "Failed to get embedded file metadata",
364 ))
365 }
366 }
367 InnerFile::Path { path, .. } => {
368 let metadata = std::fs::metadata(path)?;
369 Ok(FileMetaData {
370 modified: metadata.modified()?,
371 size: metadata.len(),
372 })
373 }
374 }
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Hash)]
379pub struct DirEntry {
381 inner: InnerEntry,
382}
383
384impl DirEntry {
385 pub fn from_file(file: File) -> Self {
387 Self {
388 inner: InnerEntry::File(file.inner),
389 }
390 }
391
392 pub fn from_dir(dir: Dir) -> Self {
394 Self {
395 inner: InnerEntry::Dir(dir.inner),
396 }
397 }
398
399 pub fn path(&self) -> &std::path::Path {
401 match &self.inner {
402 InnerEntry::File(file) => file.path(),
403 InnerEntry::Dir(dir) => dir.path(),
404 }
405 }
406
407 pub fn absolute_path(&self) -> &std::path::Path {
409 match &self.inner {
410 InnerEntry::File(file) => file.absolute_path(),
411 InnerEntry::Dir(dir) => dir.absolute_path(),
412 }
413 }
414
415 pub fn is_embedded(&self) -> bool {
417 matches!(&self.inner, InnerEntry::File(InnerFile::Embed(_)))
418 || matches!(&self.inner, InnerEntry::Dir(InnerDir::Embed(..)))
419 }
420
421 pub const fn is_file(&self) -> bool {
423 matches!(&self.inner, InnerEntry::File(_))
424 }
425
426 pub const fn is_dir(&self) -> bool {
428 matches!(&self.inner, InnerEntry::Dir(_))
429 }
430
431 pub fn into_file(self) -> Option<File> {
433 if let InnerEntry::File(file) = self.inner {
434 Some(File { inner: file })
435 } else {
436 None
437 }
438 }
439
440 pub fn into_dir(self) -> Option<Dir> {
442 if let InnerEntry::Dir(dir) = self.inner {
443 Some(Dir { inner: dir })
444 } else {
445 None
446 }
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Hash)]
451pub struct DirSet {
454 pub dirs: Vec<Dir>,
456}
457
458impl DirSet {
459 pub fn new(dirs: Vec<Dir>) -> Self {
462 Self { dirs }
463 }
464
465 #[doc(hidden)]
468 pub fn entries(&self) -> Vec<DirEntry> {
469 self.dirs.iter().flat_map(|dir| dir.entries()).collect()
470 }
471
472 pub fn get_file(&self, name: &str) -> Option<File> {
475 for dir in self.dirs.iter().rev() {
476 if let Some(file) = dir.get_file(name) {
477 return Some(file);
478 }
479 }
480 None
481 }
482
483 pub fn walk(&self) -> impl Iterator<Item = File> {
486 let mut queue: VecDeque<DirEntry> = VecDeque::with_capacity(self.dirs.len() * 128); for dir in self.dirs.iter() {
488 queue.push_back(DirEntry::from_dir(dir.clone()));
489 }
490 std::iter::from_fn(move || {
491 while let Some(entry) = queue.pop_front() {
492 match entry.inner {
493 InnerEntry::File(file) => return Some(File { inner: file }),
494 InnerEntry::Dir(dir) => {
495 for child in( Dir { inner: dir }).entries().into_iter().rev() {
496 queue.push_front(child);
497 }
498 },
499 }
500 }
501 None
502 })
503 }
504
505 pub fn walk_override(&self) -> impl Iterator<Item = File> {
508 let mut history = std::collections::HashSet::new();
509 let mut queue: VecDeque<DirEntry> = VecDeque::with_capacity(self.dirs.len() * 128); for dir in self.dirs.iter() {
511 queue.push_front(DirEntry::from_dir(dir.clone()));
512 }
513 std::iter::from_fn(move || {
514 while let Some(entry) = queue.pop_front() {
515 match entry.inner {
516 InnerEntry::File(file) => {
517 if history.insert(file.path().to_owned()) {
518 return Some(File { inner: file })
519 }
520 },
521 InnerEntry::Dir(dir) => {
522 for child in( Dir { inner: dir }).entries().into_iter() {
523 queue.push_front(child);
524 }
525 },
526 }
527 }
528 None
529 })
530 }
531}