dir_structure/vfs/
include_dir_vfs.rs1use core::fmt;
4use core::slice;
5use std::error;
6use std::io;
7use std::path::Component;
8use std::path::Path;
9use std::path::PathBuf;
10use std::pin::Pin;
11use std::result::Result as StdResult;
12
13use include_dir::Dir;
14use include_dir::DirEntry;
15#[cfg(doc)]
16use include_dir::include_dir as _include_dir;
17
18use crate::error::Error;
19use crate::error::Result;
20use crate::error::VfsResult;
21use crate::traits::vfs::DirEntryInfo;
22use crate::traits::vfs::DirEntryKind;
23use crate::traits::vfs::DirWalker;
24use crate::traits::vfs::PathType;
25use crate::traits::vfs::Vfs;
26use crate::traits::vfs::VfsCore;
27
28pub struct IncludeDirVfs {
30 dir: Dir<'static>,
31}
32
33impl IncludeDirVfs {
34 pub fn new(dir: Dir<'static>) -> Self {
36 Self { dir }
37 }
38}
39
40#[macro_export]
44macro_rules! include_dir_vfs {
45 ($path:expr) => {{
46 $crate::vfs::include_dir_vfs::IncludeDirVfs::new(
47 $crate::vfs::include_dir_vfs::include_dir_patched!($path),
48 )
49 }};
50}
51
52pub use dir_structure_macros::include_dir_patched;
53
54#[derive(Debug)]
55struct NormalizeError;
56
57impl fmt::Display for NormalizeError {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(f, "path normalization error")
60 }
61}
62
63impl error::Error for NormalizeError {}
64
65fn normalize_lexically(path: &Path) -> StdResult<PathBuf, NormalizeError> {
69 let mut lexical = PathBuf::new();
70 let mut iter = path.components().peekable();
71
72 let root = match iter.peek() {
76 Some(Component::ParentDir) => return Err(NormalizeError),
77 Some(p @ Component::RootDir) => {
78 lexical.push(p);
79 iter.next();
80 lexical.as_os_str().len()
81 }
82 Some(Component::CurDir) => {
83 iter.next();
84 0
85 }
86 Some(Component::Prefix(prefix)) => {
87 lexical.push(prefix.as_os_str());
88 iter.next();
89 if let Some(p @ Component::RootDir) = iter.peek() {
90 lexical.push(p);
91 iter.next();
92 }
93 lexical.as_os_str().len()
94 }
95 None => return Ok(PathBuf::new()),
96 Some(Component::Normal(_)) => 0,
97 };
98
99 for component in iter {
100 match component {
101 Component::RootDir => unreachable!(),
102 Component::Prefix(_) => return Err(NormalizeError),
103 Component::CurDir => continue,
104 Component::ParentDir => {
105 if lexical.as_os_str().len() == root {
107 return Err(NormalizeError);
108 } else {
109 lexical.pop();
110 }
111 }
112 Component::Normal(path) => lexical.push(path),
113 }
114 }
115 Ok(lexical)
116}
117
118fn norm(path: &Path) -> Result<PathBuf, PathBuf> {
119 normalize_lexically(path).map_err(|e| {
120 Error::Io(
121 path.to_path_buf(),
122 io::Error::new(io::ErrorKind::InvalidInput, e).into(),
123 )
124 })
125}
126
127fn get_dir_or_root<'a>(root: &'a Dir<'static>, path: &Path) -> Result<&'a Dir<'static>, PathBuf> {
128 if path.as_os_str().is_empty() || path == Path::new(".") {
129 return Ok(root);
130 }
131
132 let p = norm(path)?;
133 root.get_dir(&p)
134 .ok_or(Error::Io(p, io::ErrorKind::NotFound.into()))
135}
136
137impl VfsCore for IncludeDirVfs {
138 type Path = Path;
139}
140
141impl<'vfs> Vfs<'vfs> for IncludeDirVfs {
142 type DirWalk<'a>
143 = IncludeDirWalker<'a>
144 where
145 'vfs: 'a,
146 Self: 'a;
147 type RFile = io::Cursor<&'static [u8]>;
148
149 fn open_read(self: Pin<&Self>, path: &Path) -> VfsResult<Self::RFile, Self> {
150 let p = norm(path)?;
151 if self.dir.get_dir(&p).is_some() {
152 return Err(Error::Io(p, io::ErrorKind::IsADirectory.into()));
153 }
154
155 let file = self
156 .dir
157 .get_file(&p)
158 .ok_or(Error::Io(p.clone(), io::ErrorKind::NotFound.into()))?;
159
160 Ok(io::Cursor::new(file.contents()))
161 }
162
163 fn read(self: Pin<&Self>, path: &Path) -> VfsResult<Vec<u8>, Self> {
164 let p = norm(path)?;
165 self.dir
166 .get_file(&p)
167 .map(|it| it.contents().to_vec())
168 .ok_or(Error::Io(p, io::ErrorKind::NotFound.into()))
169 }
170
171 fn read_string(self: Pin<&Self>, path: &Path) -> VfsResult<String, Self> {
172 let p = norm(path)?;
173 #[derive(Debug)]
174 struct Utf8Error;
175 impl fmt::Display for Utf8Error {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 write!(f, "invalid utf-8: corrupt contents")
178 }
179 }
180
181 impl error::Error for Utf8Error {
182 fn description(&self) -> &str {
183 "invalid utf-8: corrupt contents"
184 }
185 }
186
187 if self.dir.get_dir(&p).is_some() {
188 return Err(Error::Io(p, io::ErrorKind::IsADirectory.into()));
189 }
190
191 let file = self
192 .dir
193 .get_file(&p)
194 .ok_or(Error::Io(p.clone(), io::ErrorKind::NotFound.into()))?;
195
196 file.contents_utf8()
197 .map(|s| s.to_string())
198 .ok_or(Error::Parse(p, Box::new(Utf8Error)))
199 }
200
201 fn exists(self: Pin<&Self>, path: &Path) -> VfsResult<bool, Self> {
202 let path = norm(path)?;
203
204 Ok(get_dir_or_root(&self.dir, &path)
205 .map_or_else(|_| self.dir.get_file(&path).is_some(), |_| true))
206 }
207
208 fn is_dir(self: Pin<&Self>, path: &Self::Path) -> VfsResult<bool, Self> {
209 let path = norm(path)?;
210 Ok(self.dir.get_dir(&path).is_some())
211 }
212
213 fn walk_dir<'b>(self: Pin<&'b Self>, path: &Path) -> VfsResult<Self::DirWalk<'b>, Self>
214 where
215 'vfs: 'b,
216 {
217 let path = norm(path)?;
218 Ok(IncludeDirWalker(
219 get_dir_or_root(&self.dir, &path)?.entries().iter(),
220 ))
221 }
222}
223
224pub struct IncludeDirWalker<'a>(slice::Iter<'a, DirEntry<'static>>);
226
227impl<'a> DirWalker<'a> for IncludeDirWalker<'a> {
228 type P = Path;
229
230 fn next(&mut self) -> Option<Result<DirEntryInfo<Self::P>, <Self::P as PathType>::OwnedPath>> {
231 let next_entry = self.0.next()?;
232 match next_entry {
233 DirEntry::Dir(dir) => Some(Ok(DirEntryInfo {
234 kind: DirEntryKind::Directory,
235 name: dir.path().file_name().unwrap().to_os_string(),
236 path: dir.path().to_path_buf(),
237 })),
238 DirEntry::File(file) => Some(Ok(DirEntryInfo {
239 kind: DirEntryKind::File,
240 name: file.path().file_name().unwrap().to_os_string(),
241 path: file.path().to_path_buf(),
242 })),
243 }
244 }
245}