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
73            .front()
74            .copied()
75            .unwrap_or_else(|| if context_lines_before == 0 { span.offset() } else { 0 });
76        Ok(MietteSpanContents::new(
77            &input[starting_offset..offset],
78            (starting_offset, offset - starting_offset).into(),
79            start_line,
80            if context_lines_before == 0 { start_column } else { 0 },
81            line_count,
82        ))
83    } else {
84        Err(MietteError::OutOfBounds)
85    }
86}
87
88impl SourceCode for [u8] {
89    fn read_span<'a>(
90        &'a self,
91        span: &SourceSpan,
92        context_lines_before: usize,
93        context_lines_after: usize,
94    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
95        let contents = context_info(self, span, context_lines_before, context_lines_after)?;
96        Ok(Box::new(contents))
97    }
98}
99
100impl SourceCode for &[u8] {
101    fn read_span<'a>(
102        &'a self,
103        span: &SourceSpan,
104        context_lines_before: usize,
105        context_lines_after: usize,
106    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
107        <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
108    }
109}
110
111impl SourceCode for Vec<u8> {
112    fn read_span<'a>(
113        &'a self,
114        span: &SourceSpan,
115        context_lines_before: usize,
116        context_lines_after: usize,
117    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
118        <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
119    }
120}
121
122impl SourceCode for str {
123    fn read_span<'a>(
124        &'a self,
125        span: &SourceSpan,
126        context_lines_before: usize,
127        context_lines_after: usize,
128    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
129        <[u8] as SourceCode>::read_span(
130            self.as_bytes(),
131            span,
132            context_lines_before,
133            context_lines_after,
134        )
135    }
136}
137
138/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
139impl SourceCode for &str {
140    fn read_span<'a>(
141        &'a self,
142        span: &SourceSpan,
143        context_lines_before: usize,
144        context_lines_after: usize,
145    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
146        <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
147    }
148}
149
150impl SourceCode for String {
151    fn read_span<'a>(
152        &'a self,
153        span: &SourceSpan,
154        context_lines_before: usize,
155        context_lines_after: usize,
156    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
157        <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
158    }
159}
160
161impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
162    fn read_span<'a>(
163        &'a self,
164        span: &SourceSpan,
165        context_lines_before: usize,
166        context_lines_after: usize,
167    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
168        self.as_ref().read_span(span, context_lines_before, context_lines_after)
169    }
170}
171
172impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
173where
174    // The minimal bounds are used here.
175    // `T::Owned` need not be
176    // `SourceCode`, because `&T`
177    // can always be obtained from
178    // `Cow<'_, T>`.
179    T::Owned: Debug + Send + Sync,
180{
181    fn read_span<'a>(
182        &'a self,
183        span: &SourceSpan,
184        context_lines_before: usize,
185        context_lines_after: usize,
186    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
187        self.as_ref().read_span(span, context_lines_before, context_lines_after)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn basic() -> Result<(), MietteError> {
197        let src = String::from("foo\n");
198        let contents = src.read_span(&(0, 4).into(), 0, 0)?;
199        assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
200        assert_eq!(0, contents.line());
201        assert_eq!(0, contents.column());
202        Ok(())
203    }
204
205    #[test]
206    fn shifted() -> Result<(), MietteError> {
207        let src = String::from("foobar");
208        let contents = src.read_span(&(3, 3).into(), 1, 1)?;
209        assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
210        assert_eq!(0, contents.line());
211        assert_eq!(0, contents.column());
212        Ok(())
213    }
214
215    #[test]
216    fn middle() -> Result<(), MietteError> {
217        let src = String::from("foo\nbar\nbaz\n");
218        let contents = src.read_span(&(4, 4).into(), 0, 0)?;
219        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
220        assert_eq!(1, contents.line());
221        assert_eq!(0, contents.column());
222        Ok(())
223    }
224
225    #[test]
226    fn middle_of_line() -> Result<(), MietteError> {
227        let src = String::from("foo\nbarbar\nbaz\n");
228        let contents = src.read_span(&(7, 4).into(), 0, 0)?;
229        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
230        assert_eq!(1, contents.line());
231        assert_eq!(3, contents.column());
232        Ok(())
233    }
234
235    #[test]
236    fn with_crlf() -> Result<(), MietteError> {
237        let src = String::from("foo\r\nbar\r\nbaz\r\n");
238        let contents = src.read_span(&(5, 5).into(), 0, 0)?;
239        assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
240        assert_eq!(1, contents.line());
241        assert_eq!(0, contents.column());
242        Ok(())
243    }
244
245    #[test]
246    fn with_context() -> Result<(), MietteError> {
247        let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
248        let contents = src.read_span(&(8, 3).into(), 1, 1)?;
249        assert_eq!("foo\nbar\nbaz\n", std::str::from_utf8(contents.data()).unwrap());
250        assert_eq!(1, contents.line());
251        assert_eq!(0, contents.column());
252        Ok(())
253    }
254
255    #[test]
256    fn multiline_with_context() -> Result<(), MietteError> {
257        let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
258        let contents = src.read_span(&(9, 11).into(), 1, 1)?;
259        assert_eq!("\nfoo\nbar\nbaz\n\n", std::str::from_utf8(contents.data()).unwrap());
260        assert_eq!(2, contents.line());
261        assert_eq!(0, contents.column());
262        let span: SourceSpan = (8, 14).into();
263        assert_eq!(&span, contents.span());
264        Ok(())
265    }
266
267    #[test]
268    fn multiline_with_context_line_start() -> Result<(), MietteError> {
269        let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
270        let contents = src.read_span(&(2, 0).into(), 2, 2)?;
271        assert_eq!("one\ntwo\n\n", std::str::from_utf8(contents.data()).unwrap());
272        assert_eq!(0, contents.line());
273        assert_eq!(0, contents.column());
274        let span: SourceSpan = (0, 9).into();
275        assert_eq!(&span, contents.span());
276        Ok(())
277    }
278}