1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[derive(Clone, Debug, Default, PartialEq, Eq)]
6pub struct MatchSpan {
7 pub start: usize,
8 pub end: usize,
9}
10
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
13pub struct TextMatch {
14 pub value: String,
15 pub span: MatchSpan,
16}
17
18#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct NamedMatch {
21 pub name: Option<String>,
22 pub value: String,
23 pub span: MatchSpan,
24}
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub enum MatchKind {
29 Exact,
30 Prefix,
31 Suffix,
32 Contains,
33 Pattern,
34 #[default]
35 Unknown,
36}
37
38pub fn is_match_span_valid(input: &str, span: &MatchSpan) -> bool {
40 span.start <= span.end
41 && span.end <= input.len()
42 && input.is_char_boundary(span.start)
43 && input.is_char_boundary(span.end)
44}
45
46pub fn slice_match<'a>(input: &'a str, span: &MatchSpan) -> Option<&'a str> {
48 if is_match_span_valid(input, span) {
49 input.get(span.start..span.end)
50 } else {
51 None
52 }
53}
54
55pub fn match_len(span: &MatchSpan) -> usize {
57 span.end.saturating_sub(span.start)
58}
59
60pub fn match_is_empty(span: &MatchSpan) -> bool {
62 match_len(span) == 0
63}
64
65pub fn contains_match(matches: &[TextMatch], value: &str) -> bool {
67 matches.iter().any(|item| item.value == value)
68}
69
70pub fn first_match(matches: &[TextMatch]) -> Option<&TextMatch> {
72 matches.first()
73}
74
75pub fn last_match(matches: &[TextMatch]) -> Option<&TextMatch> {
77 matches.last()
78}