1use std::{
2 cell::Cell,
3 collections::hash_map::{Entry, HashMap},
4 fs, io,
5 path::{Path, PathBuf},
6};
7
8use super::Source;
9
10pub struct Fs {
12 inc_dirs: Vec<PathBuf>,
13 cache: Cell<Option<HashMap<PathBuf, String>>>,
14}
15
16impl Default for Fs {
17 fn default() -> Self {
18 Fs {
19 inc_dirs: Vec::new(),
20 cache: Cell::new(Some(HashMap::new())),
21 }
22 }
23}
24
25impl Fs {
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn builder() -> FsBuilder {
31 FsBuilder {
32 source: Self::new(),
33 }
34 }
35
36 pub fn include_dir(&mut self, dir: &Path) -> io::Result<()> {
37 self.check_dir(dir)?;
38 self.inc_dirs.push(dir.to_path_buf());
39 Ok(())
40 }
41
42 fn check_dir(&self, dir: &Path) -> io::Result<()> {
43 let meta = fs::metadata(dir)?;
44 if !meta.is_dir() {
45 Err(io::Error::new(
46 io::ErrorKind::InvalidData,
47 format!("{:?} is not a directory", dir),
48 ))
49 } else {
50 Ok(())
51 }
52 }
53
54 fn check_file(&self, path: &Path) -> io::Result<()> {
55 let map = self.cache.take().unwrap();
56 let contains = map.contains_key(path);
57 self.cache.set(Some(map));
58 if contains {
59 return Ok(());
60 }
61
62 match fs::metadata(path) {
63 Ok(meta) => {
64 if !meta.is_file() {
65 Err(io::Error::new(
66 io::ErrorKind::InvalidData,
67 format!("{:?} is not a file", path),
68 ))
69 } else {
70 Ok(())
71 }
72 }
73 Err(e) => Err(e),
74 }
75 }
76
77 fn find_in_dir(&self, dir: &Path, name: &Path) -> io::Result<Option<PathBuf>> {
78 let path = dir.join(name);
79 match self.check_file(&path) {
80 Ok(()) => Ok(Some(path)),
81 Err(e) => match e.kind() {
82 io::ErrorKind::NotFound => Ok(None),
83 _ => Err(e),
84 },
85 }
86 }
87
88 fn find_file(&self, dir: Option<&Path>, name: &Path) -> io::Result<PathBuf> {
89 if name.is_absolute() {
90 return Ok(name.to_path_buf());
91 }
92
93 if let Some(dir) = dir {
94 if let Some(path) = self.find_in_dir(dir, name)? {
95 return Ok(path);
96 }
97 }
98
99 for dir in self.inc_dirs.iter() {
100 if let Some(path) = self.find_in_dir(dir, name)? {
101 return Ok(path);
102 }
103 }
104
105 Err(io::Error::new(
106 io::ErrorKind::NotFound,
107 name.to_string_lossy(),
108 ))
109 }
110}
111
112pub struct FsBuilder {
113 source: Fs,
114}
115
116impl FsBuilder {
117 pub fn include_dir<P: AsRef<Path>>(mut self, dir: P) -> io::Result<Self> {
118 self.source.include_dir(dir.as_ref()).map(|()| self)
119 }
120
121 pub fn build(self) -> Fs {
122 self.source
123 }
124}
125
126impl Source for Fs {
127 fn read(&self, path: &Path, dir: Option<&Path>) -> io::Result<(PathBuf, String)> {
128 self.find_file(dir, path).and_then(|path| {
129 let mut map = self.cache.take().unwrap();
130
131 let res = match map.entry(path.clone()) {
132 Entry::Occupied(v) => Ok(v.get().clone()),
133 Entry::Vacant(v) => fs::read_to_string(&path).map(|data| {
134 v.insert(data.clone());
135 data
136 }),
137 }
138 .map(|data| (path, data));
139
140 self.cache.set(Some(map));
141 res
142 })
143 }
144}