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