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