duvet_core/
file.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{contents::Contents, diagnostic::IntoDiagnostic, path::Path, Result};
5use core::{
6    fmt,
7    ops::{Deref, Range},
8};
9use miette::{SourceCode, WrapErr};
10use std::sync::Arc;
11
12#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct BinaryFile {
14    pub(crate) path: Path,
15    pub(crate) contents: Contents,
16}
17
18impl BinaryFile {
19    pub fn new<P, C>(path: P, contents: C) -> Self
20    where
21        P: Into<Path>,
22        C: Into<Contents>,
23    {
24        let path = path.into();
25        let contents = contents.into();
26        Self { path, contents }
27    }
28
29    pub fn path(&self) -> &Path {
30        &self.path
31    }
32
33    pub fn hash(&self) -> &[u8; 32] {
34        self.contents.hash()
35    }
36}
37
38impl Deref for BinaryFile {
39    type Target = [u8];
40
41    fn deref(&self) -> &Self::Target {
42        &self.contents
43    }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct SourceFile {
48    pub(crate) path: Path,
49    pub(crate) contents: Contents,
50}
51
52impl SourceFile {
53    pub fn new<P, C>(path: P, contents: C) -> Result<Self>
54    where
55        P: Into<Path>,
56        C: Into<Contents>,
57    {
58        let path = path.into();
59        let contents = contents.into();
60        let _ = core::str::from_utf8(&contents)
61            .into_diagnostic()
62            .wrap_err_with(|| path.clone())?;
63        Ok(Self { path, contents })
64    }
65
66    pub fn path(&self) -> &Path {
67        &self.path
68    }
69
70    pub fn hash(&self) -> &[u8; 32] {
71        self.contents.hash()
72    }
73
74    pub async fn as_toml<T>(&self) -> crate::Result<Arc<T>>
75    where
76        T: 'static + Send + Sync + serde::de::DeserializeOwned,
77    {
78        let path = self.path.clone();
79        let contents = self.contents.clone();
80        // TODO can we get better errors by mapping string ranges?
81        crate::Cache::current()
82            .get_or_init(*self.hash(), move || {
83                crate::Query::from(
84                    toml_edit::de::from_slice(contents.data())
85                        .map(Arc::new)
86                        .into_diagnostic()
87                        .wrap_err(path)
88                        .map_err(|err| err.into()),
89                )
90            })
91            .await
92    }
93
94    pub async fn as_json<T>(&self) -> crate::Result<Arc<T>>
95    where
96        T: 'static + Send + Sync + serde::de::DeserializeOwned,
97    {
98        let path = self.path.clone();
99        let contents = self.contents.clone();
100        // TODO can we get better errors by mapping string ranges?
101        crate::Cache::current()
102            .get_or_init(*self.hash(), move || {
103                crate::Query::from(
104                    serde_json::from_slice(contents.data())
105                        .map(Arc::new)
106                        .into_diagnostic()
107                        .wrap_err(path)
108                        .map_err(|err| err.into()),
109                )
110            })
111            .await
112    }
113
114    pub fn mapping(&self) -> Mapping {
115        let contents = self.clone();
116        crate::Cache::current()
117            .get_or_init(*self.hash(), move || {
118                crate::Query::from(Mapping::new(&contents))
119            })
120            .try_get()
121            .expect("mapping is synchronous")
122            .clone()
123    }
124
125    pub fn substr_range(&self, range: Range<usize>) -> Option<Slice> {
126        let _ = self.get(range.clone())?;
127        Some(Slice {
128            file: self.clone(),
129            start: range.start,
130            end: range.end,
131        })
132    }
133
134    pub fn substr(&self, v: &str) -> Option<Slice<SourceFile>> {
135        unsafe {
136            let beginning = self.as_bytes().as_ptr();
137            let end = beginning.add(self.len());
138
139            if !(beginning..=end).contains(&v.as_ptr()) {
140                return None;
141            }
142
143            Some(self.substr_unchecked(v))
144        }
145    }
146
147    /// # Safety
148    ///
149    /// Callers should ensure that the `v` is a slice of `self`
150    pub unsafe fn substr_unchecked(&self, v: &str) -> Slice<SourceFile> {
151        let range = self.substr_to_range(v);
152        Slice {
153            file: self.clone(),
154            start: range.start,
155            end: range.end,
156        }
157    }
158
159    #[inline]
160    fn substr_to_range(&self, v: &str) -> Range<usize> {
161        let start = v.as_bytes().as_ptr() as usize - self.as_bytes().as_ptr() as usize;
162        let end = start + v.len();
163        start..end
164    }
165
166    pub fn lines_slices(&self) -> impl Iterator<Item = Slice> + '_ {
167        self.lines()
168            .map(|line| unsafe { self.substr_unchecked(line) })
169    }
170}
171
172impl Deref for SourceFile {
173    type Target = str;
174
175    fn deref(&self) -> &Self::Target {
176        unsafe {
177            // Safety: this validity was checked at creation on SourceFile
178            core::str::from_utf8_unchecked(&self.contents)
179        }
180    }
181}
182
183impl AsRef<str> for SourceFile {
184    fn as_ref(&self) -> &str {
185        self
186    }
187}
188
189impl SourceCode for SourceFile {
190    fn read_span<'a>(
191        &'a self,
192        span: &miette::SourceSpan,
193        context_lines_before: usize,
194        context_lines_after: usize,
195    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
196        use miette::MietteSpanContents;
197
198        let contents = (**self).read_span(span, context_lines_before, context_lines_after)?;
199
200        let path = self.path.to_string();
201
202        Ok(Box::new(MietteSpanContents::new_named(
203            path,
204            contents.data(),
205            *contents.span(),
206            contents.line(),
207            contents.column(),
208            contents.line_count(),
209        )))
210    }
211}
212
213#[derive(Clone, Debug)]
214pub struct Mapping {
215    line_offsets: Arc<[usize]>,
216    #[cfg(debug_assertions)]
217    offset_to_line: Arc<[usize]>,
218}
219
220impl Mapping {
221    #[cfg(not(debug_assertions))]
222    fn new(contents: &SourceFile) -> Self {
223        let mut line_offsets = vec![];
224
225        for line in contents.lines() {
226            let range = contents.substr_to_range(line);
227            line_offsets.push(range.start);
228        }
229
230        Self {
231            line_offsets: line_offsets.into(),
232        }
233    }
234
235    #[cfg(debug_assertions)]
236    fn new(contents: &SourceFile) -> Self {
237        let mut offset_to_line = vec![];
238        let mut line_offsets = vec![];
239        let mut last_end = 0;
240        let mut last_line = 0;
241
242        for (lineno, line) in contents.lines().enumerate() {
243            // lines start at 1
244            let lineno = lineno + 1;
245
246            let range = contents.substr_to_range(line);
247
248            // fill in any newline gaps from the `lines` iter
249            for _ in 0..(range.start - last_end) {
250                offset_to_line.push(last_line);
251            }
252
253            for _ in range.clone() {
254                offset_to_line.push(lineno);
255            }
256
257            last_line = lineno;
258            last_end = range.end;
259            line_offsets.push(range.start);
260        }
261
262        Self {
263            offset_to_line: offset_to_line.into(),
264            line_offsets: line_offsets.into(),
265        }
266    }
267
268    pub fn offset_to_line(&self, offset: usize) -> usize {
269        let res = self.line_offsets.binary_search(&offset);
270
271        let line = match res {
272            Ok(line) => line + 1,
273            Err(line) => line,
274        };
275
276        #[cfg(debug_assertions)]
277        if let Some(expected) = self.offset_to_line.get(offset) {
278            assert_eq!(*expected, line, "offset={offset}, {:?}", self.line_offsets);
279        }
280
281        line
282    }
283}
284
285#[derive(Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
286pub struct Slice<File = SourceFile> {
287    file: File,
288    start: usize,
289    end: usize,
290}
291
292impl<F: File> Slice<F> {
293    pub fn path(&self) -> &Path {
294        self.file.path()
295    }
296
297    pub fn file(&self) -> &F {
298        &self.file
299    }
300
301    pub fn range(&self) -> Range<usize> {
302        self.start..self.end
303    }
304}
305
306impl Slice<SourceFile> {
307    pub fn error<E>(&self, e: E, label: impl AsRef<str>) -> crate::diagnostic::Error
308    where
309        E: Into<crate::diagnostic::Error>,
310    {
311        e.into().with_source_slice(self.clone(), label)
312    }
313
314    pub fn parse<T>(&self) -> crate::Result<T>
315    where
316        T: core::str::FromStr,
317        T::Err: Into<crate::diagnostic::Error>,
318    {
319        self.as_ref()
320            .parse()
321            .map_err(|err| self.error(err, "error here"))
322    }
323
324    pub fn line(&self) -> usize {
325        self.line_range().start
326    }
327
328    pub fn line_range(&self) -> Range<usize> {
329        let mapping = self.file.mapping();
330        let start = mapping.offset_to_line(self.start);
331        let end = mapping.offset_to_line(self.end);
332        start..(end + 1)
333    }
334}
335
336impl fmt::Debug for Slice<BinaryFile> {
337    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
338        self[..].fmt(f)
339    }
340}
341
342impl fmt::Debug for Slice<SourceFile> {
343    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
344        self[..].fmt(f)
345    }
346}
347
348impl fmt::Display for Slice<SourceFile> {
349    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350        self[..].fmt(f)
351    }
352}
353
354impl Deref for Slice<BinaryFile> {
355    type Target = [u8];
356
357    fn deref(&self) -> &Self::Target {
358        unsafe {
359            // Safety: range validatity was checked at slice creation time
360            self.file.get_unchecked(self.start..self.end)
361        }
362    }
363}
364
365impl AsRef<[u8]> for Slice<BinaryFile> {
366    #[inline]
367    fn as_ref(&self) -> &[u8] {
368        self
369    }
370}
371
372impl Deref for Slice<SourceFile> {
373    type Target = str;
374
375    fn deref(&self) -> &Self::Target {
376        unsafe {
377            // Safety: range validatity was checked at slice creation time
378            self.file.get_unchecked(self.start..self.end)
379        }
380    }
381}
382
383impl AsRef<str> for Slice<SourceFile> {
384    #[inline]
385    fn as_ref(&self) -> &str {
386        self
387    }
388}
389
390impl PartialEq<[u8]> for Slice<BinaryFile> {
391    fn eq(&self, other: &[u8]) -> bool {
392        (**self).eq(other)
393    }
394}
395
396impl PartialEq<[u8]> for Slice<SourceFile> {
397    fn eq(&self, other: &[u8]) -> bool {
398        (**self).as_bytes().eq(other)
399    }
400}
401
402impl PartialEq<str> for Slice<SourceFile> {
403    fn eq(&self, other: &str) -> bool {
404        (**self).eq(other)
405    }
406}
407
408pub trait File {
409    fn path(&self) -> &Path;
410}
411
412impl File for SourceFile {
413    fn path(&self) -> &Path {
414        &self.path
415    }
416}
417
418impl File for BinaryFile {
419    fn path(&self) -> &Path {
420        &self.path
421    }
422}