Skip to main content

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    fn name(&self) -> Option<&str> {
172        self.as_ref().name()
173    }
174}
175
176impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
177where
178    // The minimal bounds are used here.
179    // `T::Owned` need not be
180    // `SourceCode`, because `&T`
181    // can always be obtained from
182    // `Cow<'_, T>`.
183    T::Owned: Debug + Send + Sync,
184{
185    fn read_span<'a>(
186        &'a self,
187        span: &SourceSpan,
188        context_lines_before: usize,
189        context_lines_after: usize,
190    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
191        self.as_ref().read_span(span, context_lines_before, context_lines_after)
192    }
193
194    fn name(&self) -> Option<&str> {
195        self.as_ref().name()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn basic() -> Result<(), MietteError> {
205        let src = String::from("foo\n");
206        let contents = src.read_span(&(0, 4).into(), 0, 0)?;
207        assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
208        assert_eq!(0, contents.line());
209        assert_eq!(0, contents.column());
210        Ok(())
211    }
212
213    #[test]
214    fn shifted() -> Result<(), MietteError> {
215        let src = String::from("foobar");
216        let contents = src.read_span(&(3, 3).into(), 1, 1)?;
217        assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
218        assert_eq!(0, contents.line());
219        assert_eq!(0, contents.column());
220        Ok(())
221    }
222
223    #[test]
224    fn middle() -> Result<(), MietteError> {
225        let src = String::from("foo\nbar\nbaz\n");
226        let contents = src.read_span(&(4, 4).into(), 0, 0)?;
227        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
228        assert_eq!(1, contents.line());
229        assert_eq!(0, contents.column());
230        Ok(())
231    }
232
233    #[test]
234    fn middle_of_line() -> Result<(), MietteError> {
235        let src = String::from("foo\nbarbar\nbaz\n");
236        let contents = src.read_span(&(7, 4).into(), 0, 0)?;
237        assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
238        assert_eq!(1, contents.line());
239        assert_eq!(3, contents.column());
240        Ok(())
241    }
242
243    #[test]
244    fn with_crlf() -> Result<(), MietteError> {
245        let src = String::from("foo\r\nbar\r\nbaz\r\n");
246        let contents = src.read_span(&(5, 5).into(), 0, 0)?;
247        assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
248        assert_eq!(1, contents.line());
249        assert_eq!(0, contents.column());
250        Ok(())
251    }
252
253    #[test]
254    fn with_context() -> Result<(), MietteError> {
255        let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
256        let contents = src.read_span(&(8, 3).into(), 1, 1)?;
257        assert_eq!("foo\nbar\nbaz\n", std::str::from_utf8(contents.data()).unwrap());
258        assert_eq!(1, contents.line());
259        assert_eq!(0, contents.column());
260        Ok(())
261    }
262
263    #[test]
264    fn multiline_with_context() -> Result<(), MietteError> {
265        let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
266        let contents = src.read_span(&(9, 11).into(), 1, 1)?;
267        assert_eq!("\nfoo\nbar\nbaz\n\n", std::str::from_utf8(contents.data()).unwrap());
268        assert_eq!(2, contents.line());
269        assert_eq!(0, contents.column());
270        let span: SourceSpan = (8, 14).into();
271        assert_eq!(&span, contents.span());
272        Ok(())
273    }
274
275    #[test]
276    fn multiline_with_context_line_start() -> Result<(), MietteError> {
277        let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
278        let contents = src.read_span(&(2, 0).into(), 2, 2)?;
279        assert_eq!("one\ntwo\n\n", std::str::from_utf8(contents.data()).unwrap());
280        assert_eq!(0, contents.line());
281        assert_eq!(0, contents.column());
282        let span: SourceSpan = (0, 9).into();
283        assert_eq!(&span, contents.span());
284        Ok(())
285    }
286}