1use std::{
2 ffi::OsStr,
3 path::{Component, Path, PathBuf},
4};
5
6use bstr::{BStr, BString, ByteSlice};
7use gix_error::{ErrorExt, ExnMessageResult, validation};
8
9use crate::Stack;
10
11pub trait ToNormalPathComponents {
13 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>>;
15}
16
17impl ToNormalPathComponents for &Path {
18 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>> {
19 self.components().map(|c| component_to_os_str(c, self.display()))
20 }
21}
22
23impl ToNormalPathComponents for PathBuf {
24 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>> {
25 self.components().map(|c| component_to_os_str(c, self.display()))
26 }
27}
28
29fn component_to_os_str(
30 component: Component<'_>,
31 path_with_component: impl std::fmt::Display,
32) -> ExnMessageResult<&OsStr> {
33 match component {
34 Component::Normal(os_str) => Ok(os_str),
35 _ => Err(validation(format!(
36 "Input path \"{path_with_component}\" contains relative or absolute components"
37 ))
38 .raise()),
39 }
40}
41
42impl ToNormalPathComponents for &BStr {
43 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>> {
44 self.split(|b| *b == b'/')
45 .filter_map(|c| bytes_component_to_os_str(c, self))
46 }
47}
48
49impl ToNormalPathComponents for &str {
50 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>> {
51 self.split('/')
52 .filter_map(|c| bytes_component_to_os_str(c.as_bytes(), (*self).into()))
53 }
54}
55
56impl ToNormalPathComponents for &BString {
57 fn to_normal_path_components(&self) -> impl Iterator<Item = ExnMessageResult<&OsStr>> {
58 self.split(|b| *b == b'/')
59 .filter_map(|c| bytes_component_to_os_str(c, self.as_bstr()))
60 }
61}
62
63fn bytes_component_to_os_str<'a>(component: &'a [u8], path: &BStr) -> Option<ExnMessageResult<&'a OsStr>> {
64 if component.is_empty() {
65 return None;
66 }
67 let component = match gix_path::try_from_byte_slice(component.as_bstr()) {
68 Ok(c) => c,
69 Err(err) => return Some(Err(err)),
70 };
71 let component = component.components().next()?;
72 Some(component_to_os_str(component, path))
73}
74
75impl Stack {
77 pub fn root(&self) -> &Path {
79 &self.root
80 }
81
82 pub fn current(&self) -> &Path {
84 &self.current
85 }
86
87 pub fn current_relative(&self) -> &Path {
89 &self.current_relative
90 }
91}
92
93pub trait Delegate {
95 fn push_directory(&mut self, stack: &Stack) -> std::io::Result<()>;
101
102 fn push(&mut self, is_last_component: bool, stack: &Stack) -> std::io::Result<()>;
107
108 fn pop_directory(&mut self);
113}
114
115impl Stack {
116 pub fn new(root: PathBuf) -> Self {
119 Stack {
120 current: root.clone(),
121 current_relative: PathBuf::with_capacity(128),
122 valid_components: 0,
123 root,
124 current_is_directory: true,
125 }
126 }
127
128 pub fn make_relative_path_current(
139 &mut self,
140 relative: impl ToNormalPathComponents,
141 delegate: &mut dyn Delegate,
142 ) -> std::io::Result<()> {
143 let mut components = relative.to_normal_path_components().peekable();
144 if self.valid_components != 0 && components.peek().is_none() {
145 return Err(std::io::Error::other("empty inputs are not allowed"));
146 }
147 if self.valid_components == 0 {
148 delegate.push_directory(self)?;
149 }
150
151 let mut existing_components = self.current_relative.components();
152 let mut matching_components = 0;
153 while let (Some(existing_comp), Some(new_comp)) = (existing_components.next(), components.peek()) {
154 match new_comp {
155 Ok(new_comp) => {
156 if existing_comp.as_os_str() == *new_comp {
157 components.next();
158 matching_components += 1;
159 } else {
160 break;
161 }
162 }
163 Err(_) => {
164 let err = components.next().expect("just peeked").expect_err("peeked an error");
165 return Err(std::io::Error::other(err.into_error()));
166 }
167 }
168 }
169
170 for _ in 0..self.valid_components - matching_components {
171 self.current.pop();
172 self.current_relative.pop();
173 if self.current_is_directory {
174 delegate.pop_directory();
175 }
176 self.current_is_directory = true;
177 }
178 self.valid_components = matching_components;
179
180 if matching_components != 0 && components.peek().is_none() {
181 if self.current_is_directory {
183 delegate.pop_directory();
184 self.current_is_directory = false;
185 }
186 return delegate.push(true, self);
187 }
188
189 if !self.current_is_directory && components.peek().is_some() {
190 delegate.push(false, self)?;
191 self.current_is_directory = true;
193 if let Err(err) = delegate.push_directory(self) {
194 self.current_is_directory = false;
195 return Err(err);
196 }
197 }
198
199 while let Some(comp) = components.next() {
200 let comp = comp.map_err(|err| std::io::Error::other(err.into_error()))?;
201 let is_last_component = components.peek().is_none();
202 let parent_is_directory = self.current_is_directory;
203 self.current_is_directory = !is_last_component;
204 self.current.push(comp);
205 self.current_relative.push(comp);
206 self.valid_components += 1;
207 let res = delegate.push(is_last_component, self);
208 if let Err(err) = res {
209 self.current.pop();
210 self.current_relative.pop();
211 self.valid_components -= 1;
212 self.current_is_directory = parent_is_directory;
213 return Err(err);
214 }
215 if self.current_is_directory
216 && let Err(err) = delegate.push_directory(self)
217 {
218 self.current.pop();
219 self.current_relative.pop();
220 self.valid_components -= 1;
221 self.current_is_directory = parent_is_directory;
222 return Err(err);
223 }
224 }
225 Ok(())
226 }
227}