zellij_utils/common_path.rs
1// The following license refers to code in this file and this file only.
2// We chose to vendor this dependency rather than depend on it through crates.io in order to facilitate
3// packaging. This license was copied verbatim from: https://docs.rs/crate/common-path/1.0.0/source/LICENSE-MIT
4//
5// MIT License
6//
7// Copyright 2018 Paul Woolcock <paul@woolcock.us>
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy of
10// this software and associated documentation files (the "Software"), to deal in
11// the Software without restriction, including without limitation the rights to
12// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13// of the Software, and to permit persons to whom the Software is furnished to do
14// so, subject to the following conditions:
15//
16// The above copyright notice and this permission notice shall be included in all
17// copies or substantial portions of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25// SOFTWARE.
26
27use std::path::{Path, PathBuf};
28
29/// Find the common prefix, if any, between any number of paths
30///
31/// # Example
32///
33/// ```rust
34/// use std::path::{PathBuf, Path};
35/// use zellij_utils::common_path::common_path_all;
36///
37/// # fn main() {
38/// let baz = Path::new("/foo/bar/baz");
39/// let quux = Path::new("/foo/bar/quux");
40/// let foo = Path::new("/foo/bar/foo");
41/// let prefix = common_path_all(vec![baz, quux, foo]).unwrap();
42/// assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
43/// # }
44/// ```
45pub fn common_path_all<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Option<PathBuf> {
46 let mut path_iter = paths.into_iter();
47 let mut result = path_iter.next()?.to_path_buf();
48 for path in path_iter {
49 if let Some(r) = common_path(&result, &path) {
50 result = r;
51 } else {
52 return None;
53 }
54 }
55 Some(result.to_path_buf())
56}
57
58/// Find the common prefix, if any, between 2 paths
59///
60/// # Example
61///
62/// ```rust
63/// use std::path::{PathBuf, Path};
64/// use zellij_utils::common_path::common_path;
65///
66/// # fn main() {
67/// let baz = Path::new("/foo/bar/baz");
68/// let quux = Path::new("/foo/bar/quux");
69/// let prefix = common_path(baz, quux).unwrap();
70/// assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
71/// # }
72/// ```
73pub fn common_path<P, Q>(one: P, two: Q) -> Option<PathBuf>
74where
75 P: AsRef<Path>,
76 Q: AsRef<Path>,
77{
78 let one = one.as_ref();
79 let two = two.as_ref();
80 let one = one.components();
81 let two = two.components();
82 let mut final_path = PathBuf::new();
83 let mut found = false;
84 let paths = one.zip(two);
85 for (l, r) in paths {
86 if l == r {
87 final_path.push(l.as_os_str());
88 found = true;
89 } else {
90 break;
91 }
92 }
93 if found {
94 Some(final_path)
95 } else {
96 None
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn compare_all_paths() {
106 let one = Path::new("/foo/bar/baz/one.txt");
107 let two = Path::new("/foo/bar/quux/quuux/two.txt");
108 let three = Path::new("/foo/bar/baz/foo/bar");
109 let result = Path::new("/foo/bar");
110 let path_permutations = vec![
111 vec![one, two, three],
112 vec![one, three, two],
113 vec![two, one, three],
114 vec![two, three, one],
115 vec![three, one, two],
116 vec![three, two, one],
117 ];
118 for all in path_permutations {
119 assert_eq!(common_path_all(all).unwrap(), result.to_path_buf())
120 }
121 }
122
123 #[test]
124 fn compare_paths() {
125 let one = Path::new("/foo/bar/baz/one.txt");
126 let two = Path::new("/foo/bar/quux/quuux/two.txt");
127 let result = Path::new("/foo/bar");
128 assert_eq!(common_path(&one, &two).unwrap(), result.to_path_buf())
129 }
130
131 #[test]
132 fn no_common_path() {
133 let one = Path::new("/foo/bar");
134 let two = Path::new("./baz/quux");
135 assert!(common_path(&one, &two).is_none());
136 }
137}