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