summaryrefslogtreecommitdiffstats
path: root/src/context/matcher.rs
blob: 6c7316176d7c73a71185f6abd80db1ac1dffca36 (plain)
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
use std::fmt::{Display, Formatter, Result as FmtResult};

use globset::{GlobBuilder, GlobMatcher};
use regex::{Regex, RegexBuilder};

use crate::config::option::CaseSensitivity;
use crate::error::JoshutoResult;

#[derive(Clone, Debug, Default)]
pub enum MatchContext {
    Glob(GlobMatcher),
    Regex(Regex),
    String {
        pattern: String,
        actual_case_sensitivity: CaseSensitivity,
    },
    #[default]
    None,
}

impl MatchContext {
    pub fn new_glob(pattern: &str, case_sensitivity: CaseSensitivity) -> JoshutoResult<Self> {
        let pattern_lower = pattern.to_lowercase();

        let (pattern, actual_case_sensitivity) = match case_sensitivity {
            CaseSensitivity::Insensitive => (pattern_lower.as_str(), CaseSensitivity::Insensitive),
            CaseSensitivity::Sensitive => (pattern, CaseSensitivity::Sensitive),
            // Determine the actual case sensitivity by whether an uppercase letter occurs.
            CaseSensitivity::Smart => {
                if pattern_lower == pattern {
                    (pattern_lower.as_str(), CaseSensitivity::Insensitive)
                } else {
                    (pattern, CaseSensitivity::Sensitive)
                }
            }
        };

        let glob = GlobBuilder::new(pattern)
            .case_insensitive(matches!(
                actual_case_sensitivity,
                CaseSensitivity::Insensitive
            ))
            .build()?
            .compile_matcher();

        Ok(Self::Glob(glob))
    }

    pub fn new_regex(pattern: &str, case_sensitivity: CaseSensitivity) -> JoshutoResult<Self> {
        let pattern_lower = pattern.to_lowercase();

        let (pattern, actual_case_sensitivity) = match case_sensitivity {
            CaseSensitivity::Insensitive => (pattern_lower.as_str(), CaseSensitivity::Insensitive),
            CaseSensitivity::Sensitive => (pattern, CaseSensitivity::Sensitive),
            // Determine the actual case sensitivity by whether an uppercase letter occurs.
            CaseSensitivity::Smart => {
                if pattern_lower == pattern {
                    (pattern_lower.as_str(), CaseSensitivity::Insensitive)
                } else {
                    (pattern, CaseSensitivity::Sensitive)
                }
            }
        };

        let re = RegexBuilder::new(pattern)
            .case_insensitive(matches!(
                actual_case_sensitivity,
                CaseSensitivity::Insensitive
            ))
            .build()?;

        Ok(Self::Regex(re))
    }

    pub fn new_string(pattern: &str, case_sensitivity: CaseSensitivity) -> Self {
        let (pattern, actual_case_sensitivity) = match case_sensitivity {
            CaseSensitivity::Insensitive => (pattern.to_lowercase(), CaseSensitivity::Insensitive),
            CaseSensitivity::Sensitive => (pattern.to_string(), CaseSensitivity::Sensitive),
            // Determine the actual case sensitivity by whether an uppercase letter occurs.
            CaseSensitivity::Smart => {
                if pattern.chars().all(|c| c.is_lowercase()) {
                    // All characters in `pattern` is lowercase
                    (pattern.to_string(), CaseSensitivity::Insensitive)
                } else {
                    (pattern.to_string(), CaseSensitivity::Sensitive)
                }
            }
        };

        Self::String {
            pattern,
            actual_case_sensitivity,
        }
    }

    pub fn is_match(&self, main: &str) -> bool {
        match self {
            Self::Glob(glob_matcher) => Self::is_match_glob(main, glob_matcher),
            Self::Regex(regex) => Self::is_match_regex(main, regex),
            Self::String {
                pattern,
                actual_case_sensitivity,
            } => Self::is_match_string(main, pattern, *actual_case_sensitivity),
            Self::None => true,
        }
    }

    fn is_match_glob(main: &str, glob_matcher: &GlobMatcher) -> bool {
        glob_matcher.is_match(main)
    }

    fn is_match_regex(main: &str, regex: &Regex) -> bool {
        match regex.find(main) {
            Some(res) => res.range() == (0..main.len()),
            None => false,
        }
    }

    fn is_match_string(
        main: &str,
        pattern: &str,
        actual_case_sensitivity: CaseSensitivity,
    ) -> bool {
        match actual_case_sensitivity {
            CaseSensitivity::Insensitive => main.to_lowercase().contains(pattern),
            CaseSensitivity::Sensitive => main.contains(pattern),
            CaseSensitivity::Smart => unreachable!(),
        }
    }

    pub fn is_none(&self) -> bool {
        matches!(self, Self::None)
    }
}

impl Display for MatchContext {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            Self::Glob(glob_matcher) => write!(f, "{}", glob_matcher.glob().glob()),
            Self::Regex(regex) => write!(f, "{}", regex.as_str()),
            Self::String { pattern, .. } => write!(f, "{pattern}"),
            Self::None => Ok(()),
        }
    }
}