every_range/
lib.rs

1//! This crate implements an extension to [`Iterator`],
2//! which features an [`every_range`] method
3//! on any [`Iterator`] with an [`Item`] of [`Range<usize>`].
4//!
5//! [`every_range`]: trait.EveryRange.html#method.every_range
6//!
7//! [`Iterator`]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html
8//! [`Item`]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#associatedtype.Item
9//!
10//! [`Range`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html
11//! [`Range<usize>`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html
12//!
13//! [`EveryRangeIter`] iterates over [`Range`]s and "fill in"
14//! missing ranges, i.e. the gap between two consecutive ranges.
15//! The original ranges and the generated ones,
16//! can be distinguished by the [`Included`] and
17//! [`Excluded`] enum variants.
18//!
19//! [`EveryRangeIter`]: struct.EveryRangeIter.html
20//! [`Included`]: enum.EveryRangeKind.html#variant.Included
21//! [`Excluded`]: enum.EveryRangeKind.html#variant.Excluded
22//!
23//! [`EveryRangeIter`] is useful when the ranges being iterated
24//! are related to substrings that later are used to replaced
25//! parts in a string.
26//!
27//! # Example: How does it work?
28//!
29//! ```no_run
30//! use every_range::EveryRange;
31//!
32//! // Lets use text as an example, but it could be anything
33//! let text = "Foo rust-lang.org Bar
34//! Baz crates.io Qux";
35//!
36//! // Get some ranges from somewhere
37//! let ranges = vec![
38//!     4..17,  // "rust-lang.org"
39//!     26..35, // "crates.io"
40//! ];
41//!
42//! // `text.len()` tells `EveryRange` the end, so it knows
43//! // whether to produce an extra range after or not
44//! let iter = ranges.into_iter().every_range(text.len());
45//!
46//! // The input `ranges` result in `Included` every other range is `Excluded`
47//! for (kind, range) in iter {
48//!     println!("{:?} {:>2?} - {:?}", kind, range.clone(), &text[range]);
49//! }
50//! ```
51//!
52//! This will output the following:
53//!
54//! ```text
55//! Excluded  0.. 4 - "Foo "
56//! Included  4..17 - "rust-lang.org"
57//! Excluded 17..26 - " Bar\nBaz "
58//! Included 26..35 - "crates.io"
59//! Excluded 35..39 - " Qux"
60//! ```
61//!
62//! # Example: "Autolink" or HTMLify URLs
63//!
64//! Using [`every_range`] it is easy to collect ranges or
65//! substring into a [`String`].
66//!
67//! [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html
68//!
69//! ```no_run
70//! use std::borrow::Cow;
71//! use every_range::{EveryRange, EveryRangeKind};
72//!
73//! let text = "Foo rust-lang.org Bar
74//! Baz crates.io Qux";
75//!
76//! // For URLs input ranges could be produced by linkify
77//! let ranges = vec![
78//!     4..17,  // "rust-lang.org"
79//!     26..35, // "crates.io"
80//! ];
81//!
82//! let output = ranges
83//!     .into_iter()
84//!     .every_range(text.len())
85//!     .map(|(kind, range)| {
86//!         if kind == EveryRangeKind::Included {
87//!             let url = &text[range];
88//!             format!("<a href=\"{0}\">{0}</a>", url).into()
89//!         } else {
90//!             Cow::Borrowed(&text[range])
91//!         }
92//!     })
93//!     .collect::<Vec<_>>()
94//!     .concat();
95//!
96//! println!("{}", output);
97//! ```
98//!
99//! This will output the following:
100//!
101//! ```text
102//! Foo <a href="rust-lang.org">rust-lang.org</a> Bar
103//! Baz <a href="crates.io">crates.io</a> Qux
104//! ```
105
106#![forbid(unsafe_code)]
107#![deny(missing_docs)]
108#![deny(missing_debug_implementations)]
109#![warn(clippy::all)]
110
111use std::iter::FusedIterator;
112use std::ops::Range;
113
114/// `EveryRangeKind` can be used to distinguish original input
115/// ranges from generates ranges.
116#[derive(PartialEq, Clone, Copy, Debug)]
117pub enum EveryRangeKind {
118    /// `Included` ranges are the ones produces by the inner [`Iterator`].
119    ///
120    /// [`Iterator`]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html
121    Included,
122
123    /// Excluded ranges are the ones generated dynamically by [`EveryRangeIter`].
124    ///
125    /// [`EveryRangeIter`]: struct.EveryRangeIter.html
126    Excluded,
127}
128
129// TODO: EveryRangeIter is not very lenient, consider if `range.start > self.end` and `range.end > self.end` should stop the iterator, instead of panicking
130// TODO: The question is, if so, does it ignore the last range? does it clamp it? does it just return it anyways and stop after?
131
132/// `EveryRangeIter` iterates over [`Range`]s and "fill in"
133/// missing ranges, i.e. the gap between two consecutive ranges.
134/// The original ranges and the generated ones,
135/// can be distinguished by the [`Included`] and
136/// [`Excluded`] enum variants.
137///
138/// [`EveryRangeIter`]: struct.EveryRangeIter.html
139/// [`Included`]: enum.EveryRangeKind.html#variant.Included
140/// [`Excluded`]: enum.EveryRangeKind.html#variant.Excluded
141///
142/// [`Range`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html
143///
144/// # Panics
145///
146/// Currently, `EveryRangeIter` resorts to panicking
147/// in the following conditions. `EveryRangeIter` might
148/// be made more lenient in the future, if the behavior
149/// can be better consistently defined without panicking.
150///
151/// - Panics if [`Range`]s are received out of order.
152/// - Panics if [`Range`]s overlap.
153/// - Panics if any [`Range`] exceeds the `end` of the `EveryRangeIter`.
154#[allow(missing_debug_implementations)]
155pub struct EveryRangeIter<I>
156where
157    I: Iterator<Item = Range<usize>>,
158{
159    index: usize,
160    end: usize,
161    iter: I,
162    next: Option<Range<usize>>,
163}
164
165impl<I> EveryRangeIter<I>
166where
167    I: Iterator<Item = Range<usize>>,
168{
169    /// Create an `EveryRangeIter` with an `iter` and `end`,
170    /// which represents the "end point". Thereby, if `end` is
171    /// greater than the last [`range.end`] then an ending
172    /// [`Excluded`] range is generated, otherwise no additional
173    /// ending range is generated.
174    ///
175    /// [`Excluded`]: enum.EveryRangeKind.html#variant.Excluded
176    ///
177    /// [`range.end`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html#structfield.end
178    #[inline]
179    pub fn new(iter: I, end: usize) -> Self {
180        Self {
181            index: 0,
182            end,
183            iter,
184            next: None,
185        }
186    }
187}
188
189impl<I> Iterator for EveryRangeIter<I>
190where
191    I: Iterator<Item = Range<usize>>,
192{
193    type Item = (EveryRangeKind, Range<usize>);
194
195    #[inline]
196    fn next(&mut self) -> Option<Self::Item> {
197        if let Some(next) = self.next.take() {
198            self.index = next.end;
199
200            Some((EveryRangeKind::Included, next))
201        } else if let Some(next) = self.iter.next() {
202            assert!(self.index <= next.start);
203            assert!(next.end <= self.end);
204            assert!(next.start <= next.end);
205
206            if self.index < next.start {
207                let start = self.index;
208                self.index = next.start;
209                self.next = Some(next);
210
211                Some((EveryRangeKind::Excluded, start..self.index))
212            } else {
213                self.index = next.end;
214
215                Some((EveryRangeKind::Included, next))
216            }
217        } else if self.index < self.end {
218            let start = self.index;
219
220            self.index = self.end;
221
222            Some((EveryRangeKind::Excluded, start..self.end))
223        } else {
224            None
225        }
226    }
227}
228
229impl<I> FusedIterator for EveryRangeIter<I> where I: Iterator<Item = Range<usize>> {}
230
231/// Trait which implements `every_range` to get a `EveryRangeIter`.
232///
233/// *[See `EveryRangeIter` for more information.][`EveryRangeIter`]*
234///
235/// [`every_range`]: trait.EveryRange.html#method.every_range
236/// [`EveryRangeIter`]: struct.EveryRangeIter.html
237pub trait EveryRange: Sized + Iterator<Item = Range<usize>> {
238    /// Create an [`EveryRangeIter`] with `end`, which represents
239    /// the "end point". Thereby, if `end` is greater than the last
240    /// [`range.end`] then an ending [`Excluded`] range is generated,
241    /// otherwise no additional ending range is generated.
242    ///
243    /// *[See `EveryRangeIter` for more information.][`EveryRangeIter`]*
244    ///
245    /// [`EveryRangeIter`]: struct.EveryRangeIter.html
246    /// [`Excluded`]: enum.EveryRangeKind.html#variant.Excluded
247    ///
248    /// [`range.end`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html#structfield.end
249    #[inline]
250    fn every_range(self, end: usize) -> EveryRangeIter<Self> {
251        EveryRangeIter::new(self, end)
252    }
253}
254
255impl<T> EveryRange for T where T: Iterator<Item = Range<usize>> {}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn every_range_matches1() {
263        let text = "Foo12Bar34Baz56";
264
265        use EveryRangeKind::*;
266        let expected = [
267            ((Included, 0..1), "F"),
268            ((Included, 1..2), "o"),
269            ((Included, 2..3), "o"),
270            ((Excluded, 3..5), "12"),
271            ((Included, 5..6), "B"),
272            ((Included, 6..7), "a"),
273            ((Included, 7..8), "r"),
274            ((Excluded, 8..10), "34"),
275            ((Included, 10..11), "B"),
276            ((Included, 11..12), "a"),
277            ((Included, 12..13), "z"),
278            ((Excluded, 13..15), "56"),
279        ];
280
281        let mut iter_actual = text
282            .match_indices(char::is_alphabetic)
283            .map(|(start, part)| {
284                let end = start + part.len();
285                start..end
286            })
287            .every_range(text.len())
288            .map(|(kind, range)| ((kind, range.clone()), &text[range]));
289
290        for expected in expected.iter().cloned() {
291            assert_eq!(Some(expected), iter_actual.next());
292        }
293
294        assert_eq!(None, iter_actual.next());
295    }
296
297    #[test]
298    fn every_range_matches2() {
299        let text = "Foo12Bar34Baz56";
300
301        use EveryRangeKind::*;
302        let expected = [
303            ((Excluded, 0..3), "Foo"),
304            ((Included, 3..4), "1"),
305            ((Included, 4..5), "2"),
306            ((Excluded, 5..8), "Bar"),
307            ((Included, 8..9), "3"),
308            ((Included, 9..10), "4"),
309            ((Excluded, 10..13), "Baz"),
310            ((Included, 13..14), "5"),
311            ((Included, 14..15), "6"),
312        ];
313
314        let mut iter_actual = text
315            .match_indices(char::is_numeric)
316            .map(|(start, part)| {
317                let end = start + part.len();
318                start..end
319            })
320            .every_range(text.len())
321            .map(|(kind, range)| ((kind, range.clone()), &text[range]));
322
323        for expected in expected.iter().cloned() {
324            assert_eq!(Some(expected), iter_actual.next());
325        }
326
327        assert_eq!(None, iter_actual.next());
328    }
329
330    #[test]
331    #[should_panic = "assertion failed: next.end <= self.end"]
332    fn range_start_after_end() {
333        [0..2, 4..6].iter().cloned().every_range(3).for_each(|_| {});
334    }
335
336    #[test]
337    #[should_panic = "assertion failed: next.end <= self.end"]
338    fn range_end_after_end() {
339        [0..2, 4..6].iter().cloned().every_range(5).for_each(|_| {});
340    }
341
342    #[test]
343    #[should_panic = "assertion failed: self.index <= next.start"]
344    fn range_start_after_index() {
345        [0..4, 2..6].iter().cloned().every_range(5).for_each(|_| {});
346    }
347
348    #[test]
349    #[should_panic = "assertion failed: self.index <= next.start"]
350    fn ranges_out_of_order1() {
351        [4..6, 0..2, 8..10]
352            .iter()
353            .cloned()
354            .every_range(20)
355            .for_each(|_| {});
356    }
357
358    #[test]
359    #[should_panic = "assertion failed: self.index <= next.start"]
360    fn ranges_out_of_order2() {
361        [8..10, 0..2, 4..6]
362            .iter()
363            .cloned()
364            .every_range(20)
365            .for_each(|_| {});
366    }
367
368    #[test]
369    #[should_panic = "assertion failed: self.index <= next.start"]
370    fn ranges_out_of_order3() {
371        [0..2, 8..10, 4..6]
372            .iter()
373            .cloned()
374            .every_range(20)
375            .for_each(|_| {});
376    }
377
378    #[test]
379    #[should_panic = "assertion failed: self.index <= next.start"]
380    fn ranges_out_of_order4() {
381        [4..6, 8..10, 0..2]
382            .iter()
383            .cloned()
384            .every_range(20)
385            .for_each(|_| {});
386    }
387
388    #[test]
389    #[should_panic = "assertion failed: self.index <= next.start"]
390    fn ranges_out_of_order5() {
391        [8..10, 4..6, 0..2]
392            .iter()
393            .cloned()
394            .every_range(20)
395            .for_each(|_| {});
396    }
397}