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
use std::{
    error::Error,
    fmt::Display,
    path::{self, Path, PathBuf},
};

#[derive(Debug)]
#[non_exhaustive]
pub struct StripPrefixError {
    absolute_path: PathBuf,
    kind: StripPrefixErrorKind,
}

impl Display for StripPrefixError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.kind {
            StripPrefixErrorKind::Strip { ancestor, inner: _ } => write!(
                f,
                "cannot strip prefix {:?} from path {:?}",
                ancestor, self.absolute_path
            ),
            StripPrefixErrorKind::PrefixNotFound { prefix } => write!(
                f,
                "never encountered prefix {:?} in path {:?}",
                prefix, self.absolute_path
            ),
        }
    }
}

impl Error for StripPrefixError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match &self.kind {
            StripPrefixErrorKind::Strip { ancestor: _, inner } => Some(inner),
            StripPrefixErrorKind::PrefixNotFound { prefix: _ } => None,
        }
    }
}

#[derive(Debug)]
pub enum StripPrefixErrorKind {
    #[non_exhaustive]
    Strip {
        ancestor: PathBuf,
        inner: path::StripPrefixError,
    },
    #[non_exhaustive]
    PrefixNotFound { prefix: PathBuf },
}

// DISCUSS: can this function name be improved?
pub(crate) fn remove_relative_path_prefix_from_absolute_path(
    prefix: &Path,
    absolute_path: &Path,
) -> Result<PathBuf, StripPrefixError> {
    (|| {
        for ancestor in absolute_path.ancestors() {
            if ancestor.ends_with(prefix) {
                let relative_path = absolute_path
                    .strip_prefix(ancestor)
                    .map(ToOwned::to_owned)
                    .map_err(|inner| StripPrefixErrorKind::Strip {
                        ancestor: ancestor.to_owned(),
                        inner,
                    })?;
                return Ok(relative_path);
            }
        }

        return Err(StripPrefixErrorKind::PrefixNotFound {
            prefix: prefix.to_owned(),
        })?;
    })()
    .map_err(|kind| StripPrefixError {
        absolute_path: absolute_path.to_owned(),
        kind,
    })
}

pub(crate) fn is_glob(string: &str) -> bool {
    string.contains('*')
}

pub(crate) fn glob_file_extension(glob: &str) -> Option<String> {
    if glob.ends_with('*') {
        return None;
    }
    Some(
        glob.rsplit('*')
            .next()
            .expect("Expected glob to contain an asterisk")
            .to_owned(),
    )
}

pub(crate) fn is_monorepo_file(monorepo_root: &Path, file: &Path) -> bool {
    for ancestor in file.ancestors() {
        if ancestor.ends_with(monorepo_root) {
            return true;
        }
    }
    false
}

pub(crate) fn is_child_of_node_modules(file: &Path) -> bool {
    for ancestor in file.ancestors() {
        if ancestor.ends_with("node_modules") {
            return true;
        }
    }
    false
}