query_range/range/
query_range_iterator.rs

1use std::ops::Range;
2use std::cmp::min;
3use super::utility::{ get_range, Shift, shift_range, shift_range_in_content, is_within };
4
5/// Iterates all found query within given content.
6///
7/// ### Examples
8///
9/// **Iteration:**
10/// ```
11/// use query_range::QueryRangeItr;
12///
13/// let query = "needle";
14/// let content = "haystackneedlehaystackneedlehaystack";
15/// let mut occurrences = QueryRangeItr::new(query, content);
16/// while let Some(next) = occurrences.next() {
17///     println!("{}", &content[next]);
18/// }
19/// ```
20///
21/// **Collecting to an Array:**
22/// ```
23/// use query_range::QueryRangeItr;
24///
25/// let query = "needle";
26/// let content = "haystackneedlehaystackneedlehaystack";
27/// let mut occurrences = QueryRangeItr::new(query, content);
28/// let needles: Vec<String> = occurrences.map(|range| String::from(&content[range])).collect();
29/// ```
30/// **Collecting to Strings:**
31/// ```
32/// use query_range::QueryRangeItr;
33///
34/// let query = "needle";
35/// let content = "haystackneedlehaystackneedlehaystack";
36/// let mut occurrences = QueryRangeItr::new(query, content);
37/// let needles: Vec<String> = occurrences.collect_strings();
38/// ```
39///
40/// **Transforming the query:**
41/// ```
42/// use query_range::QueryRangeItr;
43///
44/// let query = "needle";
45/// let content = "haystackneedlehaystackneedlehaystack";
46/// let result = QueryRangeItr::transform_query(query, content, |it| it.to_uppercase());
47/// ```
48///
49/// **Reassembling the content:**
50/// ```
51/// use query_range::QueryRangeItr;
52/// use query_range::utility::to_title_case;
53///
54/// let query = "needle";
55/// let content = "haystackneedlehaystackneedlehaystack";
56/// let result = QueryRangeItr::transform_all(
57///     query,
58///     content,
59///     |it| it.to_uppercase(), // query transform
60///     |it| to_title_case(it), // non-query transform
61/// );
62/// ```
63pub struct QueryRangeItr<'a> {
64    inverted: bool,
65    query: &'a str,
66    current_content: &'a str,
67    full_content: &'a str,
68    removed_count: usize,
69}
70
71// ----------------------------------------------------------------------------------------------- /
72
73/// Basic implementation
74impl<'a> QueryRangeItr<'a> {
75
76    /// Private, common initializer method.
77    fn new_base(query: &'a str, content: &'a str, inverted: bool) -> QueryRangeItr<'a> {
78        Self {
79            inverted,
80            query,
81            current_content: content,
82            full_content: content,
83            removed_count: 0,
84        }
85    }
86
87    /// Creates a new iterator with given content or query which will iterate each *found* instance
88    /// of the query.
89    pub fn new(query: &'a str, content: &'a str) -> QueryRangeItr<'a> {
90        Self::new_base(query, content, false)
91    }
92
93    /// Creates a new iterator with given content or query which will iterate the content in
94    /// between each *found* instance of the query.
95    pub fn new_inverted(query: &'a str, content: &'a str) -> QueryRangeItr<'a> {
96        Self::new_base(query, content, true)
97    }
98
99    /// Collects all iterated ranges and builds an array of strings from the original content at those ranges
100    pub fn collect_strings(&mut self) -> Vec<String> {
101        let content = self.full_content;
102        self.map(|range| String::from(&content[range])).collect()
103    }
104
105    /// Gets the next range that matches the given query.
106    fn next_standard(&mut self) -> Option<Range<usize>> {
107        let current_content = self.current_content;
108        let possible_range = get_range(self.query, current_content);
109        if let Some(range) = possible_range {
110            if is_within(current_content, &range) {
111                let next_start = range.end;
112                let possible_range = shift_range_in_content(range, Shift::Up(self.removed_count), self.full_content);
113                if let Some(range) = possible_range {
114                    let start_len = current_content.len();
115                    self.current_content = &current_content[next_start..];
116                    self.removed_count += start_len - self.current_content.len();
117                    Some(range)
118                } else {
119                    None
120                }
121            } else {
122                None
123            }
124        } else {
125            None
126        }
127    }
128
129    /// Gets the next range that doesn't match the given query.
130    fn next_inverted(&mut self) -> Option<Range<usize>> {
131        let current_content = self.current_content;
132        let start_index: usize = 0;
133        let len = current_content.len();
134        let range = get_range(self.query, current_content).unwrap_or(len..len);
135        let end_index = range.start;
136        let next_start: usize = min(range.end, len);
137        let possible_range = shift_range(start_index..end_index, Shift::Up(self.removed_count));
138        self.current_content = &current_content[next_start..];
139        let start_length = len;
140        self.removed_count += start_length - self.current_content.len();
141        if current_content.len() > 0 {
142            possible_range
143        } else {
144            None
145        }
146    }
147}
148
149// ----------------------------------------------------------------------------------------------- /
150
151/// Transform methods
152impl<'a> QueryRangeItr<'a> {
153
154    /// Reassembles content, but transforms the query content or the non-query content
155    ///
156    /// **Example:**
157    /// ```
158    /// use query_range::QueryRangeItr;
159    ///
160    /// let query = "needle";
161    /// let content = "haystackneedlehaystackneedlehaystack";
162    /// let result = QueryRangeItr::transform(query, content, |it| it.to_uppercase(), false);
163    /// assert_eq!(result, "haystackNEEDLEhaystackNEEDLEhaystack");
164    /// ```
165    ///
166    /// **Parameters:**
167    /// - `query` - The search query
168    /// - `content` - The content to look for the query in
169    /// - `transform` -  A transform closure to run on all query content
170    /// - `invert` - If `true`, applies the transform to the non-query content
171    pub fn transform<T>(
172        query: &'a str,
173        content: &'a str,
174        transform: T,
175        invert: bool,
176    ) -> String where T: Fn(&str) -> String {
177        let selects = Self::new_base(query, content, invert);
178        let non_selects = Self::new_base(query, content, !invert);
179        let transform_each = |range: Range<usize>| {
180            let start = range.clone().start;
181            let original = &content[range];
182            let value = transform(original);
183            (value, start)
184        };
185        let transform_rest = |range: Range<usize>| {
186            let start = range.clone().start;
187            let original = &content[range];
188            let value = String::from(original);
189            (value, start)
190        };
191        let mut selected_subs: Vec<(String, usize)>  = selects.map(|range| transform_each(range)).collect();
192        let mut non_selected_subs: Vec<(String, usize)> = non_selects.map(|range| transform_rest(range)).collect();
193        selected_subs.append(&mut non_selected_subs);
194        let mut merged = selected_subs;
195        merged.sort_unstable_by(|s1,s2|  s1.1.cmp(&s2.1));
196        let strings: Vec<String> = merged.iter().map(|s| s.0.clone()).collect();
197        strings.join("")
198    }
199
200    /// Reassembles content, but transforms the query content
201    ///
202    /// **Example:**
203    /// ```
204    /// use query_range::QueryRangeItr;
205    ///
206    /// let query = "needle";
207    /// let content = "haystackneedlehaystackneedlehaystack";
208    /// let result = QueryRangeItr::transform_query(query, content, |it| it.to_uppercase());
209    /// assert_eq!(result, "haystackNEEDLEhaystackNEEDLEhaystack");
210    /// ```
211    ///
212    /// **Parameters:**
213    /// - `query` - The search query
214    /// - `content` - The content to look for the query in
215    /// - `transform` -  A transform closure to run on all query content
216    pub fn transform_query<T>(
217        query: &'a str,
218        content: &'a str,
219        transform: T,
220    ) -> String where T: Fn(&str) -> String {
221        Self::transform(query, content, transform, false)
222    }
223
224    /// Reassembles content, but transforms the non-query content
225    ///
226    /// **Example:**
227    /// ```
228    /// use query_range::QueryRangeItr;
229    ///
230    /// let query = "needle";
231    /// let content = "haystackneedlehaystackneedlehaystack";
232    /// let result = QueryRangeItr::transform_other(query, content, |it| it.to_uppercase());
233    /// assert_eq!(result, "HAYSTACKneedleHAYSTACKneedleHAYSTACK");
234    /// ```
235    ///
236    /// **Parameters:**
237    /// - `query` - The search query
238    /// - `content` - The content to look for the query in
239    /// - `transform` -  A transform closure to run on all non-query content
240    pub fn transform_other<T>(
241        query: &'a str,
242        content: &'a str,
243        transform: T,
244    ) -> String where T: Fn(&str) -> String {
245        Self::transform(query, content, transform, true)
246    }
247
248    /// Reassembles content, but transforms both the query content and the non-query content
249    ///
250    /// **Example:**
251    /// ```
252    /// use query_range::QueryRangeItr;
253    /// use query_range::utility::to_title_case;
254    ///
255    /// let query = "needle";
256    /// let content = "haystackneedlehaystackneedlehaystack";
257    /// let result = QueryRangeItr::transform_all(
258    ///     query,
259    ///     content,
260    ///     |it| it.to_uppercase(),
261    ///     |it| to_title_case(it),
262    /// );
263    /// assert_eq!(result, "HaystackNEEDLEHaystackNEEDLEHaystack");
264    /// ```
265    ///
266    /// **Parameters:**
267    /// - `query` - The search query
268    /// - `content` - The content to look for the query in
269    /// - `transform_query` -  A transform closure to run on all query content
270    /// - `transform_non_query` - If `true`, applies the transform to the non-query content
271    pub fn transform_all<TQ, TNQ>(
272        query: &'a str,
273        content: &'a str,
274        transform_query: TQ,
275        transform_non_query: TNQ,
276    ) -> String
277        where
278            TQ: Fn(&str) -> String,
279            TNQ: Fn(&str) -> String,
280    {
281        let transformed_content = Self::transform(query, content, &transform_query, false);
282        let transformed_query = transform_query(query);
283        let transformed = QueryRangeItr::transform(&transformed_query, &transformed_content, transform_non_query, true);
284        transformed
285    }
286}
287
288// Iterator implementation ----------------------------------------------------------------------- /
289
290impl<'a> Iterator for QueryRangeItr<'a> {
291    type Item = Range<usize>;
292
293    /// Gets next range of the query in the content.
294    fn next(&mut self) -> Option<Self::Item> {
295        if self.inverted {
296            self.next_inverted()
297        } else {
298            self.next_standard()
299        }
300    }
301}
302
303// Tests ----------------------------------------------------------------------------------------- /
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use super::super::utility::to_title_case;
309
310    #[test]
311    fn can_iterate_iter() {
312        let query = "needle";
313        let content = "haystackneedlehaystackneedlehaystack";
314        let mut occurrences = QueryRangeItr::new(query, content);
315        while let Some(next) = occurrences.next() {
316            assert_eq!(String::from(&content[next]), "needle");
317        }
318    }
319
320    #[test]
321    fn can_map_iter() {
322        let query = "needle";
323        let content = "haystackneedlehaystackneedlehaystack";
324        let occurrences = QueryRangeItr::new(query, content);
325        let needles: Vec<String> = occurrences.map(|range| String::from(&content[range])).collect();
326        assert_eq!(needles.len(), 2);
327        needles.iter().for_each(|n| assert_eq!(n, "needle"));
328    }
329
330    #[test]
331    fn can_collect_strings() {
332        let query = "needle";
333        let content = "haystackneedlehaystackneedlehaystack";
334        let mut occurrences = QueryRangeItr::new(query, content);
335        let needles: Vec<String> = occurrences.collect_strings();
336        assert_eq!(needles.len(), 2);
337        needles.iter().for_each(|n| assert_eq!(n, "needle"));
338    }
339
340    #[test]
341    fn can_transform_query() {
342        let query = "needle";
343        let content = "haystackneedlehaystackneedlehaystack";
344        let result = QueryRangeItr::transform_query(query, content, |it| it.to_uppercase());
345        assert_eq!(result, "haystackNEEDLEhaystackNEEDLEhaystack");
346    }
347
348    #[test]
349    fn can_reassemble_string() {
350        let query = "needle";
351        let content = "haystackneedlehaystackneedlehaystack";
352        let result = QueryRangeItr::transform_all(
353            query,
354            content,
355            |it| it.to_uppercase(),
356            |it| to_title_case(it),
357        );
358        assert_eq!(result, "HaystackNEEDLEHaystackNEEDLEHaystack");
359    }
360}