summaryrefslogtreecommitdiff
path: root/rola-vcs/src/tools/dir_search.rs
blob: 3d32c700e8c177d5bfb2fd2a454363dcde45843a (plain) (blame)
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
use std::path::PathBuf;

pub enum DirSearchPattern<'a> {
    File(&'a str),
    Dir(&'a str),
}

/// Searches upward from the given path towards parent directories.
/// If any ancestor directory contains a file or directory matching the pattern,
/// returns that ancestor directory's path.
pub fn dir_search_prev(path: impl Into<PathBuf>, pattern: DirSearchPattern) -> Option<PathBuf> {
    let mut current: PathBuf = path.into();
    // Canonicalize the path if possible to ensure absolute traversal
    if let Ok(canonical) = current.canonicalize() {
        current = canonical;
    } else {
        // If canonicalization fails (e.g. path does not exist yet),
        // try to make it absolute using current dir
        if current.is_relative()
            && let Ok(cwd) = std::env::current_dir() {
                current = cwd.join(&current);
            }
    }

    loop {
        // Check if the current directory exists and is a directory
        if current.is_dir() {
            let has_match = match &pattern {
                DirSearchPattern::File(name) => {
                    let mut entry = current.clone();
                    entry.push(name);
                    entry.is_file()
                }
                DirSearchPattern::Dir(name) => {
                    let mut entry = current.clone();
                    entry.push(name);
                    entry.is_dir()
                }
            };

            if has_match {
                return Some(current);
            }
        }

        // Try to go to the parent directory
        if !current.pop() {
            // pop() returns false when there's no parent
            break;
        }
    }

    None
}