use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
fn context_info<'a>(
input: &'a [u8],
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<MietteSpanContents<'a>, MietteError> {
let mut offset = 0usize;
let mut line_count = 0usize;
let mut start_line = 0usize;
let mut start_column = 0usize;
let mut before_lines_starts = VecDeque::new();
let mut current_line_start = 0usize;
let mut end_lines = 0usize;
let mut post_span = false;
let mut post_span_got_newline = false;
let mut iter = input.iter().copied().peekable();
while let Some(char) = iter.next() {
if matches!(char, b'\r' | b'\n') {
line_count += 1;
if char == b'\r' && iter.next_if_eq(&b'\n').is_some() {
offset += 1;
}
if offset < span.offset() {
start_column = 0;
before_lines_starts.push_back(current_line_start);
if before_lines_starts.len() > context_lines_before {
start_line += 1;
before_lines_starts.pop_front();
}
} else if offset >= span.offset() + span.len().saturating_sub(1) {
if post_span {
start_column = 0;
if post_span_got_newline {
end_lines += 1;
} else {
post_span_got_newline = true;
}
if end_lines >= context_lines_after {
offset += 1;
break;
}
}
}
current_line_start = offset + 1;
} else if offset < span.offset() {
start_column += 1;
}
if offset >= (span.offset() + span.len()).saturating_sub(1) {
post_span = true;
if end_lines >= context_lines_after {
offset += 1;
break;
}
}
offset += 1;
}
if offset >= (span.offset() + span.len()).saturating_sub(1) {
let starting_offset = before_lines_starts.front().copied().unwrap_or_else(|| {
if context_lines_before == 0 {
span.offset()
} else {
0
}
});
Ok(MietteSpanContents::new(
&input[starting_offset..offset],
(starting_offset, offset - starting_offset).into(),
start_line,
if context_lines_before == 0 {
start_column
} else {
0
},
line_count,
))
} else {
Err(MietteError::OutOfBounds)
}
}
impl SourceCode for [u8] {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
let contents = context_info(self, span, context_lines_before, context_lines_after)?;
Ok(Box::new(contents))
}
}
impl<'src> SourceCode for &'src [u8] {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
}
impl SourceCode for Vec<u8> {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
}
impl SourceCode for str {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(
self.as_bytes(),
span,
context_lines_before,
context_lines_after,
)
}
}
impl<'s> SourceCode for &'s str {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
}
impl SourceCode for String {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
}
impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
self.as_ref()
.read_span(span, context_lines_before, context_lines_after)
}
}
impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
where
T::Owned: Debug + Send + Sync,
{
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
self.as_ref()
.read_span(span, context_lines_before, context_lines_after)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() -> Result<(), MietteError> {
let src = String::from("foo\n");
let contents = src.read_span(&(0, 4).into(), 0, 0)?;
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());
Ok(())
}
#[test]
fn shifted() -> Result<(), MietteError> {
let src = String::from("foobar");
let contents = src.read_span(&(3, 3).into(), 1, 1)?;
assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());
Ok(())
}
#[test]
fn middle() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(4, 4).into(), 0, 0)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
Ok(())
}
#[test]
fn middle_of_line() -> Result<(), MietteError> {
let src = String::from("foo\nbarbar\nbaz\n");
let contents = src.read_span(&(7, 4).into(), 0, 0)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(3, contents.column());
Ok(())
}
#[test]
fn with_crlf() -> Result<(), MietteError> {
let src = String::from("foo\r\nbar\r\nbaz\r\n");
let contents = src.read_span(&(5, 5).into(), 0, 0)?;
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
Ok(())
}
#[test]
fn with_context() -> Result<(), MietteError> {
let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
let contents = src.read_span(&(8, 3).into(), 1, 1)?;
assert_eq!(
"foo\nbar\nbaz\n",
std::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
Ok(())
}
#[test]
fn multiline_with_context() -> Result<(), MietteError> {
let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
let contents = src.read_span(&(9, 11).into(), 1, 1)?;
assert_eq!(
"\nfoo\nbar\nbaz\n\n",
std::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(2, contents.line());
assert_eq!(0, contents.column());
let span: SourceSpan = (8, 14).into();
assert_eq!(&span, contents.span());
Ok(())
}
#[test]
fn multiline_with_context_line_start() -> Result<(), MietteError> {
let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
let contents = src.read_span(&(2, 0).into(), 2, 2)?;
assert_eq!(
"one\ntwo\n\n",
std::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());
let span: SourceSpan = (0, 9).into();
assert_eq!(&span, contents.span());
Ok(())
}
}