miette/
source_impls.rs

1/*!
2Default trait implementations for [`SourceCode`].
3*/
4use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
5
6use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
7
8fn context_info<'a>(
9    input: &'a [u8],
10    span: &SourceSpan,
11    context_lines_before: usize,
12    context_lines_after: usize,
13) -> Result<MietteSpanContents<'a>, MietteError> {
14    let mut offset = 0usize;
15    let mut line_count = 0usize;
16    let mut start_line = 0usize;
17    let mut start_column = 0usize;
18    let mut before_lines_starts = VecDeque::new();
19    let mut current_line_start = 0usize;
20    let mut end_lines = 0usize;
21    let mut post_span = false;
22    let mut post_span_got_newline = false;
23    let mut iter = input.iter().copied().peekable();
24    while let Some(char) = iter.next() {
25        if matches!(char, b'\r' | b'\n') {
26            line_count += 1;
27            if char == b'\r' && iter.next_if_eq(&b'\n').is_some() {
28                offset += 1;
29            }
30            if offset < span.offset() {
31                // We're before the start of the span.
32                start_column = 0;
33                before_lines_starts.push_back(current_line_start);
34                if before_lines_starts.len() > context_lines_before {
35                    start_line += 1;
36                    before_lines_starts.pop_front();
37                }
38            } else if offset >= span.offset() + span.len().saturating_sub(1) {
39                // We're after the end of the span, but haven't necessarily
40                // started collecting end lines yet (we might still be
41                // collecting context lines).
42                if post_span {
43                    start_column = 0;
44                    if post_span_got_newline {
45                        end_lines += 1;
46                    } else {
47                        post_span_got_newline = true;
48                    }
49                    if end_lines >= context_lines_after {
50                        offset += 1;
51                        break;
52                    }
53                }
54            }
55            current_line_start = offset + 1;
56        } else if offset < span.offset() {
57            start_column += 1;
58        }
59
60        if offset >= (span.offset() + span.len()).saturating_sub(1) {
61            post_span = true;
62            if end_lines >= context_lines_after {
63                offset += 1;
64                break;
65            }
66        }
67
68        offset += 1;
69    }
70
71    if offset >= (span.offset() + span.len()).saturating_sub(1) {
72        let starting_offset = before_lines_starts.front().copied().unwrap_or_else(|| {
73            if context_lines_before == 0 {
74                span.offset()
75            } else {
76                0
77            }
78        });
79        Ok(MietteSpanContents::new(
80            &input[starting_offset..offset],
81            (starting_offset, offset - starting_offset).into(),
82            start_line,
83            if context_lines_before == 0 {
84                start_column
85            } else {
86                0
87            },
88            line_count,
89        ))
90    } else {
91        Err(MietteError::OutOfBounds)
92    }
93}
94
95impl SourceCode for [u8] {
96    fn read_span<'a>(
97        &'a self,
98        span: &SourceSpan,
99        context_lines_before: usize,
100        context_lines_after: usize,
101    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
102        let contents = context_info(self, span, context_lines_before, context_lines_after)?;
103        Ok(Box::new(contents))
104    }
105}
106
107impl<'src> SourceCode for &'src [u8] {
108    fn read_span<'a>(
109        &'a self,
110        span: &SourceSpan,
111        context_lines_before: usize,
112        context_lines_after: usize,
113    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
114        <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
115    }
116}
117
118impl SourceCode for Vec<u8> {
119    fn read_span<'a>(
120        &'a self,
121        span: &SourceSpan,
122        context_lines_before: usize,
123        context_lines_after: usize,
124    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
125        <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
126    }
127}
128
129impl SourceCode for str {
130    fn read_span<'a>(
131        &'a self,
132        span: &SourceSpan,
133        context_lines_before: usize,
134        context_lines_after: usize,
135    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
136        <[u8] as SourceCode>::read_span(
137            self.as_bytes(),
138            span,
139            context_lines_before,
140            context_lines_after,
141        )
142    }
143}
144
145/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
146impl<'s> SourceCode for &'s str {
147    fn read_span<'a>(
148        &'a self,
149        span: &SourceSpan,
150        context_lines_before: usize,
151        context_lines_after: usize,
152    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
153        <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
154    }
155}
156
157impl SourceCode for String {
158    fn read_span<'a>(
159        &'a self,
160        span: &SourceSpan,
161        context_lines_before: usize,
162        context_lines_after: usize,
163    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
164        <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
165    }
166}
167
168impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
169    fn read_span<'a>(
170        &'a self,
171        span: &SourceSpan,
172        context_lines_before: usize,
173        context_lines_after: usize,
174    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
175        self.as_ref()
176            .read_span(span, context_lines_before, context_lines_after)
177    }
178}
179
180impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
181where
182    // The minimal bounds are used here.
183    // `T::Owned` need not be
184    // `SourceCode`, because `&T`
185    // can always be obtained from
186    // `Cow<'_, T>`.
187    T::Owned: Debug + Send + Sync,
188{
189    fn read_span<'a>(
190        &'a self,
191        span: &SourceSpan,
192        context_lines_before: usize,
193        context_lines_after: usize,
194    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
195        self.as_ref()
196            .read_span(span, context_lines_before, context_lines_after)
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn basic() -> Result<(), MietteError> {
206        let src = String::from("foo\n");
207        let contents = src.read_span(&(0, 4).into(), 0, 0)?;
208        assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
209        assert_eq!(0, contents.line());
210        assert_eq!(0, contents.column());
211        Ok(())
212    }
213
214    #[test]
215    fn shifted() -> Result<(), MietteError> {
216        let src = String::from("foobar");
217        let contents = src.read_span(&(3, 3).into(), 1, 1)?;
218        assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
219        assert_eq!(0, contents.line());
220        assert_eq!(0, contents.column());
221        Ok(())
222    }
223
224    #[test]
225    fn middle() -> Result<(), MietteError> {
226        let src = String::from("foo\nbar\nbaz\n");
227        let contents = src.read_span(&(4, 4).into(), 0, 0)?;
228        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
229        assert_eq!(1, contents.line());
230        assert_eq!(0, contents.column());
231        Ok(())
232    }
233
234    #[test]
235    fn middle_of_line() -> Result<(), MietteError> {
236        let src = String::from("foo\nbarbar\nbaz\n");
237        let contents = src.read_span(&(7, 4).into(), 0, 0)?;
238        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
239        assert_eq!(1, contents.line());
240        assert_eq!(3, contents.column());
241        Ok(())
242    }
243
244    #[test]
245    fn with_crlf() -> Result<(), MietteError> {
246        let src = String::from("foo\r\nbar\r\nbaz\r\n");
247        let contents = src.read_span(&(5, 5).into(), 0, 0)?;
248        assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
249        assert_eq!(1, contents.line());
250        assert_eq!(0, contents.column());
251        Ok(())
252    }
253
254    #[test]
255    fn with_context() -> Result<(), MietteError> {
256        let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
257        let contents = src.read_span(&(8, 3).into(), 1, 1)?;
258        assert_eq!(
259            "foo\nbar\nbaz\n",
260            std::str::from_utf8(contents.data()).unwrap()
261        );
262        assert_eq!(1, contents.line());
263        assert_eq!(0, contents.column());
264        Ok(())
265    }
266
267    #[test]
268    fn multiline_with_context() -> Result<(), MietteError> {
269        let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
270        let contents = src.read_span(&(9, 11).into(), 1, 1)?;
271        assert_eq!(
272            "\nfoo\nbar\nbaz\n\n",
273            std::str::from_utf8(contents.data()).unwrap()
274        );
275        assert_eq!(2, contents.line());
276        assert_eq!(0, contents.column());
277        let span: SourceSpan = (8, 14).into();
278        assert_eq!(&span, contents.span());
279        Ok(())
280    }
281
282    #[test]
283    fn multiline_with_context_line_start() -> Result<(), MietteError> {
284        let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
285        let contents = src.read_span(&(2, 0).into(), 2, 2)?;
286        assert_eq!(
287            "one\ntwo\n\n",
288            std::str::from_utf8(contents.data()).unwrap()
289        );
290        assert_eq!(0, contents.line());
291        assert_eq!(0, contents.column());
292        let span: SourceSpan = (0, 9).into();
293        assert_eq!(&span, contents.span());
294        Ok(())
295    }
296}