initiative_core/utils/
substr.rs

1use std::ops::{Deref, Range};
2
3/// Represents a portion of a string slice. This behaviour mimics the core-level string slice
4/// model, with two exceptions:
5///
6/// * A reference to the original slice is maintained, permitting logic like Substr::after() to
7///   reference data outside of (after) the subslice.
8/// * The concept of "inner" and "outer" slices exists, which is used to handle the contents of
9///   quoted strings.
10#[derive(Clone, Debug)]
11pub struct Substr<'a> {
12    phrase: &'a str,
13    inner_range: Range<usize>,
14    outer_range: Range<usize>,
15}
16
17impl<'a> Substr<'a> {
18    pub fn new(phrase: &'a str, inner_range: Range<usize>, outer_range: Range<usize>) -> Self {
19        assert!(inner_range.start >= outer_range.start);
20        assert!(inner_range.end <= outer_range.end);
21        assert!(outer_range.end <= phrase.len());
22
23        Self {
24            phrase,
25            inner_range,
26            outer_range,
27        }
28    }
29
30    /// Returns a representation of the Substr as a normal string slice.
31    pub fn as_str(&self) -> &'a str {
32        &self.phrase[self.inner_range.clone()]
33    }
34
35    /// Returns the outer portion of the Substr, including quotes if present.
36    pub fn as_outer_str(&self) -> &'a str {
37        &self.phrase[self.outer_range.clone()]
38    }
39
40    /// Returns the entire input phrase.
41    pub fn as_original_str(&self) -> &'a str {
42        self.phrase
43    }
44
45    /// Returns the outer range of the Substr, ie. including quotes (if any).
46    pub fn range(&self) -> Range<usize> {
47        self.outer_range.clone()
48    }
49
50    /// Does this Substr appear at the end of the phrase (including any ignored characters)?
51    pub fn is_at_end(&self) -> bool {
52        self.outer_range.end == self.phrase.len()
53    }
54
55    /// Are there characters consumed but ignored by this Substr (ie. quotation marks)?
56    pub fn is_quoted(&self) -> bool {
57        self.inner_range != self.outer_range
58    }
59
60    /// Can the word be autocompleted? (Is it in such a position that adding characters to the end
61    /// of the overall phrase will extend the current word?)
62    pub fn can_complete(&self) -> bool {
63        self.is_at_end() && !self.is_quoted()
64    }
65
66    /// Get the remainder of the phrase starting from the end of the Substr.
67    pub fn after(&self) -> Substr<'a> {
68        Substr {
69            phrase: self.phrase,
70            inner_range: self.outer_range.end..self.phrase.len(),
71            outer_range: self.outer_range.end..self.phrase.len(),
72        }
73    }
74}
75
76impl Eq for Substr<'_> {}
77
78impl PartialEq for Substr<'_> {
79    fn eq(&self, other: &Self) -> bool {
80        self == other.as_str()
81    }
82}
83
84impl PartialEq<str> for Substr<'_> {
85    fn eq(&self, other: &str) -> bool {
86        self.as_str() == other
87    }
88}
89
90impl AsRef<str> for Substr<'_> {
91    fn as_ref(&self) -> &str {
92        self.as_str()
93    }
94}
95
96impl Deref for Substr<'_> {
97    type Target = str;
98
99    fn deref(&self) -> &Self::Target {
100        self.as_str()
101    }
102}
103
104impl<'a> From<&'a str> for Substr<'a> {
105    fn from(input: &'a str) -> Substr<'a> {
106        Substr {
107            phrase: input,
108            inner_range: 0..input.len(),
109            outer_range: 0..input.len(),
110        }
111    }
112}
113
114impl<'a> From<&'a String> for Substr<'a> {
115    fn from(input: &'a String) -> Substr<'a> {
116        Substr {
117            phrase: input,
118            inner_range: 0..input.len(),
119            outer_range: 0..input.len(),
120        }
121    }
122}
123
124#[cfg(test)]
125mod test {
126    use super::*;
127
128    #[test]
129    fn new_test() {
130        assert_eq!(
131            Substr {
132                phrase: "abc",
133                inner_range: 1..2,
134                outer_range: 0..3,
135            },
136            Substr::new("abc", 1..2, 0..3),
137        );
138
139        Substr::new("", 0..0, 0..0);
140        Substr::new("a", 0..1, 0..1);
141    }
142
143    #[test]
144    #[should_panic]
145    fn new_test_inner_starts_before_outer() {
146        Substr::new("abc", 0..2, 1..3);
147    }
148
149    #[test]
150    #[should_panic]
151    fn new_test_inner_ends_after_outer() {
152        Substr::new("abc", 1..3, 0..2);
153    }
154
155    #[test]
156    #[should_panic]
157    fn new_test_range_too_long() {
158        Substr::new("abc", 0..2, 0..4);
159    }
160
161    #[test]
162    fn as_str_test() {
163        let (as_str, as_outer_str, as_original_str) = {
164            let substr = Substr::new("abcde", 2..3, 1..4);
165            (
166                substr.as_str(),
167                substr.as_outer_str(),
168                substr.as_original_str(),
169            )
170        };
171
172        assert_eq!("c", as_str);
173        assert_eq!("bcd", as_outer_str);
174        assert_eq!("abcde", as_original_str);
175    }
176
177    #[test]
178    fn range_test() {
179        assert_eq!(0..3, Substr::new("abc", 1..2, 0..3).range());
180    }
181
182    #[test]
183    fn is_at_end_test() {
184        assert!(Substr::new("abc", 1..2, 1..3).is_at_end());
185        assert!(!Substr::new("abc", 1..2, 1..2).is_at_end());
186    }
187
188    #[test]
189    fn is_quoted_test() {
190        assert!(Substr::new("abc", 1..3, 0..3).is_quoted());
191        assert!(!Substr::new("abc", 1..3, 1..3).is_quoted());
192    }
193
194    #[test]
195    fn can_complete_test() {
196        assert!(Substr::new("abc", 1..3, 1..3).can_complete());
197        assert!(!Substr::new("abc", 1..2, 1..2).can_complete());
198        assert!(!Substr::new("abc", 1..2, 1..3).can_complete());
199    }
200
201    #[test]
202    fn after_test() {
203        assert_eq!("c", Substr::new("abc", 0..1, 0..2).after().as_str());
204    }
205}