1use std::{
2 borrow::Cow,
3 ffi::OsStr,
4 os::unix::ffi::OsStrExt,
5 path::{Path, PathBuf},
6 sync::Arc,
7};
8
9use anyhow::{Result, bail};
10
11#[repr(transparent)]
12#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct RelPath([u8]);
14
15impl RelPath {
16 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
17 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
18 }
19
20 pub fn components(&self) -> RelPathComponents {
21 RelPathComponents(&self.0)
22 }
23
24 pub fn file_name(&self) -> Option<&[u8]> {
25 self.components().next_back()
26 }
27
28 pub fn parent(&self) -> Option<&Self> {
29 let mut components = self.components();
30 components.next_back()?;
31 Some(Self::new(components.0))
32 }
33
34 pub fn starts_with(&self, other: &Self) -> bool {
35 let mut components = self.components();
36 other.components().all(|other_component| {
37 components
38 .next()
39 .map_or(false, |component| component == other_component)
40 })
41 }
42
43 pub fn strip_prefix(&self, other: &Self) -> Result<&Self, ()> {
44 let mut components = self.components();
45 other
46 .components()
47 .all(|other_component| {
48 components
49 .next()
50 .map_or(false, |component| component == other_component)
51 })
52 .then(|| Self::new(components.0))
53 .ok_or_else(|| ())
54 }
55
56 pub fn from_path(relative_path: &Path) -> Result<&Self> {
57 use std::path::Component;
58 match relative_path.components().next() {
59 Some(Component::Prefix(_)) => bail!(
60 "path `{}` should be relative, not a windows prefix",
61 relative_path.to_string_lossy()
62 ),
63 Some(Component::RootDir) => {
64 bail!(
65 "path `{}` should be relative",
66 relative_path.to_string_lossy()
67 )
68 }
69 Some(Component::CurDir) => {
70 bail!(
71 "path `{}` should not start with `.`",
72 relative_path.to_string_lossy()
73 )
74 }
75 Some(Component::ParentDir) => {
76 bail!(
77 "path `{}` should not start with `..`",
78 relative_path.to_string_lossy()
79 )
80 }
81 None => bail!("relative path should not be empty"),
82 _ => Ok(Self::new(relative_path.as_os_str().as_bytes())),
83 }
84 }
85
86 pub fn append_to_abs_path(&self, abs_path: &Path) -> PathBuf {
87 // TODO: implement this differently
88 let mut result = abs_path.to_path_buf();
89 for component in self.components() {
90 result.push(String::from_utf8_lossy(component).as_ref());
91 }
92 result
93 }
94
95 pub fn to_proto(&self) -> String {
96 String::from_utf8_lossy(&self.0).to_string()
97 }
98
99 pub fn as_bytes(&self) -> &[u8] {
100 &self.0
101 }
102
103 pub fn as_os_str(&self) -> Cow<'_, OsStr> {
104 #[cfg(target_os = "windows")]
105 {
106 use std::ffi::OsString;
107 let path = String::from_utf8_lossy(&self.0);
108 match path {
109 Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
110 Cow::Owned(s) => Cow::Owned(OsString::from(s)),
111 }
112 }
113 #[cfg(not(target_os = "windows"))]
114 {
115 use std::os::unix::ffi::OsStrExt;
116
117 Cow::Borrowed(OsStr::from_bytes(&self.0))
118 }
119 }
120}
121
122impl From<&RelPath> for Arc<RelPath> {
123 fn from(rel_path: &RelPath) -> Self {
124 let bytes: Arc<[u8]> = Arc::from(&rel_path.0);
125 unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
126 }
127}
128
129impl AsRef<RelPath> for &str {
130 fn as_ref(&self) -> &RelPath {
131 RelPath::new(self)
132 }
133}
134
135impl std::fmt::Debug for RelPath {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 if let Ok(str) = std::str::from_utf8(&self.0) {
138 write!(f, "RelPath({})", str)
139 } else {
140 write!(f, "RelPath({:?})", &self.0)
141 }
142 }
143}
144
145pub struct RelPathComponents<'a>(&'a [u8]);
146
147const SEPARATOR: u8 = b'/';
148
149impl<'a> Iterator for RelPathComponents<'a> {
150 type Item = &'a [u8];
151
152 fn next(&mut self) -> Option<Self::Item> {
153 if let Some(sep_ix) = self.0.iter().position(|&byte| byte == SEPARATOR) {
154 let (head, tail) = self.0.split_at(sep_ix);
155 self.0 = &tail[1..];
156 Some(head)
157 } else if self.0.is_empty() {
158 None
159 } else {
160 let result = self.0;
161 self.0 = &[];
162 Some(result)
163 }
164 }
165}
166
167impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
168 fn next_back(&mut self) -> Option<Self::Item> {
169 if let Some(sep_ix) = self.0.iter().rposition(|&byte| byte == SEPARATOR) {
170 let (head, tail) = self.0.split_at(sep_ix);
171 self.0 = head;
172 Some(&tail[1..])
173 } else if self.0.is_empty() {
174 None
175 } else {
176 let result = self.0;
177 self.0 = &[];
178 Some(result)
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_rel_path_components() {
189 let path = RelPath::new("foo/bar/baz");
190 let mut components = path.components();
191 assert_eq!(components.next(), Some("foo".as_bytes()));
192 assert_eq!(components.next(), Some("bar".as_bytes()));
193 assert_eq!(components.next(), Some("baz".as_bytes()));
194 assert_eq!(components.next(), None);
195 }
196
197 #[test]
198 fn test_rel_path_parent() {
199 assert_eq!(
200 RelPath::new("foo/bar/baz").parent().unwrap(),
201 RelPath::new("foo/bar")
202 );
203 assert_eq!(RelPath::new("foo").parent().unwrap(), RelPath::new(""));
204 assert_eq!(RelPath::new("").parent(), None);
205 }
206}