#![forbid(unsafe_code)]
#![forbid(elided_lifetimes_in_paths)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![warn(clippy::all)]
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
use core::fmt;
use core::iter::FusedIterator;
use core::ops::{Deref, Range};
use core::str::Lines;
pub trait LineSpanExt {
fn line_spans(&self) -> LineSpanIter<'_>;
fn find_line_start(&self, index: usize) -> usize;
fn find_line_end(&self, index: usize) -> usize;
fn find_line_range(&self, index: usize) -> Range<usize>;
fn find_next_line_start(&self, index: usize) -> Option<usize>;
fn find_next_line_end(&self, index: usize) -> Option<usize>;
fn find_next_line_range(&self, index: usize) -> Option<Range<usize>>;
fn find_prev_line_start(&self, index: usize) -> Option<usize>;
fn find_prev_line_end(&self, index: usize) -> Option<usize>;
fn find_prev_line_range(&self, index: usize) -> Option<Range<usize>>;
}
impl LineSpanExt for str {
#[inline]
fn line_spans(&self) -> LineSpanIter<'_> {
LineSpanIter::from(self)
}
#[inline]
fn find_line_start(&self, index: usize) -> usize {
self[..index].rfind('\n').map_or(0, |i| i + 1)
}
#[inline]
fn find_line_end(&self, index: usize) -> usize {
let end: usize = self[index..]
.find('\n')
.map_or_else(|| self.len(), |i| index + i);
if (end > 0) && (self.as_bytes()[end - 1] == b'\r') {
end - 1
} else {
end
}
}
#[inline]
fn find_line_range(&self, index: usize) -> Range<usize> {
let start = self.find_line_start(index);
let end = self.find_line_end(index);
start..end
}
#[inline]
fn find_next_line_start(&self, index: usize) -> Option<usize> {
let i = self[index..].find('\n')?;
Some(index + i + 1)
}
#[inline]
fn find_next_line_end(&self, index: usize) -> Option<usize> {
let index = self.find_next_line_start(index)?;
let index = self.find_line_end(index);
Some(index)
}
#[inline]
fn find_next_line_range(&self, index: usize) -> Option<Range<usize>> {
let start = self.find_next_line_start(index)?;
let end = self.find_line_end(start);
Some(start..end)
}
#[inline]
fn find_prev_line_start(&self, index: usize) -> Option<usize> {
let index = self.find_prev_line_end(index)?;
let index = self.find_line_start(index);
Some(index)
}
#[inline]
fn find_prev_line_end(&self, index: usize) -> Option<usize> {
let mut end: usize = self[..index].rfind('\n')?;
if (end > 0) && (self.as_bytes()[end - 1] == b'\r') {
end -= 1;
}
Some(end)
}
#[inline]
fn find_prev_line_range(&self, index: usize) -> Option<Range<usize>> {
let end = self.find_prev_line_end(index)?;
let start = self.find_line_start(end);
Some(start..end)
}
}
#[inline]
pub fn find_line_start(text: &str, index: usize) -> usize {
text.find_line_start(index)
}
pub fn find_line_end(text: &str, index: usize) -> usize {
text.find_line_end(index)
}
#[inline]
pub fn find_line_range(text: &str, index: usize) -> Range<usize> {
text.find_line_range(index)
}
#[inline]
pub fn find_next_line_start(text: &str, index: usize) -> Option<usize> {
text.find_next_line_start(index)
}
#[inline]
pub fn find_next_line_end(text: &str, index: usize) -> Option<usize> {
text.find_next_line_end(index)
}
#[inline]
pub fn find_next_line_range(text: &str, index: usize) -> Option<Range<usize>> {
text.find_next_line_range(index)
}
#[inline]
pub fn find_prev_line_start(text: &str, index: usize) -> Option<usize> {
text.find_prev_line_start(index)
}
#[inline]
pub fn find_prev_line_end(text: &str, index: usize) -> Option<usize> {
text.find_prev_line_end(index)
}
#[inline]
pub fn find_prev_line_range(text: &str, index: usize) -> Option<Range<usize>> {
text.find_prev_line_range(index)
}
#[inline]
pub fn str_to_range(string: &str, substring: &str) -> Option<Range<usize>> {
let str_start = string.as_ptr() as usize;
let sub_start = substring.as_ptr() as usize;
if str_start <= sub_start {
let start = sub_start - str_start;
let end = start + substring.len();
if (sub_start + substring.len()) <= (str_start + string.len()) {
return Some(start..end);
}
}
None
}
#[inline]
pub fn str_to_range_unchecked(string: &str, substring: &str) -> Range<usize> {
let start = (substring.as_ptr() as usize) - (string.as_ptr() as usize);
let end = start + substring.len();
start..end
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct LineSpan<'a> {
text: &'a str,
start: usize,
end: usize,
ending: usize,
}
impl<'a> LineSpan<'a> {
#[inline]
pub fn start(&self) -> usize {
self.start
}
#[inline]
pub fn end(&self) -> usize {
self.end
}
#[inline]
pub fn ending(&self) -> usize {
self.ending
}
#[inline]
pub fn range(&self) -> Range<usize> {
self.start..self.end
}
#[inline]
pub fn range_with_ending(&self) -> Range<usize> {
self.start..self.ending
}
#[inline]
pub fn ending_range(&self) -> Range<usize> {
self.end..self.ending
}
#[inline]
pub fn as_str(&self) -> &'a str {
&self.text[self.range()]
}
#[inline]
pub fn as_str_with_ending(&self) -> &'a str {
&self.text[self.range_with_ending()]
}
#[inline]
pub fn ending_str(&self) -> &'a str {
&self.text[self.ending_range()]
}
}
impl<'a> Deref for LineSpan<'a> {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<'a> From<LineSpan<'a>> for &'a str {
#[inline]
fn from(span: LineSpan<'a>) -> &'a str {
span.as_str()
}
}
impl<'a> From<LineSpan<'a>> for Range<usize> {
#[inline]
fn from(span: LineSpan<'a>) -> Range<usize> {
span.range()
}
}
impl<'a> fmt::Debug for LineSpan<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("LineSpan")
.field("start", &self.start)
.field("end", &self.end)
.field("ending", &self.ending)
.field("line", &self.as_str())
.finish()
}
}
impl<'a> fmt::Display for LineSpan<'a> {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(fmt)
}
}
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct LineSpanIter<'a> {
text: &'a str,
iter: Lines<'a>,
}
impl<'a> LineSpanIter<'a> {
#[inline]
fn from(text: &'a str) -> Self {
Self {
text,
iter: text.lines(),
}
}
}
impl<'a> Iterator for LineSpanIter<'a> {
type Item = LineSpan<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some(line) = self.iter.next() {
let Range { start, end } = str_to_range_unchecked(self.text, line);
let ending = find_next_line_start(self.text, end).unwrap_or(self.text.len());
Some(LineSpan {
text: self.text,
start,
end,
ending,
})
} else {
None
}
}
}
impl<'a> FusedIterator for LineSpanIter<'a> {}
pub trait LineSpans {
fn line_spans(&self) -> LineSpanIter<'_>;
}
impl LineSpans for str {
#[inline]
fn line_spans(&self) -> LineSpanIter<'_> {
LineSpanIter::from(self)
}
}
#[cfg(feature = "alloc")]
impl LineSpans for alloc::string::String {
#[inline]
fn line_spans(&self) -> LineSpanIter<'_> {
LineSpanIter::from(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::{LineSpan, LineSpanExt};
#[test]
fn test_line_spans() {
let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
let mut it = text.line_spans();
assert_eq!(Some(""), it.next().as_deref());
assert_eq!(Some("foo"), it.next().as_deref());
assert_eq!(Some("bar"), it.next().as_deref());
assert_eq!(Some("baz"), it.next().as_deref());
assert_eq!(Some("qux"), it.next().as_deref());
assert_eq!(Some("\r"), it.next().as_deref());
assert_eq!(None, it.next().as_deref());
let mut it = text.line_spans().map(|span| span.range());
assert_eq!(Some(0..0), it.next());
assert_eq!(Some(2..5), it.next());
assert_eq!(Some(6..9), it.next());
assert_eq!(Some(11..14), it.next());
assert_eq!(Some(15..18), it.next());
assert_eq!(Some(20..21), it.next());
assert_eq!(None, it.next());
}
#[test]
fn test_line_spans_vs_lines() {
let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
let mut iter_spans = text.line_spans();
let mut iter_lines = text.lines();
loop {
let span = iter_spans.next();
let line = iter_lines.next();
assert_eq!(span.map(|s| s.as_str()), line);
if span.is_none() {
break;
}
}
}
#[test]
fn test_line_span_ending() {
let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
let lines = [
("", "\r\n"),
("foo", "foo\n"),
("bar", "bar\r\n"),
("baz", "baz\n"),
("qux", "qux\r\n"),
("\r", "\r"),
];
let mut iter = text.line_spans();
for &expected in lines.iter() {
let actual = iter.next();
let actual = actual.map(|span| (span.as_str(), span.as_str_with_ending()));
assert_eq!(Some(expected), actual);
}
assert_eq!(None, iter.next());
}
#[test]
fn lib_example() {
let text = "foo\nbar\r\nbaz";
let spans = [
LineSpan {
text,
start: 0,
end: 3,
ending: 4,
},
LineSpan {
text,
start: 4,
end: 7,
ending: 9,
},
LineSpan {
text,
start: 9,
end: 12,
ending: 12,
},
];
#[rustfmt::skip]
let lines = [
("foo", "foo\n"),
("bar", "bar\r\n"),
("baz", "baz"),
];
assert_eq!(spans.len(), lines.len());
let mut iter = text.line_spans();
for (expected, (line_end, line_ending)) in spans.iter().zip(&lines) {
let actual = iter.next();
assert_eq!(Some(*expected), actual);
let actual = actual.unwrap();
assert_eq!(*line_end, actual.as_str());
assert_eq!(*line_ending, actual.as_str_with_ending());
}
assert_eq!(None, iter.next());
}
}