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