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]::<[LineCol]>()</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}