char_positions/
lib.rs

1//! Similar to the standard library's [`.char_indicies()`], but instead of only
2//! producing the start byte position. This library implements [`.char_positions()`],
3//! which can produce any combination of line, column, start byte, and end byte position.
4//!
5//! As an example use
6//! <code>text.[char_positions]::&lt;[LineCol]&gt;()</code>
7//! to get the line and column of each [`char`].
8//! Use [`LineColByteRange`] to additionally get the byte range,
9//! or just [`Line`] to simply get the line number.
10//!
11//! ## Example
12//!
13//! ```
14//! use char_positions::{CharPositionsExt, LineCol};
15//!
16//! let text = "Hello šŸ‘‹\nWorld šŸŒ\nšŸ¦€šŸ¦€";
17//!
18//! for (LineCol(line, col), c) in text.char_positions() {
19//!     println!("[Ln {line}, Col {col}] {c:?}");
20//! }
21//! ```
22//!
23//! Which will output:
24//!
25//! ```text
26//! [Ln 1, Col 1] 'H'
27//! [Ln 1, Col 2] 'e'
28//! [Ln 1, Col 3] 'l'
29//! [Ln 1, Col 4] 'l'
30//! [Ln 1, Col 5] 'o'
31//! [Ln 1, Col 6] ' '
32//! [Ln 1, Col 7] 'šŸ‘‹'
33//! [Ln 1, Col 8] '\n'
34//! [Ln 2, Col 1] 'W'
35//! [Ln 2, Col 2] 'o'
36//! [Ln 2, Col 3] 'r'
37//! [Ln 2, Col 4] 'l'
38//! [Ln 2, Col 5] 'd'
39//! [Ln 2, Col 6] ' '
40//! [Ln 2, Col 7] 'šŸŒ'
41//! [Ln 2, Col 8] '\n'
42//! [Ln 3, Col 1] 'šŸ¦€'
43//! [Ln 3, Col 2] 'šŸ¦€'
44//! ```
45//!
46//! ## Supported
47//!
48//! | `.char_positions::<T>()` | Produces |
49//! |:---|---|
50//! | [`usize`] | Start byte index (same as [`.char_indicies()`]) |
51//! | [`std::ops::Range<usize>`] | Start byte and end byte index, i.e. `&text[range]` is the `char` |
52//! | [`LineColByteRange`] | Line number, column number, and byte range |
53//! | [`LineCol`] | Line number and column number |
54//! | [`Line`] | Line number |
55//! | [`Col`] | Column number |
56//! | [`ByteRange`] | Same as using [`std::ops::Range<usize>`] |
57//! | [`ByteStart`] | Start byte index (same as [`.char_indicies()`]) |
58//! | [`ByteEnd`] | End byte index |
59//! | _Tuples are also supported, e.g._ | |
60//! | <code>([Line],)</code> | _Produces the tuple_ |
61//! | <code>([Line], [Col])</code> | _Produces the tuple_ |
62//! | <code>([Line], [Col], [ByteStart], [ByteEnd])</code> | _Produces the tuple_ |
63//! | _etc._ | |
64//!
65//! ## Example - `LineColByteRange`
66//!
67//! ```
68//! use char_positions::{CharPositionsExt, LineColByteRange};
69//!
70//! let text = "Hello šŸ‘‹\nWorld šŸŒ\nšŸ¦€šŸ¦€";
71//!
72//! let mut iter = text
73//!     .char_positions::<LineColByteRange>()
74//!     .map(|(LineColByteRange(line, col, range), c)| (line, col, range, c));
75//!
76//! assert_eq!(iter.next(), Some((1, 1, 0..1, 'H')));
77//! assert_eq!(iter.next(), Some((1, 2, 1..2, 'e')));
78//! assert_eq!(iter.next(), Some((1, 3, 2..3, 'l')));
79//! assert_eq!(iter.next(), Some((1, 4, 3..4, 'l')));
80//! assert_eq!(iter.next(), Some((1, 5, 4..5, 'o')));
81//! assert_eq!(iter.next(), Some((1, 6, 5..6, ' ')));
82//! assert_eq!(iter.next(), Some((1, 7, 6..10, 'šŸ‘‹')));
83//! assert_eq!(iter.next(), Some((1, 8, 10..11, '\n')));
84//! assert_eq!(iter.next(), Some((2, 1, 11..12, 'W')));
85//! assert_eq!(iter.next(), Some((2, 2, 12..13, 'o')));
86//! assert_eq!(iter.next(), Some((2, 3, 13..14, 'r')));
87//! assert_eq!(iter.next(), Some((2, 4, 14..15, 'l')));
88//! assert_eq!(iter.next(), Some((2, 5, 15..16, 'd')));
89//! assert_eq!(iter.next(), Some((2, 6, 16..17, ' ')));
90//! assert_eq!(iter.next(), Some((2, 7, 17..21, 'šŸŒ')));
91//! assert_eq!(iter.next(), Some((2, 8, 21..22, '\n')));
92//! assert_eq!(iter.next(), Some((3, 1, 22..26, 'šŸ¦€')));
93//! assert_eq!(iter.next(), Some((3, 2, 26..30, 'šŸ¦€')));
94//! assert_eq!(iter.next(), None);
95//! ```
96//!
97// Manually linking everything, as `cargo rdme` does not support intralinks
98//!
99//! [`.char_positions()`]: https://docs.rs/char-positions/*/char_positions/trait.CharPositionsExt.html#tymethod.char_positions
100//! [char_positions]: https://docs.rs/char-positions/*/char_positions/trait.CharPositionsExt.html#tymethod.char_positions
101//!
102//! [`LineColByteRange`]: https://docs.rs/char-positions/*/char_positions/struct.LineColByteRange.html
103//! [`LineCol`]: https://docs.rs/char-positions/*/char_positions/struct.LineCol.html
104//! [`Line`]: https://docs.rs/char-positions/*/char_positions/struct.Line.html
105//! [`Col`]: https://docs.rs/char-positions/*/char_positions/struct.Line.html
106//! [`ByteRange`]: https://docs.rs/char-positions/*/char_positions/struct.ByteRange.html
107//! [`ByteStart`]: https://docs.rs/char-positions/*/char_positions/struct.ByteStart.html
108//! [`ByteEnd`]: https://docs.rs/char-positions/*/char_positions/struct.ByteEnd.html
109//!
110//! [LineColByteRange]: https://docs.rs/char-positions/*/char_positions/struct.LineColByteRange.html
111//! [LineCol]: https://docs.rs/char-positions/*/char_positions/struct.LineCol.html
112//! [Line]: https://docs.rs/char-positions/*/char_positions/struct.Line.html
113//! [Col]: https://docs.rs/char-positions/*/char_positions/struct.Col.html
114//! [ByteStart]: https://docs.rs/char-positions/*/char_positions/struct.ByteStart.html
115//! [ByteEnd]: https://docs.rs/char-positions/*/char_positions/struct.ByteEnd.html
116//!
117//! [`.char_indicies()`]: https://doc.rust-lang.org/std/primitive.str.html#method.char_indices
118//!
119//! [`char`]: https://doc.rust-lang.org/std/primitive.char.html
120//! [`usize`]: https://doc.rust-lang.org/std/primitive.usize.html
121//! [`std::ops::Range<usize>`]: https://doc.rust-lang.org/std/ops/struct.Range.html
122
123#![no_std]
124#![forbid(unsafe_code)]
125#![forbid(elided_lifetimes_in_paths)]
126
127use core::iter::FusedIterator;
128use core::marker::PhantomData;
129use core::ops::Range;
130
131use char_ranges::{CharRanges, CharRangesExt};
132
133pub trait CharPositionsExt {
134    /// Returns an iterator over [`char`]s and their positions.
135    ///
136    /// See examples in the [crate root](crate).
137    fn char_positions<T>(&self) -> CharPositions<'_, T>
138    where
139        LineColByteRange: Into<T>;
140}
141
142impl CharPositionsExt for str {
143    #[inline]
144    fn char_positions<T>(&self) -> CharPositions<'_, T>
145    where
146        LineColByteRange: Into<T>,
147    {
148        CharPositions::new(self)
149    }
150}
151
152/// An iterator over [`char`]s and their positions.
153///
154/// Note: Cloning this iterator is essentially a copy.
155///
156/// See examples in the [crate root](crate).
157#[derive(Clone, Debug)]
158pub struct CharPositions<'a, T> {
159    iter: CharRanges<'a>,
160    pos: LineCol,
161    phantom: PhantomData<T>,
162}
163
164impl<'a, T> CharPositions<'a, T> {
165    #[inline]
166    fn new(s: &'a str) -> Self {
167        Self {
168            iter: s.char_ranges(),
169            pos: LineCol::START,
170            phantom: PhantomData,
171        }
172    }
173
174    /// Returns the remaining substring.
175    #[inline]
176    pub fn as_str(&self) -> &'a str {
177        self.iter.as_str()
178    }
179}
180
181impl<T> Iterator for CharPositions<'_, T>
182where
183    LineColByteRange: Into<T>,
184{
185    type Item = (T, char);
186
187    fn next(&mut self) -> Option<Self::Item> {
188        let (r, c) = self.iter.next()?;
189        let pos = LineColByteRange(self.pos.0, self.pos.1, r);
190
191        match c {
192            '\n' => {
193                self.pos.0 += 1;
194                self.pos.1 = 1;
195            }
196            _ => {
197                self.pos.1 += 1;
198            }
199        }
200
201        Some((pos.into(), c))
202    }
203}
204
205impl<T> FusedIterator for CharPositions<'_, T> where Self: Iterator {}
206
207/// `Line(line)`
208#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
209pub struct Line(
210    /// 1-indexed line.
211    pub usize,
212);
213
214/// `Col(col)`
215#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
216pub struct Col(
217    /// 1-indexed column.
218    pub usize,
219);
220
221/// `ByteStart(byte_start)`
222#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
223pub struct ByteStart(
224    /// The start (inclusive) byte positions.
225    pub usize,
226);
227
228/// `ByteEnd(byte_end)`
229#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
230pub struct ByteEnd(
231    /// The end (exclusive) byte position.
232    pub usize,
233);
234
235/// `ByteRange(byte_start..byte_end)`
236#[derive(PartialEq, Eq, Hash, Clone, Debug)]
237pub struct ByteRange(
238    /// The start (inclusive) and end (exclusive) byte positions.
239    pub Range<usize>,
240);
241
242/// `LineCol(line, col)`
243#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
244pub struct LineCol(
245    /// 1-indexed line.
246    pub usize,
247    /// 1-indexed column.
248    pub usize,
249);
250
251impl LineCol {
252    const START: Self = Self(1, 1);
253
254    #[inline]
255    pub const fn line(&self) -> usize {
256        self.0
257    }
258
259    #[inline]
260    pub const fn column(&self) -> usize {
261        self.1
262    }
263}
264
265/// `LineColByte(line, col, byte_start)`
266#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
267pub struct LineColByte(
268    /// 1-indexed line.
269    pub usize,
270    /// 1-indexed column.
271    pub usize,
272    /// The start (inclusive) byte positions.
273    pub usize,
274);
275
276impl LineColByte {
277    #[inline]
278    pub const fn line(&self) -> usize {
279        self.0
280    }
281
282    #[inline]
283    pub const fn column(&self) -> usize {
284        self.1
285    }
286
287    /// Inclusive.
288    #[doc(alias = "byte")]
289    #[inline]
290    pub const fn byte_start(&self) -> usize {
291        self.2
292    }
293}
294
295/// `LineColByteRange(line, col, byte_start..byte_end)`
296#[derive(PartialEq, Eq, Hash, Clone, Debug)]
297pub struct LineColByteRange(
298    /// 1-indexed line.
299    pub usize,
300    /// 1-indexed column.
301    pub usize,
302    /// The start (inclusive) and end (exclusive) byte positions.
303    pub Range<usize>,
304);
305
306impl LineColByteRange {
307    #[inline]
308    pub const fn line(&self) -> usize {
309        self.0
310    }
311
312    #[inline]
313    pub const fn column(&self) -> usize {
314        self.1
315    }
316
317    /// Inclusive.
318    #[inline]
319    pub const fn byte_start(&self) -> usize {
320        self.2.start
321    }
322
323    /// Exclusive.
324    #[inline]
325    pub const fn byte_end(&self) -> usize {
326        self.2.end
327    }
328
329    #[inline]
330    pub const fn byte_range(&self) -> Range<usize> {
331        self.2.start..self.2.end
332    }
333}
334
335impl From<LineCol> for Line {
336    #[inline]
337    fn from(pos: LineCol) -> Self {
338        Self(pos.0)
339    }
340}
341
342impl From<LineCol> for Col {
343    #[inline]
344    fn from(pos: LineCol) -> Self {
345        Self(pos.1)
346    }
347}
348
349impl From<LineColByte> for Line {
350    #[inline]
351    fn from(pos: LineColByte) -> Self {
352        Self(pos.0)
353    }
354}
355
356impl From<LineColByte> for Col {
357    #[inline]
358    fn from(pos: LineColByte) -> Self {
359        Self(pos.1)
360    }
361}
362
363impl From<LineColByte> for LineCol {
364    #[inline]
365    fn from(pos: LineColByte) -> Self {
366        Self(pos.0, pos.1)
367    }
368}
369
370impl From<LineColByteRange> for Line {
371    #[inline]
372    fn from(pos: LineColByteRange) -> Self {
373        Self(pos.0)
374    }
375}
376
377impl From<LineColByteRange> for Col {
378    #[inline]
379    fn from(pos: LineColByteRange) -> Self {
380        Self(pos.1)
381    }
382}
383
384impl From<LineColByteRange> for ByteStart {
385    #[inline]
386    fn from(pos: LineColByteRange) -> Self {
387        Self(pos.2.start)
388    }
389}
390
391impl From<LineColByteRange> for ByteEnd {
392    #[inline]
393    fn from(pos: LineColByteRange) -> Self {
394        Self(pos.2.end)
395    }
396}
397
398impl From<LineColByteRange> for ByteRange {
399    #[inline]
400    fn from(pos: LineColByteRange) -> Self {
401        Self(pos.2)
402    }
403}
404
405impl From<LineColByteRange> for LineCol {
406    #[inline]
407    fn from(pos: LineColByteRange) -> Self {
408        Self(pos.0, pos.1)
409    }
410}
411
412impl From<LineColByteRange> for LineColByte {
413    #[inline]
414    fn from(pos: LineColByteRange) -> Self {
415        Self(pos.0, pos.1, pos.2.start)
416    }
417}
418
419impl From<LineColByteRange> for usize {
420    #[inline]
421    fn from(pos: LineColByteRange) -> Self {
422        pos.2.start
423    }
424}
425
426impl From<LineColByteRange> for Range<usize> {
427    #[inline]
428    fn from(pos: LineColByteRange) -> Self {
429        pos.2
430    }
431}
432
433impl From<ByteStart> for usize {
434    #[inline]
435    fn from(ByteStart(pos): ByteStart) -> Self {
436        pos
437    }
438}
439
440impl From<ByteEnd> for usize {
441    #[inline]
442    fn from(ByteEnd(pos): ByteEnd) -> Self {
443        pos
444    }
445}
446
447impl From<ByteRange> for Range<usize> {
448    #[inline]
449    fn from(ByteRange(r): ByteRange) -> Self {
450        r
451    }
452}
453
454impl<A> From<LineColByteRange> for (A,)
455where
456    LineColByteRange: Into<A>,
457{
458    #[inline]
459    fn from(pos: LineColByteRange) -> Self {
460        (pos.into(),)
461    }
462}
463
464impl<A, B> From<LineColByteRange> for (A, B)
465where
466    LineColByteRange: Into<A>,
467    LineColByteRange: Into<B>,
468{
469    #[inline]
470    fn from(pos: LineColByteRange) -> Self {
471        (pos.clone().into(), pos.into())
472    }
473}
474
475impl<A, B, C> From<LineColByteRange> for (A, B, C)
476where
477    LineColByteRange: Into<A>,
478    LineColByteRange: Into<B>,
479    LineColByteRange: Into<C>,
480{
481    #[inline]
482    fn from(pos: LineColByteRange) -> Self {
483        (pos.clone().into(), pos.clone().into(), pos.into())
484    }
485}
486
487impl<A, B, C, D> From<LineColByteRange> for (A, B, C, D)
488where
489    LineColByteRange: Into<A>,
490    LineColByteRange: Into<B>,
491    LineColByteRange: Into<C>,
492    LineColByteRange: Into<D>,
493{
494    #[inline]
495    fn from(pos: LineColByteRange) -> Self {
496        (
497            pos.clone().into(),
498            pos.clone().into(),
499            pos.clone().into(),
500            pos.into(),
501        )
502    }
503}
504
505impl<A, B, C, D, E> From<LineColByteRange> for (A, B, C, D, E)
506where
507    LineColByteRange: Into<A>,
508    LineColByteRange: Into<B>,
509    LineColByteRange: Into<C>,
510    LineColByteRange: Into<D>,
511    LineColByteRange: Into<E>,
512{
513    #[inline]
514    fn from(pos: LineColByteRange) -> Self {
515        (
516            pos.clone().into(),
517            pos.clone().into(),
518            pos.clone().into(),
519            pos.clone().into(),
520            pos.into(),
521        )
522    }
523}
524
525impl<A, B, C, D, E, F> From<LineColByteRange> for (A, B, C, D, E, F)
526where
527    LineColByteRange: Into<A>,
528    LineColByteRange: Into<B>,
529    LineColByteRange: Into<C>,
530    LineColByteRange: Into<D>,
531    LineColByteRange: Into<E>,
532    LineColByteRange: Into<F>,
533{
534    #[inline]
535    fn from(pos: LineColByteRange) -> Self {
536        (
537            pos.clone().into(),
538            pos.clone().into(),
539            pos.clone().into(),
540            pos.clone().into(),
541            pos.clone().into(),
542            pos.into(),
543        )
544    }
545}