1use crate::{fail, util};
2use std::{ffi, fmt, path};
3
4#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Debug)]
5pub enum Part {
6 Folder { name: ffi::OsString },
7 File { name: ffi::OsString },
8 Range { begin: usize, size: usize },
9}
10
11#[derive(Clone, PartialOrd, PartialEq, Ord, Eq, Debug)]
12pub struct Path {
13 parts: Vec<Part>,
14}
15
16impl Path {
17 pub fn root() -> Path {
18 Path { parts: Vec::new() }
19 }
20 pub fn folder(path: impl AsRef<path::Path>) -> Path {
21 let mut p = Path::root();
22 for component in path.as_ref().components() {
23 let component = component.as_os_str();
24 if component != "/" {
25 p = p.push_clone(Part::Folder {
26 name: component.into(),
27 });
28 }
29 }
30 p
31 }
32 pub fn file(path: impl AsRef<path::Path>) -> Path {
33 let mut p = Path::folder(path);
34 if let Some(part) = p.parts.pop() {
36 if let Part::Folder { name } = part {
37 p.parts.push(Part::File { name });
38 }
39 }
40 p
41 }
42 pub fn current() -> util::Result<Path> {
43 Ok(Path::folder(std::env::current_dir()?))
44 }
45
46 pub fn include(&self, rhs: &Path) -> bool {
47 let parts_to_check = rhs.parts.len();
48 if self.parts.len() < parts_to_check {
49 return false;
50 }
51 for ix in 0..parts_to_check {
52 if rhs.parts[ix] != self.parts[ix] {
53 return false;
54 }
55 }
56 true
57 }
58 pub fn is_hidden(&self) -> bool {
59 let is_hidden = |name: &ffi::OsString| {
60 if let Some(ch) = name.to_string_lossy().chars().next() {
61 if ch == '.' {
62 return true;
63 }
64 }
65 false
66 };
67 for part in &self.parts {
68 match part {
69 Part::Folder { name } => {
70 if is_hidden(name) {
71 return true;
72 }
73 }
74 Part::File { name } => {
75 if is_hidden(name) {
76 return true;
77 }
78 }
79 _ => {}
80 }
81 }
82 false
83 }
84 pub fn is_empty(&self) -> bool {
85 self.parts.is_empty()
86 }
87 pub fn is_folder(&self) -> bool {
88 for part in &self.parts {
89 match part {
90 Part::Folder { .. } => {}
91 Part::File { .. } => return false,
92 _ => {}
93 }
94 }
95 true
96 }
97 pub fn is_file(&self) -> bool {
98 !self.is_folder()
99 }
100 pub fn extension(&self) -> Option<&ffi::OsStr> {
101 for part in &self.parts {
102 match part {
103 Part::Folder { .. } => {}
104 Part::File { name } => return path::Path::new(name).extension(),
105 _ => {}
106 }
107 }
108 None
109 }
110 pub fn push(&mut self, part: Part) {
111 self.parts.push(part);
112 }
113 pub fn pop(&mut self) -> Option<Part> {
114 self.parts.pop()
115 }
116 pub fn push_clone(&self, part: Part) -> Path {
117 let mut path = self.clone();
118 path.push(part);
119 path
120 }
121 pub fn fs_path(&self) -> util::Result<FsPath> {
122 let mut ret = FsPath::Folder(path::PathBuf::from("/"));
123 for part in &self.parts {
124 match part {
125 Part::Folder { name } => match ret {
126 FsPath::Folder(mut folder) => {
127 folder.push(name);
128 ret = FsPath::Folder(folder)
129 }
130 _ => fail!("Cannot add folder part to non-folder"),
131 },
132 Part::File { name } => match ret {
133 FsPath::Folder(mut folder) => {
134 folder.push(name);
135 ret = FsPath::File(folder)
136 }
137 _ => fail!("Cannot add file part to non-folder"),
138 },
139 Part::Range { .. } => match &ret {
140 FsPath::File(_) => {}
141 _ => fail!("Cannot add range part to non-file"),
142 },
143 }
144 }
145 Ok(ret)
146 }
147 pub fn path_buf(&self) -> std::path::PathBuf {
148 let mut res = std::path::PathBuf::new();
149 res.push("/");
150 for part in &self.parts {
151 match part {
152 Part::Folder { name } => res.push(name),
153 Part::File { name } => res.push(name),
154 _ => {}
155 }
156 }
157 res
158 }
159 pub fn relative_from(&self, base: &Path) -> std::path::PathBuf {
160 let mut start_ix = Some(0);
161 for (ix, part) in base.parts.iter().enumerate() {
162 if ix >= self.parts.len() || &self.parts[ix] != part {
163 start_ix = None;
164 break;
165 }
166 start_ix = Some(ix);
167 }
168 if let Some(start_ix) = start_ix {
169 let mut rel = std::path::PathBuf::new();
170 for ix in start_ix + 1..self.parts.len() {
171 if ix < self.parts.len() {
172 let part = &self.parts[ix];
173 match part {
174 Part::Folder { name } => rel.push(name),
175 Part::File { name } => rel.push(name),
176 Part::Range { .. } => break,
177 }
178 }
179 }
180 rel
181 } else {
182 self.path_buf()
183 }
184 }
185 pub fn exist(&self) -> bool {
186 if let Ok(fs_path) = self.fs_path() {
187 let path;
188 match fs_path {
189 FsPath::File(p) => path = p,
190 FsPath::Folder(p) => path = p,
191 }
192 return std::fs::metadata(path).is_ok();
193 }
194 false
195 }
196 pub fn keep_folder(&mut self) {
197 while let Some(part) = self.parts.last() {
198 match part {
199 Part::Folder { .. } => {
200 break;
201 }
202 _ => {}
203 }
204 }
205 }
206}
207
208impl fmt::Display for Path {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 write!(f, "/")?;
211 for part in &self.parts {
212 match part {
213 Part::Folder { name } => write!(f, "{}/", name.to_string_lossy())?,
214 Part::File { name } => write!(f, "{} ", name.to_string_lossy())?,
215 Part::Range { begin, size } => write!(f, "[{}, {}]", begin, size)?,
216 }
217 }
218 Ok(())
219 }
220}
221
222#[derive(Debug)]
223pub enum FsPath {
224 Folder(path::PathBuf),
225 File(path::PathBuf),
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_path_buf() {
234 let mut path = Path::root();
235 assert_eq!(path.path_buf(), std::path::PathBuf::from("/"));
236 path.push(Part::Folder {
237 name: "base".into(),
238 });
239 assert_eq!(path.path_buf(), std::path::PathBuf::from("/base"));
240 }
241
242 #[test]
243 fn test_relative() {
244 let base = Path::folder("/base");
245 let file = Path::file("/base/rel/name.txt");
246 assert_eq!(
247 file.relative_from(&base),
248 std::path::PathBuf::from("rel/name.txt")
249 );
250 }
251}