roan_ast/
source.rs

1use std::{
2    fs::File,
3    io::{self, BufReader, Read},
4    path::PathBuf,
5    str::Chars,
6};
7use tracing::debug;
8
9/// A source of Roan code.
10#[derive(Clone, Debug)]
11pub struct Source {
12    content: String,
13    path: Option<PathBuf>,
14}
15
16impl Source {
17    /// Creates a new `Source` from a `String`.
18    pub fn from_string(string: String) -> Self {
19        debug!("Creating source from string");
20        Self {
21            content: string,
22            path: None,
23        }
24    }
25
26    /// Creates a new `Source` from a byte slice.
27    pub fn from_bytes<T: AsRef<[u8]> + ?Sized>(source: &T) -> Self {
28        debug!("Creating source from bytes");
29        Self {
30            content: source.as_ref().iter().map(|&b| b as char).collect(),
31            path: None,
32        }
33    }
34
35    /// Creates a new `Source` from a file path.
36    pub fn from_path(path: PathBuf) -> io::Result<Self> {
37        debug!("Creating source from path: {:?}", path);
38        let file = File::open(&path)?;
39        let reader = BufReader::new(file);
40        Ok(Self {
41            content: reader
42                .bytes()
43                .filter_map(|b| b.ok().map(|b| b as char))
44                .collect(),
45            path: Some(path),
46        })
47    }
48
49    /// Sets or updates the path of this `Source`.
50    pub fn with_path(self, new_path: PathBuf) -> Self {
51        Self {
52            content: self.content,
53            path: Some(new_path),
54        }
55    }
56
57    /// Returns the content of this `Source`.
58    pub fn content(&self) -> String {
59        self.content.clone()
60    }
61
62    /// Returns the path associated with this `Source`, if any.
63    pub fn path(&self) -> Option<PathBuf> {
64        self.path.clone()
65    }
66
67    /// Returns the content of this `Source`.
68    pub fn len(&self) -> usize {
69        self.content.len()
70    }
71
72    /// Returns the content of this `Source` as a char iterator.
73    pub fn chars(&self) -> Chars {
74        self.content.chars()
75    }
76
77    /// Returns the content of this `Source` between the specified indices.
78    pub fn get_between(&self, start: usize, end: usize) -> String {
79        self.content[start..end].to_string()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_source_from_string() {
89        let source = Source::from_string("fn main() {}".to_string());
90
91        assert_eq!(source.content(), "fn main() {}");
92        assert_eq!(source.path(), None);
93    }
94
95    #[test]
96    fn test_source_from_bytes() {
97        let source = Source::from_bytes(b"fn main() {}");
98
99        assert_eq!(source.content(), "fn main() {}");
100        assert_eq!(source.path(), None);
101    }
102
103    #[test]
104    fn test_source_with_path() {
105        let source = Source::from_string("fn main() {}".to_string())
106            .with_path(PathBuf::from("tests/test.roan"));
107
108        assert_eq!(source.content(), "fn main() {}");
109        assert_eq!(source.path(), Some(PathBuf::from("tests/test.roan")));
110    }
111
112    #[test]
113    fn test_source_len() {
114        let source = Source::from_string("fn main() {}".to_string());
115
116        assert_eq!(source.len(), 12);
117    }
118
119    #[test]
120    fn test_source_chars() {
121        let source = Source::from_string("fn main() {}".to_string());
122
123        assert_eq!(source.chars().collect::<String>(), "fn main() {}");
124    }
125
126    #[test]
127    fn test_source_get_between() {
128        let source = Source::from_string("fn main() {}".to_string());
129
130        assert_eq!(source.get_between(3, 7), "main");
131    }
132}