lutra_compiler/project/
source_tree.rs1use std::{path, str::FromStr};
2
3use indexmap::IndexMap;
4use itertools::Itertools;
5
6#[derive(Debug, Clone)]
14pub struct SourceTree {
15 root: path::PathBuf,
18
19 sources: IndexMap<path::PathBuf, String>,
22}
23
24impl SourceTree {
25 pub fn empty() -> Self {
26 SourceTree {
27 sources: Default::default(),
28 root: path::PathBuf::new(),
29 }
30 }
31
32 pub fn single(path: path::PathBuf, content: String) -> Self {
33 SourceTree {
34 sources: [(path.clone(), content)].into(),
35 root: path::PathBuf::new(),
36 }
37 }
38
39 pub fn new<I>(iter: I, root: path::PathBuf) -> Self
40 where
41 I: IntoIterator<Item = (path::PathBuf, String)>,
42 {
43 SourceTree {
44 sources: IndexMap::from_iter(iter),
45 root,
46 }
47 }
48
49 pub fn is_empty(&self) -> bool {
50 self.sources.len() == 0
51 }
52
53 pub fn insert(&mut self, path: path::PathBuf, content: String) {
54 self.sources.insert(path, content);
55 }
56
57 pub fn replace(&mut self, path: &path::Path, content: String) -> Option<String> {
58 let source = self.sources.get_mut(path)?;
59 Some(std::mem::replace(source, content))
60 }
61
62 pub fn get_ids(&self) -> impl Iterator<Item = u16> {
63 0..self.sources.len() as u16
64 }
65 pub fn get_sources(&self) -> impl Iterator<Item = (&path::PathBuf, &String)> {
66 self.sources.iter()
67 }
68 pub fn get_root(&self) -> &std::path::Path {
69 self.root.as_path()
70 }
71
72 pub fn get_by_id(&self, source_id: u16) -> Option<(&path::Path, &str)> {
73 self.sources
74 .get_index(source_id as usize)
75 .map(|(p, c)| (p.as_path(), c.as_str()))
76 }
77
78 pub fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
79 self.sources
80 .get_full(path)
81 .map(|(i, _, content)| (i as u16, content.as_str()))
82 }
83
84 pub fn get_source_display_paths(&self) -> impl Iterator<Item = &path::Path> {
85 self.sources
86 .keys()
87 .map(|path| self.get_display_path(path).unwrap())
88 }
89
90 pub fn get_absolute_path(&self, path: impl AsRef<path::Path>) -> path::PathBuf {
92 let path = path.as_ref();
93 if path.as_os_str().is_empty() {
94 self.root.to_path_buf()
95 } else {
96 self.get_project_dir().join(path)
97 }
98 }
99
100 pub fn get_relative_path<'a>(
103 &self,
104 absolute_path: &'a path::Path,
105 ) -> Result<&'a path::Path, path::StripPrefixError> {
106 absolute_path.strip_prefix(&self.root)
107 }
108
109 pub fn get_project_dir(&self) -> &path::Path {
114 if self.root.extension().is_some_and(|e| e == "lt") {
115 self.root.parent().unwrap()
116 } else {
117 &self.root
118 }
119 }
120
121 pub fn get_display_path<'a>(&'a self, path: &'a path::Path) -> Option<&'a path::Path> {
126 if path.is_absolute() {
127 path.strip_prefix(self.get_project_dir()).ok()
128 } else if path.as_os_str().is_empty() {
129 self.root.file_name().map(path::Path::new)
130 } else {
131 Some(path)
132 }
133 }
134}
135
136impl std::fmt::Display for SourceTree {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 let mut r = format!("path: {}\nsources:\n", self.root.to_string_lossy());
139
140 for source in self.sources.keys().sorted() {
141 r += "- ";
142 r += &source.to_string_lossy();
143 r += "\n";
144 }
145
146 f.write_str(&r)
147 }
148}
149
150pub struct SourceOverlay<'a> {
152 tree: &'a SourceTree,
153
154 snippet_path: path::PathBuf,
155 snippet: &'a str,
156}
157
158impl<'a> SourceOverlay<'a> {
159 pub fn new(tree: &'a SourceTree, snippet: &'a str, snippet_path: Option<&str>) -> Self {
160 Self {
161 tree,
162 snippet,
163 snippet_path: snippet_path
164 .map(|s| path::PathBuf::from_str(s).unwrap())
165 .unwrap_or_default(),
166 }
167 }
168
169 pub const fn overlay_id() -> u16 {
170 u16::MAX
171 }
172}
173
174pub(crate) trait SourceProvider {
175 fn get_root(&self) -> &path::Path;
176
177 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)>;
178
179 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)>;
180}
181
182impl SourceProvider for SourceTree {
183 fn get_root(&self) -> &path::Path {
184 &self.root
185 }
186 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
187 SourceTree::get_by_id(self, id)
188 }
189
190 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
191 SourceTree::get_by_path(self, path)
192 }
193}
194
195impl<'a> SourceProvider for SourceOverlay<'a> {
196 fn get_root(&self) -> &path::Path {
197 &self.tree.root
198 }
199
200 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
201 if id == SourceOverlay::overlay_id() {
202 Some((self.snippet_path.as_path(), self.snippet))
203 } else {
204 self.tree.get_by_id(id)
205 }
206 }
207
208 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
209 if path == self.snippet_path {
210 Some((SourceOverlay::overlay_id(), self.snippet))
211 } else {
212 self.tree.get_by_path(path)
213 }
214 }
215}