use std::cmp::Ordering;
use std::path::Path;
pub fn lt_by_dirs(path1: &Path, path2: &Path) -> bool {
let path1_parts = path1.components();
let path2_parts = path2.components();
let mut path1_parts_iter = path1_parts;
let mut path2_parts_iter = path2_parts;
loop {
match (path1_parts_iter.next(), path2_parts_iter.next()) {
(None, None) => return false,
(None, Some(_)) => return true,
(Some(_), None) => return false,
(Some(part1), Some(part2)) => match part1.cmp(&part2) {
Ordering::Equal => continue,
Ordering::Less => return true,
Ordering::Greater => return false,
},
}
}
}
pub fn lt_path_by_dirblock(path1: &Path, path2: &Path) -> bool {
let key1 = (path1.parent(), path1.file_name());
let key2 = (path2.parent(), path2.file_name());
key1 < key2
}
pub fn bisect_path_left(paths: &[&Path], path: &Path) -> usize {
let mut hi = paths.len();
let mut lo = 0;
while lo < hi {
let mid = (lo + hi) / 2;
let cur = paths[mid];
if lt_path_by_dirblock(cur, path) {
lo = mid + 1;
} else {
hi = mid;
}
}
lo
}
pub fn bisect_path_right(paths: &[&Path], path: &Path) -> usize {
let mut hi = paths.len();
let mut lo = 0;
while lo < hi {
let mid = (lo + hi) / 2;
let cur = paths[mid];
if lt_path_by_dirblock(path, cur) {
hi = mid;
} else {
lo = mid + 1;
}
}
lo
}
#[cfg(test)]
mod tests {
use super::*;
fn p(s: &str) -> &Path {
Path::new(s)
}
fn assert_cmp(expected: i32, a: &str, b: &str) {
let (pa, pb) = (p(a), p(b));
match expected {
0 => {
assert_eq!(a, b);
assert!(!lt_by_dirs(pa, pb));
assert!(!lt_by_dirs(pb, pa));
}
v if v > 0 => {
assert!(!lt_by_dirs(pa, pb));
assert!(lt_by_dirs(pb, pa));
}
_ => {
assert!(lt_by_dirs(pa, pb));
assert!(!lt_by_dirs(pb, pa));
}
}
}
#[test]
fn lt_by_dirs_cmp_empty() {
assert_cmp(0, "", "");
assert_cmp(1, "a", "");
assert_cmp(1, "abcdef", "");
assert_cmp(1, "test/ing/a/path/", "");
}
#[test]
fn lt_by_dirs_cmp_same_str() {
for s in ["a", "ab", "abc", "a/b", "a/b/c/d/e"] {
assert_cmp(0, s, s);
}
}
#[test]
fn lt_by_dirs_simple_paths() {
assert_cmp(-1, "a", "b");
assert_cmp(-1, "aa", "ab");
assert_cmp(-1, "ab", "bb");
assert_cmp(-1, "a/a", "a/b");
assert_cmp(-1, "a/b", "b/b");
assert_cmp(-1, "a/a/a", "a/a/b");
}
#[test]
fn lt_by_dirs_tricky_paths() {
assert_cmp(1, "ab/cd/ef", "ab/cc/ef");
assert_cmp(1, "ab/cd/ef", "ab/c/ef");
assert_cmp(-1, "ab/cd/ef", "ab/cd-ef");
assert_cmp(-1, "ab/cd", "ab/cd-");
assert_cmp(-1, "ab/cd", "ab-cd");
}
#[test]
fn lt_by_dirs_non_ascii() {
assert_cmp(-1, "\u{b5}", "\u{e5}");
assert_cmp(-1, "a", "\u{e5}");
assert_cmp(-1, "b", "\u{b5}");
assert_cmp(-1, "a/b", "a/\u{e5}");
assert_cmp(-1, "b/a", "b/\u{b5}");
}
#[test]
fn lt_path_by_dirblock_simple_sorted_list() {
let paths: Vec<&Path> = vec![p(""), p("a"), p("ab"), p("abc"), p("a/b/c"), p("b/d/e")];
for (i, a) in paths.iter().enumerate() {
for (j, b) in paths.iter().enumerate() {
assert_eq!(
lt_path_by_dirblock(a, b),
i < j,
"lt_path_by_dirblock({:?}, {:?}) mismatched i={} j={}",
a,
b,
i,
j,
);
}
}
}
#[test]
fn bisect_path_left_simple_list() {
let paths: Vec<&Path> = vec![p(""), p("a"), p("b"), p("c"), p("d")];
for (i, path) in paths.iter().enumerate() {
assert_eq!(bisect_path_left(&paths, path), i);
}
assert_eq!(bisect_path_left(&paths, p("_")), 1);
assert_eq!(bisect_path_left(&paths, p("aa")), 2);
assert_eq!(bisect_path_left(&paths, p("bb")), 3);
assert_eq!(bisect_path_left(&paths, p("dd")), 5);
}
#[test]
fn bisect_path_right_after_equal_entry() {
let paths: Vec<&Path> = vec![p(""), p("a"), p("b"), p("c"), p("d")];
for (i, path) in paths.iter().enumerate() {
assert_eq!(bisect_path_right(&paths, path), i + 1);
}
}
fn assert_lt_dirblock_order(paths: &[&str]) {
let paths: Vec<&Path> = paths.iter().map(|s| p(s)).collect();
for (i, a) in paths.iter().enumerate() {
for (j, b) in paths.iter().enumerate() {
assert_eq!(
lt_path_by_dirblock(a, b),
i < j,
"lt_path_by_dirblock({:?}, {:?}) mismatched i={} j={}",
a,
b,
i,
j,
);
}
}
}
#[test]
fn lt_path_by_dirblock_tricky_paths() {
assert_lt_dirblock_order(&[
"",
"a",
"a-a",
"a=a",
"b", "a/a",
"a/a-a",
"a/a=a",
"a/b", "a/a/a",
"a/a/a-a",
"a/a/a=a", "a/a/a/a",
"a/a/a/b", "a/a/a-a/a",
"a/a/a-a/b", "a/a/a=a/a",
"a/a/a=a/b", "a/a-a/a", "a/a-a/a/a",
"a/a-a/a/b", "a/a=a/a", "a/b/a",
"a/b/b", "a-a/a",
"a-a/b", "a=a/a",
"a=a/b", "b/a",
"b/b", ]);
}
#[test]
fn lt_path_by_dirblock_non_ascii() {
assert_lt_dirblock_order(&[
"",
"a",
"\u{b5}",
"\u{e5}", "a/a",
"a/\u{b5}",
"a/\u{e5}", "a/a/a",
"a/a/\u{b5}",
"a/a/\u{e5}", "a/\u{b5}/a",
"a/\u{b5}/\u{b5}",
"a/\u{b5}/\u{e5}", "a/\u{e5}/a",
"a/\u{e5}/\u{b5}",
"a/\u{e5}/\u{e5}", "\u{b5}/a",
"\u{b5}/\u{b5}",
"\u{b5}/\u{e5}", "\u{e5}/a",
"\u{e5}/\u{b5}",
"\u{e5}/\u{e5}", ]);
}
const INVOLVED_PATHS: &[&str] = &[
"", "a", "a-a", "a-z", "a=a", "a=z", "a/a", "a/a-a", "a/a-z", "a/a=a", "a/a=z", "a/z", "a/z-a", "a/z-z", "a/z=a",
"a/z=z", "a/a/a", "a/a/z", "a/a-a/a", "a/a-z/z", "a/a=a/a", "a/a=z/z", "a/z/a", "a/z/z", "a-a/a", "a-z/z", "a=a/a", "a=z/z", ];
#[test]
fn bisect_path_left_involved_fixture() {
let paths: Vec<&Path> = INVOLVED_PATHS.iter().map(|s| p(s)).collect();
for (i, path) in paths.iter().enumerate() {
assert_eq!(bisect_path_left(&paths, path), i, "path {:?}", path);
}
}
#[test]
fn bisect_path_right_involved_fixture() {
let paths: Vec<&Path> = INVOLVED_PATHS.iter().map(|s| p(s)).collect();
for (i, path) in paths.iter().enumerate() {
assert_eq!(bisect_path_right(&paths, path), i + 1, "path {:?}", path);
}
}
}