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}