1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Calculates the common prefix for a set of paths, if a common prefix exists
//!
//! # Example
//!
//! ```rust
//! # extern crate common_path;
//! use std::path::{PathBuf, Path};
//! use common_path::common_path;
//!
//! # fn main() {
//! let baz = Path::new("/foo/bar/baz");
//! let quux = Path::new("/foo/bar/quux");
//! let prefix = common_path(baz, quux).unwrap();
//! assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
//! # }
//! ```
//!
//! Or for more than 2 paths:
//!
//! ```rust
//! # extern crate common_path;
//! use std::path::{PathBuf, Path};
//! use common_path::common_path_all;
//!
//! # fn main() {
//! let baz = Path::new("/foo/bar/baz");
//! let quux = Path::new("/foo/bar/quux");
//! let foo = Path::new("/foo/bar/foo");
//! let prefix = common_path_all(vec![baz, quux, foo]).unwrap();
//! assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
//! # }
//! ```
#[cfg(test)]
extern crate rand;

use std::path::{Path, PathBuf};

/// Find the common prefix, if any, between any number of paths
///
/// # Example
///
/// ```rust
/// # extern crate common_path;
/// use std::path::{PathBuf, Path};
/// use common_path::common_path_all;
///
/// # fn main() {
/// let baz = Path::new("/foo/bar/baz");
/// let quux = Path::new("/foo/bar/quux");
/// let foo = Path::new("/foo/bar/foo");
/// let prefix = common_path_all(vec![baz, quux, foo]).unwrap();
/// assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
/// # }
/// ```
pub fn common_path_all<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Option<PathBuf> {
    let mut path_iter = paths.into_iter();
    let mut result = path_iter.next()?.to_path_buf();
    for path in path_iter {
        if let Some(r) = common_path(&result, &path) {
            result = r;
        } else {
            return None;
        }
    }
    Some(result.to_path_buf())
}

/// Find the common prefix, if any, between 2 paths
///
/// # Example
///
/// ```rust
/// # extern crate common_path;
/// use std::path::{PathBuf, Path};
/// use common_path::common_path;
///
/// # fn main() {
/// let baz = Path::new("/foo/bar/baz");
/// let quux = Path::new("/foo/bar/quux");
/// let prefix = common_path(baz, quux).unwrap();
/// assert_eq!(prefix, Path::new("/foo/bar").to_path_buf());
/// # }
/// ```
pub fn common_path<P, Q>(one: P, two: Q) -> Option<PathBuf>
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let one = one.as_ref();
    let two = two.as_ref();
    let one = one.components();
    let two = two.components();
    let mut final_path = PathBuf::new();
    let mut found = false;
    let paths = one.zip(two);
    for (l, r) in paths {
        if l == r {
            final_path.push(l.as_os_str());
            found = true;
        } else {
            break;
        }
    }
    if found {
        Some(final_path)
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::{
        thread_rng,
        seq::SliceRandom,
    };

    #[test]
    fn compare_all_paths() {
        let mut rng = thread_rng();
        for _ in 0..6 {
            let one = Path::new("/foo/bar/baz/one.txt");
            let two = Path::new("/foo/bar/quux/quuux/two.txt");
            let three = Path::new("/foo/bar/baz/foo/bar");
            let result = Path::new("/foo/bar");
            let mut all = vec![one, two, three];
            all.shuffle(&mut rng);
            assert_eq!(common_path_all(all).unwrap(), result.to_path_buf())
        }
    }

    #[test]
    fn compare_paths() {
        let one = Path::new("/foo/bar/baz/one.txt");
        let two = Path::new("/foo/bar/quux/quuux/two.txt");
        let result = Path::new("/foo/bar");
        assert_eq!(common_path(&one, &two).unwrap(), result.to_path_buf())
    }

    #[test]
    fn no_common_path() {
        let one = Path::new("/foo/bar");
        let two = Path::new("./baz/quux");
        assert!(common_path(&one, &two).is_none());
    }
}