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