path_abs/abs.rs
1/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com
2 *
3 * Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 * http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 * http://opensource.org/licenses/MIT>, at your option. This file may not be
6 * copied, modified, or distributed except according to those terms.
7 */
8//! The absolute path type, the root type for all `Path*` types in this module.
9use std::env;
10use std::ffi;
11use std::fmt;
12use std::io;
13use std::path::{Component, PrefixComponent};
14use std_prelude::*;
15
16use super::{Error, PathMut, PathOps, Result};
17
18/// Converts any PrefixComponent into verbatim ("extended-length") form.
19fn make_verbatim_prefix(prefix: &PrefixComponent<'_>) -> Result<PathBuf> {
20 let path_prefix = Path::new(prefix.as_os_str());
21
22 if prefix.kind().is_verbatim() {
23 // This prefix already uses the extended-length
24 // syntax, so we can use it as-is.
25 Ok(path_prefix.to_path_buf())
26 } else {
27 // This prefix needs canonicalization.
28 let res = path_prefix
29 .canonicalize()
30 .map_err(|e| Error::new(e, "canonicalizing", path_prefix.to_path_buf().into()))?;
31 Ok(res)
32 }
33}
34
35/// Pops the last component from path, returning an error for a root path.
36fn pop_or_error(path: &mut PathBuf) -> ::std::result::Result<(), io::Error> {
37 if path.pop() {
38 Ok(())
39 } else {
40 Err(io::Error::new(io::ErrorKind::NotFound, ".. consumed root"))
41 }
42}
43
44#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
45/// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist.
46///
47/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
48pub struct PathAbs(pub(crate) Arc<PathBuf>);
49
50impl PathAbs {
51 /// Construct an absolute path from an arbitrary (absolute or relative) one.
52 ///
53 /// This is different from [`canonicalize`] in that it _preserves_ symlinks
54 /// and the destination may or may not exist.
55 ///
56 /// This function will:
57 /// - Resolve relative paths against the current working directory.
58 /// - Strip any `.` components (`/a/./c` -> `/a/c`)
59 /// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will
60 /// _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use
61 /// `canonicalize()` instead.
62 ///
63 /// > On windows, this will sometimes call `canonicalize()` on the first component to guarantee
64 /// > it is the correct canonicalized prefix. For paths starting with root it also has to get
65 /// > the [`current_dir`]
66 ///
67 /// > On linux, the only syscall this will make is to get the [`current_dir`] for relative
68 /// > paths.
69 ///
70 /// [`canonicalize`]: struct.PathAbs.html#method.canonicalize
71 /// [`current_dir`]: fn.current_dir.html
72 ///
73 /// # Examples
74 ///
75 /// ```rust
76 /// use path_abs::{PathAbs, PathInfo};
77 ///
78 /// # fn try_main() -> ::std::io::Result<()> {
79 /// let lib = PathAbs::new("src/lib.rs")?;
80 ///
81 /// assert_eq!(lib.is_absolute(), true);
82 /// # Ok(()) } fn main() { try_main().unwrap() }
83 /// ```
84 pub fn new<P: AsRef<Path>>(path: P) -> Result<PathAbs> {
85 let path = Arc::new(path.as_ref().to_path_buf());
86 let mut res = PathBuf::new();
87
88 fn maybe_init_res(res: &mut PathBuf, resolvee: Arc<PathBuf>) -> Result<()> {
89 if !res.as_os_str().is_empty() {
90 // res has already been initialized, let's leave it alone.
91 return Ok(());
92 }
93
94 // res has not been initialized, let's initialize it to the
95 // canonicalized current directory.
96 let cwd = env::current_dir().map_err(|e| {
97 Error::new(e, "getting current_dir while resolving absolute", resolvee)
98 })?;
99 *res = cwd
100 .canonicalize()
101 .map_err(|e| Error::new(e, "canonicalizing", cwd.into()))?;
102
103 Ok(())
104 };
105
106 for each in path.components() {
107 match each {
108 Component::Prefix(p) => {
109 // We don't care what's already in res, we can entirely
110 // replace it..
111 res = make_verbatim_prefix(&p)?;
112 }
113
114 Component::RootDir => {
115 if cfg!(windows) {
116 // In an ideal world, we would say
117 //
118 // res = std::fs::canonicalize(each)?;
119 //
120 // ...to get a properly canonicalized path.
121 // Unfortunately, Windows cannot canonicalize `\` if
122 // the current directory happens to use extended-length
123 // syntax (like `\\?\C:\Windows`), so we'll have to do
124 // it manually: initialize `res` with the current
125 // working directory (whatever it is), and truncate it
126 // to its prefix by pushing `\`.
127 maybe_init_res(&mut res, path.clone())?;
128 res.push(each);
129 } else {
130 // On other platforms, a root path component is always
131 // absolute so we can replace whatever's in res.
132 res = Path::new(&each).to_path_buf();
133 }
134 }
135
136 // This does nothing and can be ignored.
137 Component::CurDir => (),
138
139 Component::ParentDir => {
140 // A parent component is always relative to some existing
141 // path.
142 maybe_init_res(&mut res, path.clone())?;
143 pop_or_error(&mut res)
144 .map_err(|e| Error::new(e, "resolving absolute", path.clone()))?;
145 }
146
147 Component::Normal(c) => {
148 // A normal component is always relative to some existing
149 // path.
150 maybe_init_res(&mut res, path.clone())?;
151 res.push(c);
152 }
153 }
154 }
155
156 Ok(PathAbs(Arc::new(res)))
157 }
158
159 /// Create a PathAbs unchecked.
160 ///
161 /// This is mostly used for constructing during tests, or if the path was previously validated.
162 /// This is effectively the same as a `Arc<PathBuf>`.
163 ///
164 /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause
165 /// > panics in some methods if the path was not properly validated.
166 pub fn new_unchecked<P: Into<Arc<PathBuf>>>(path: P) -> PathAbs {
167 PathAbs(path.into())
168 }
169
170 /// Return a reference to a basic `std::path::Path`
171 pub fn as_path(&self) -> &Path {
172 self.as_ref()
173 }
174}
175
176impl fmt::Debug for PathAbs {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 self.0.fmt(f)
179 }
180}
181
182impl PathMut for PathAbs {
183 fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
184 self.0.append(path)
185 }
186 fn pop_up(&mut self) -> Result<()> {
187 self.0.pop_up()
188 }
189 fn truncate_to_root(&mut self) {
190 self.0.truncate_to_root()
191 }
192 fn set_file_name<S: AsRef<ffi::OsStr>>(&mut self, file_name: S) {
193 self.0.set_file_name(file_name)
194 }
195 fn set_extension<S: AsRef<ffi::OsStr>>(&mut self, extension: S) -> bool {
196 self.0.set_extension(extension)
197 }
198}
199
200impl PathOps for PathAbs {
201 type Output = PathAbs;
202
203 fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
204 Ok(PathAbs(self.0.concat(path)?))
205 }
206
207 fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output {
208 let buf = Path::join(self.as_path(), path);
209 Self::Output::new_unchecked(buf)
210 }
211
212 fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
213 PathAbs(self.0.with_file_name(file_name))
214 }
215
216 fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
217 PathAbs(self.0.with_extension(extension))
218 }
219}
220
221impl AsRef<ffi::OsStr> for PathAbs {
222 fn as_ref(&self) -> &std::ffi::OsStr {
223 self.0.as_ref().as_ref()
224 }
225}
226
227impl AsRef<Path> for PathAbs {
228 fn as_ref(&self) -> &Path {
229 self.0.as_ref()
230 }
231}
232
233impl AsRef<PathBuf> for PathAbs {
234 fn as_ref(&self) -> &PathBuf {
235 self.0.as_ref()
236 }
237}
238
239impl Borrow<Path> for PathAbs {
240 fn borrow(&self) -> &Path {
241 self.as_ref()
242 }
243}
244
245impl Borrow<PathBuf> for PathAbs {
246 fn borrow(&self) -> &PathBuf {
247 self.as_ref()
248 }
249}
250
251impl<'a> Borrow<Path> for &'a PathAbs {
252 fn borrow(&self) -> &Path {
253 self.as_ref()
254 }
255}
256
257impl<'a> Borrow<PathBuf> for &'a PathAbs {
258 fn borrow(&self) -> &PathBuf {
259 self.as_ref()
260 }
261}
262
263impl From<PathAbs> for Arc<PathBuf> {
264 fn from(path: PathAbs) -> Arc<PathBuf> {
265 path.0
266 }
267}
268
269impl From<PathAbs> for PathBuf {
270 fn from(path: PathAbs) -> PathBuf {
271 match Arc::try_unwrap(path.0) {
272 Ok(p) => p,
273 Err(inner) => inner.as_ref().clone(),
274 }
275 }
276}