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#[derive(Debug, Clone)]
59enum InnerDir {
60 Embed(include_dir::Dir<'static>, &'static str),
61 Path {
62 root: std::path::PathBuf,
63 path: std::path::PathBuf,
64 },
65}
66
67impl PartialEq for InnerDir {
68 fn eq(&self, other: &Self) -> bool {
69 self.path() == other.path()
70 }
71}
72
73impl Eq for InnerDir {}
74
75impl std::hash::Hash for InnerDir {
76 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
77 self.path().hash(state);
78 }
79}
80
81impl InnerDir {
82 fn into_dynamic(self) -> Self {
83 match &self {
84 InnerDir::Embed(dir, path) => Self::Path {
85 root: PathBuf::from(path),
86 path: PathBuf::from(path).join(dir.path()),
87 },
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 pub fn entries(&self) -> Vec<DirEntry> {
219 match &self.inner {
220 InnerDir::Embed(dir, root) => dir
221 .files()
222 .map(|file| DirEntry {
223 inner: InnerEntry::File(InnerFile::Embed(file.clone())),
224 })
225 .chain(dir.dirs().map(|subdir| DirEntry {
226 inner: InnerEntry::Dir(InnerDir::Embed(subdir.clone(), root)),
227 }))
228 .collect(),
229 InnerDir::Path { root, path } => {
230 let mut entries = Vec::new();
231 if let Ok(entries_iter) = std::fs::read_dir(path) {
232 for entry in entries_iter.flatten() {
233 let entry_path = entry.path();
234 if entry_path.is_file() {
235 entries.push(DirEntry {
236 inner: InnerEntry::File(InnerFile::Path {
237 root: root.clone(),
238 path: entry_path,
239 }),
240 });
241 } else if entry_path.is_dir() {
242 entries.push(DirEntry {
243 inner: InnerEntry::Dir(InnerDir::Path {
244 root: root.clone(),
245 path: entry_path,
246 }),
247 });
248 }
249 }
250 }
251 entries
252 }
253 }
254 }
255
256 pub fn get_file(&self, name: &str) -> Option<File> {
259 match &self.inner {
260 InnerDir::Embed(dir, _) => dir.get_file(dir.path().join(name)).map(|file| File {
261 inner: InnerFile::Embed(file.clone()),
262 }),
263 InnerDir::Path { root, path } => {
264 let new_path = path.join(name);
265 if new_path.is_file() {
266 Some(File {
267 inner: InnerFile::Path {
268 root: root.clone(),
269 path: new_path,
270 },
271 })
272 } else {
273 None
274 }
275 }
276 }
277 }
278
279 pub fn get_dir(&self, name: &str) -> Option<Dir> {
281 match &self.inner {
282 InnerDir::Embed(dir, root) => dir.get_dir(dir.path().join(name)).map(|subdir| Dir {
283 inner: InnerDir::Embed(subdir.clone(), root),
284 }),
285 InnerDir::Path { root, path } => {
286 let new_path = path.join(name);
287 if new_path.is_dir() {
288 Some(Dir {
289 inner: InnerDir::Path {
290 root: root.clone(),
291 path: new_path,
292 },
293 })
294 } else {
295 None
296 }
297 }
298 }
299 }
300
301 pub fn walk(&self) -> impl Iterator<Item = File> {
304 let mut queue: VecDeque<DirEntry> = VecDeque::from_iter(self.entries().into_iter());
305 std::iter::from_fn(move || {
306 while let Some(entry) = queue.pop_front() {
307 match entry.inner {
308 InnerEntry::File(file) => return Some(File { inner: file }),
309 InnerEntry::Dir(dir) => queue.extend(Dir { inner: dir }.entries()),
310 }
311 }
312 None
313 })
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Hash)]
318pub struct File {
321 inner: InnerFile,
322}
323
324impl File {
325 pub fn file_name(&self) -> Option<&str> {
327 self.path().file_name().and_then(|name| name.to_str())
328 }
329
330 pub fn extension(&self) -> Option<&str> {
332 self.path().extension().and_then(|ext| ext.to_str())
333 }
334
335 pub fn absolute_path(&self) -> &std::path::Path {
337 self.inner.absolute_path()
338 }
339
340 pub fn is_embedded(&self) -> bool {
342 self.inner.is_embedded()
343 }
344
345 pub fn path(&self) -> &std::path::Path {
347 self.inner.path()
348 }
349
350 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
352 match &self.inner {
353 InnerFile::Embed(file) => Ok(file.contents().to_vec()),
354 InnerFile::Path { path, .. } => std::fs::read(path),
355 }
356 }
357
358 pub fn read_str(&self) -> std::io::Result<String> {
361 match &self.inner {
362 InnerFile::Embed(file) => std::str::from_utf8(file.contents())
363 .map(str::to_owned)
364 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
365 InnerFile::Path { path, .. } => std::fs::read_to_string(path),
366 }
367 }
368
369 pub fn metadata(&self) -> std::io::Result<FileMetaData> {
371 match &self.inner {
372 InnerFile::Embed(file) => {
373 if let Some(metadata) = file.metadata() {
374 Ok(FileMetaData {
375 modified: metadata.modified(),
376 size: file.contents().len() as u64,
377 })
378 } else {
379 Err(std::io::Error::new(
380 std::io::ErrorKind::Other,
381 "Failed to get embedded file metadata",
382 ))
383 }
384 }
385 InnerFile::Path { path, .. } => {
386 let metadata = std::fs::metadata(path)?;
387 Ok(FileMetaData {
388 modified: metadata.modified()?,
389 size: metadata.len(),
390 })
391 }
392 }
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Eq, Hash)]
397pub struct DirEntry {
399 inner: InnerEntry,
400}
401
402impl DirEntry {
403 pub fn from_file(file: File) -> Self {
405 Self {
406 inner: InnerEntry::File(file.inner),
407 }
408 }
409
410 pub fn from_dir(dir: Dir) -> Self {
412 Self {
413 inner: InnerEntry::Dir(dir.inner),
414 }
415 }
416
417 pub fn path(&self) -> &std::path::Path {
419 match &self.inner {
420 InnerEntry::File(file) => file.path(),
421 InnerEntry::Dir(dir) => dir.path(),
422 }
423 }
424
425 pub fn absolute_path(&self) -> &std::path::Path {
427 match &self.inner {
428 InnerEntry::File(file) => file.absolute_path(),
429 InnerEntry::Dir(dir) => dir.absolute_path(),
430 }
431 }
432
433 pub fn is_embedded(&self) -> bool {
435 matches!(&self.inner, InnerEntry::File(InnerFile::Embed(_)))
436 || matches!(&self.inner, InnerEntry::Dir(InnerDir::Embed(..)))
437 }
438
439 pub const fn is_file(&self) -> bool {
441 matches!(&self.inner, InnerEntry::File(_))
442 }
443
444 pub const fn is_dir(&self) -> bool {
446 matches!(&self.inner, InnerEntry::Dir(_))
447 }
448
449 pub fn into_file(self) -> Option<File> {
451 if let InnerEntry::File(file) = self.inner {
452 Some(File { inner: file })
453 } else {
454 None
455 }
456 }
457
458 pub fn into_dir(self) -> Option<Dir> {
460 if let InnerEntry::Dir(dir) = self.inner {
461 Some(Dir { inner: dir })
462 } else {
463 None
464 }
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Eq, Hash)]
469pub struct DirSet {
472 pub dirs: Vec<Dir>,
474}
475
476impl DirSet {
477 pub fn new(dirs: Vec<Dir>) -> Self {
480 Self { dirs }
481 }
482
483 #[doc(hidden)]
486 pub fn entries(&self) -> Vec<DirEntry> {
487 self.dirs.iter().flat_map(|dir| dir.entries()).collect()
488 }
489
490 pub fn get_file(&self, name: &str) -> Option<File> {
493 for dir in self.dirs.iter().rev() {
494 if let Some(file) = dir.get_file(name) {
495 return Some(file);
496 }
497 }
498 None
499 }
500
501 pub fn get_dir(&self, name: &str) -> Option<Dir> {
502 for dir in self.dirs.iter().rev() {
503 if let Some(subdir) = dir.get_dir(name) {
504 return Some(subdir);
505 }
506 }
507 None
508 }
509
510 pub fn walk(&self) -> impl Iterator<Item = File> {
513 let mut queue: Vec<DirEntry> = Vec::with_capacity(self.dirs.len() * 128); for dir in self.dirs.iter() {
515 queue.push(DirEntry::from_dir(dir.clone()));
516 }
517 std::iter::from_fn(move || {
518 while let Some(entry) = queue.pop() {
519 match entry.inner {
520 InnerEntry::File(file) => return Some(File { inner: file }),
521 InnerEntry::Dir(dir) => {
522 for child in (Dir { inner: dir }).entries().into_iter().rev() {
523 queue.push(child);
524 }
525 }
526 }
527 }
528 None
529 })
530 }
531
532 pub fn walk_override(&self) -> impl Iterator<Item = File> {
535 let mut history = std::collections::HashSet::new();
536 let mut stack: Vec<DirEntry> = Vec::with_capacity(self.dirs.len() * 128); for dir in self.dirs.iter() {
538 stack.push(DirEntry::from_dir(dir.clone()));
539 }
540 std::iter::from_fn(move || {
541 while let Some(entry) = stack.pop() {
542 match entry.inner {
543 InnerEntry::File(file) => {
544 if history.insert(file.path().to_owned()) {
545 return Some(File { inner: file });
546 }
547 }
548 InnerEntry::Dir(dir) => {
549 let children = Dir { inner: dir }.entries();
551 for child in children.into_iter() {
552 stack.push(child);
553 }
554 }
555 }
556 }
557 None
558 })
559 }
560}