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
use std::ops::Range;

/// Replace all recognized `left_delim placeholder right_delim` sequences with `replace_with`.

///

/// See [`first_placeholder_range()`] for how and when such a sequence is recognized.

pub fn replace_all_placeholders(
    string: &mut String,
    placeholder: &str,
    replace_with: &str,
    left_delim: char,
    right_delim: char,
) {
    let iter = StrPlaceholderRangeIter::new(placeholder, left_delim, right_delim);

    let mut next_range = 0..string.len();
    while let Some(range) = iter.next_range(&string[next_range]) {
        string.replace_range(range.clone(), replace_with);
        next_range = (range.start + replace_with.len())..string.len();
    }
}

/// Get the first range into `string` that contains the sequence

/// `left_delim placeholder right_delim`.

///

/// For the left and right delimiters to be recognized, there must be:

/// - only one character that is equal to the delimiter

/// - an odd amount of the delimiter characters

///

/// This behaviour allows the delimiters/placeholder to be escaped.

pub fn first_placeholder_range(
    string: &str,
    placeholder: &str,
    left_delim: char,
    right_delim: char,
) -> Option<Range<usize>> {
    StrPlaceholderRangeIter::new(placeholder, left_delim, right_delim).next_range(string)
}

struct StrPlaceholderRangeIter<'a> {
    placeholder: &'a str,
    left_delim: char,
    left_delim_len: usize,
    right_delim: char,
    right_delim_len: usize,
}

impl<'a> StrPlaceholderRangeIter<'a> {
    pub fn new(placeholder: &'a str, left_delim: char, right_delim: char) -> Self {
        StrPlaceholderRangeIter {
            placeholder,
            left_delim,
            left_delim_len: left_delim.len_utf8(),
            right_delim,
            right_delim_len: right_delim.len_utf8(),
        }
    }

    pub fn next_range(&self, mut string: &str) -> Option<Range<usize>> {
        loop {
            if let Some(start_index) = string.find(self.placeholder) {
                // check that on the left side of the placeholder are an odd number of

                // `self.left_delim` characters

                let is_left_delim = is_delimited(
                    string[..start_index]
                        .chars()
                        .rev()
                        .take_while(|c| *c == self.left_delim)
                        .count(),
                );
                let end_index = start_index + self.placeholder.len();

                // check that on the right side of the placeholder are an odd number of

                // `self.right_delim` characters

                let is_right_delim = is_delimited(
                    string[end_index..]
                        .chars()
                        .take_while(|c| *c == self.right_delim)
                        .count(),
                );

                if is_left_delim && is_right_delim {
                    break Some(
                        (start_index - self.left_delim_len)..(end_index + self.right_delim_len),
                    );
                } else {
                    string = &string[end_index..];
                }
            } else {
                break None;
            }
        }
    }
}

#[inline(always)]
pub fn is_delimited(n: usize) -> bool {
    // there must be at least one delimiter, and if more than one, the number must be odd

    n > 0 && (n % 2 != 0)
}