nu_path/expansions.rs
1#[cfg(windows)]
2use omnipath::WinPathExt;
3use std::io;
4use std::path::{Path, PathBuf, absolute};
5
6use super::dots::{expand_dots, expand_ndots};
7use super::tilde::expand_tilde;
8
9// Join a path relative to another path. Paths starting with tilde are considered as absolute.
10fn join_path_relative<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
11where
12 P: AsRef<Path>,
13 Q: AsRef<Path>,
14{
15 let path = path.as_ref();
16 let relative_to = relative_to.as_ref();
17
18 if path == Path::new(".") {
19 // Joining a Path with '.' appends a '.' at the end, making the prompt
20 // more ugly - so we don't do anything, which should result in an equal
21 // path on all supported systems.
22 relative_to.into()
23 } else if path.to_string_lossy().as_ref().starts_with('~') && expand_tilde {
24 // do not end up with "/some/path/~" or "/some/path/~user"
25 path.into()
26 } else {
27 relative_to.join(path)
28 }
29}
30
31fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
32 let path = expand_tilde(path);
33 let path = expand_ndots(path);
34 canonicalize_path(&path)
35}
36
37#[cfg(windows)]
38fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
39 path.canonicalize()?.to_winuser_path()
40}
41
42#[cfg(not(windows))]
43fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
44 path.canonicalize()
45}
46
47/// Resolve all symbolic links and all components (tilde, ., .., ...+) and return the path in its
48/// absolute form.
49///
50/// Consider using [`absolute_with`] if you don't need to resolve symlinks.
51///
52/// Fails under the same conditions as
53/// [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html).
54/// The input path is specified relative to another path
55pub fn canonicalize_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
56where
57 P: AsRef<Path>,
58 Q: AsRef<Path>,
59{
60 let path = join_path_relative(path, relative_to, true);
61
62 canonicalize(path)
63}
64
65/// Returns the path in its absolute form without resolving symlinks.
66///
67/// On Unix systems, .. components are a kind of link so these aren't resolved.
68/// On Windows .. components are resoloved.
69///
70/// Fails under the same conditions as
71/// [`std::path::absolute`](https://doc.rust-lang.org/std/path/fn.absolute.html).
72/// In practice this is only likely to fail if both paths are empty.
73pub fn absolute_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
74where
75 P: AsRef<Path>,
76 Q: AsRef<Path>,
77{
78 let path = join_path_relative(path, relative_to, true);
79 let path = expand_tilde(path);
80 let path = expand_ndots(path);
81 absolute(path)
82}
83
84/// Resolve only path components (tilde, ., .., ...+), if possible.
85///
86/// Doesn't convert to absolute form or use syscalls. Output may begin with "../"
87pub fn expand_path(path: impl AsRef<Path>, need_expand_tilde: bool) -> PathBuf {
88 let path = if need_expand_tilde {
89 expand_tilde(path)
90 } else {
91 PathBuf::from(path.as_ref())
92 };
93 let path = expand_ndots(path);
94 expand_dots(path)
95}
96
97/// Resolve only path components (tilde, ., .., ...+), if possible.
98///
99/// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded
100/// version if the expansion is not possible.
101///
102/// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink).
103///
104/// Converts to absolute form but does not resolve symlinks.
105/// The input path is specified relative to another path
106pub fn expand_path_with<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
107where
108 P: AsRef<Path>,
109 Q: AsRef<Path>,
110{
111 let path = join_path_relative(path, relative_to, expand_tilde);
112
113 expand_path(path, expand_tilde)
114}
115
116/// Resolve to a path that is accepted by the system and no further - tilde is expanded, and ndot path components are expanded.
117///
118/// This function will take a leading tilde path component, and expand it to the user's home directory;
119/// it will also expand any path elements consisting of only dots into the correct number of `..` path elements.
120/// It does not do any normalization except to what will be accepted by Path::open,
121/// and it does not touch the system at all, except for getting the home directory of the current user.
122pub fn expand_to_real_path<P>(path: P) -> PathBuf
123where
124 P: AsRef<Path>,
125{
126 let path = expand_tilde(path);
127 expand_ndots(path)
128}
129
130/// Attempts to canonicalize the path against the current directory. Failing that, if
131/// the path is relative, it attempts all of the dirs in `dirs`. If that fails, it returns
132/// the original error.
133pub fn locate_in_dirs<I, P>(
134 filename: impl AsRef<Path>,
135 cwd: impl AsRef<Path>,
136 dirs: impl FnOnce() -> I,
137) -> std::io::Result<PathBuf>
138where
139 I: IntoIterator<Item = P>,
140 P: AsRef<Path>,
141{
142 let filename = filename.as_ref();
143 let cwd = cwd.as_ref();
144 match canonicalize_with(filename, cwd) {
145 Ok(path) => Ok(path),
146 Err(err) => {
147 // Try to find it in `dirs` first, before giving up
148 let mut found = None;
149 for dir in dirs() {
150 if let Ok(path) =
151 canonicalize_with(dir, cwd).and_then(|dir| canonicalize_with(filename, dir))
152 {
153 found = Some(path);
154 break;
155 }
156 }
157 found.ok_or(err)
158 }
159 }
160}