joat_path/
absolute_path.rs1use crate::path_clean::clean;
23use std::io::{Error, ErrorKind, Result};
24use std::path::{Path, PathBuf};
25
26pub fn absolute_path<B: AsRef<Path>, P: AsRef<Path>>(base_dir: B, path: P) -> Result<PathBuf> {
35 fn normalize(path: &Path) -> Result<PathBuf> {
36 path.to_str()
37 .ok_or_else(|| {
38 Error::new(
39 ErrorKind::Other,
40 format!("Path {} cannot be converted to string", path.display()),
41 )
42 })
43 .map(clean)
44 .map(PathBuf::from)
45 }
46
47 if !base_dir.as_ref().is_absolute() {
48 return Err(Error::new(
49 ErrorKind::InvalidInput,
50 format!(
51 "Base directory {} is not absolute",
52 base_dir.as_ref().display()
53 ),
54 ));
55 }
56
57 normalize(&match path.as_ref().components().count() {
58 0 => base_dir.as_ref().to_path_buf(),
59 _ => base_dir.as_ref().join(path),
60 })
61}
62
63#[cfg(test)]
64mod tests {
65 use asserts::{check_absolute_path, check_absolute_path_fails};
66 use helpers::{abs, rel};
67
68 #[test]
69 fn fails_if_base_dir_not_absolute() {
70 check_absolute_path_fails(abs("aa/bb/cc"), rel(""));
71 }
72
73 #[test]
74 fn path_empty() {
75 check_absolute_path(abs("/aa/bb/cc"), rel(""), "/aa/bb/cc", 3);
76 }
77
78 #[test]
79 fn base_dir_unnormalized_path_empty() {
80 check_absolute_path(abs("/aa/../bb/cc"), rel(""), "/bb/cc", 2);
81 }
82
83 #[test]
84 fn path_single_component_relative() {
85 check_absolute_path(abs("/aa/bb/cc"), rel("dd"), "/aa/bb/cc/dd", 4);
86 }
87
88 #[test]
89 fn path_single_component_absolute() {
90 check_absolute_path(abs("/aa/bb/cc"), abs("/dd"), "/dd", 1);
91 }
92
93 #[test]
94 fn path_multiple_components_relative() {
95 check_absolute_path(abs("/aa/bb/cc"), rel("dd/ee"), "/aa/bb/cc/dd/ee", 5);
96 }
97
98 #[test]
99 fn path_multiple_components_absolute() {
100 check_absolute_path(abs("/aa/bb/cc"), abs("/dd/ee"), "/dd/ee", 2);
101 }
102
103 #[test]
104 fn path_multiple_components_unnormalized() {
105 check_absolute_path(abs("/aa/bb/cc"), rel("dd/../ee"), "/aa/bb/cc/ee", 4);
106 }
107
108 #[test]
109 fn both_unnormalized() {
110 check_absolute_path(abs("/aa/bb/../cc"), rel("dd/../ee"), "/aa/cc/ee", 3);
111 }
112
113 mod asserts {
114 use crate::absolute_path;
115
116 use super::helpers::{abs, TestPath};
117 use super::platform_helpers::{from_test_path, path_component_count, OTHER_SEPARATOR};
118
119 pub fn check_absolute_path(
120 base_dir: TestPath,
121 path: TestPath,
122 expected_path_str: &str,
123 expected_component_count: usize,
124 ) {
125 let p = absolute_path(from_test_path(base_dir), from_test_path(path)).unwrap();
126 assert!(p.is_absolute());
127 assert_eq!(p, from_test_path(abs(expected_path_str)));
128 assert_eq!(
129 p.to_str().unwrap(),
130 from_test_path(abs(expected_path_str)).to_str().unwrap()
131 );
132 assert_eq!(path_component_count(&p).unwrap(), expected_component_count);
133 assert!(!p.to_str().unwrap().contains(OTHER_SEPARATOR));
134 }
135
136 pub fn check_absolute_path_fails(p0: TestPath, p1: TestPath) {
137 assert!(absolute_path(from_test_path(p0), from_test_path(p1)).is_err());
138 }
139 }
140
141 mod helpers {
142 use self::TestPath::*;
143
144 pub enum TestPath {
145 Abs(String),
146 Rel(String),
147 }
148
149 pub fn abs(s: &str) -> TestPath {
150 Abs(String::from(s))
151 }
152
153 pub fn rel(s: &str) -> TestPath {
154 Rel(String::from(s))
155 }
156 }
157
158 #[cfg(target_os = "windows")]
159 mod platform_helpers {
160 use std::path::Component::*;
161 use std::path::Prefix::*;
162 use std::path::{Path, PathBuf};
163
164 use super::helpers::TestPath::{self, *};
165
166 pub const OTHER_SEPARATOR: char = '/';
167
168 pub fn from_test_path(test_path: TestPath) -> PathBuf {
169 let raw = match test_path {
170 Abs(s) => format!(
171 "Z:{}",
172 s.replace('/', &std::path::MAIN_SEPARATOR.to_string())
173 ),
174 Rel(s) => s.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
175 };
176 PathBuf::from(raw)
177 }
178
179 pub fn path_component_count<P: AsRef<Path>>(path: P) -> Option<usize> {
180 let mut iter = path.as_ref().components();
181
182 match iter.next() {
183 Some(Prefix(prefix_component)) => match prefix_component.kind() {
184 Disk(90) => {}
185 _ => return None,
186 },
187 _ => return None,
188 };
189
190 match iter.next() {
191 Some(RootDir) => {}
192 _ => return None,
193 };
194
195 let mut n = 0;
196 loop {
197 match iter.next() {
198 Some(Normal(_)) => n += 1,
199 Some(_) => return None,
200 None => return Some(n),
201 }
202 }
203 }
204 }
205
206 #[cfg(not(target_os = "windows"))]
207 mod platform_helpers {
208 use std::path::Component::*;
209 use std::path::{Path, PathBuf};
210
211 use super::helpers::TestPath::{self, *};
212
213 pub const OTHER_SEPARATOR: char = '\\';
214
215 pub fn from_test_path(test_path: TestPath) -> PathBuf {
216 let raw = match test_path {
217 Abs(s) | Rel(s) => s,
218 };
219 PathBuf::from(raw)
220 }
221
222 pub fn path_component_count<P: AsRef<Path>>(path: P) -> Option<usize> {
223 let mut iter = path.as_ref().components();
224
225 match iter.next() {
226 Some(RootDir) => {}
227 _ => return None,
228 };
229
230 let mut n = 0;
231 loop {
232 match iter.next() {
233 Some(Normal(_)) => n += 1,
234 Some(_) => return None,
235 None => return Some(n),
236 }
237 }
238 }
239 }
240}