1#![expect(missing_docs, reason = "fields mostly self-describing")]
2
3use crate::codegen::Prefix;
4use std::ops::Index;
5use std::path::{Path, PathBuf};
6
7#[derive(Default, Clone, PartialEq, Eq, Debug)]
8pub struct Files {
9 pub file_names: Vec<String>,
13
14 pub file_texts: Vec<String>,
18
19 pub file_line_maps: Vec<LineMap>,
23}
24
25#[derive(Default, Clone, PartialEq, Eq, Debug)]
26pub struct LineMap {
27 line_ends: Vec<usize>,
29}
30
31impl Index<usize> for LineMap {
32 type Output = usize;
33
34 fn index(&self, index: usize) -> &Self::Output {
35 &self.line_ends[index]
36 }
37}
38
39impl LineMap {
40 pub fn from_str(text: &str) -> Self {
41 let line_ends = text.match_indices('\n').map(|(i, _)| i + 1).collect();
42 Self { line_ends }
43 }
44
45 pub fn line(&self, pos: usize) -> usize {
47 self.line_ends.partition_point(|&end| end <= pos)
48 }
49
50 pub fn get(&self, line: usize) -> Option<&usize> {
52 self.line_ends.get(line)
53 }
54}
55
56impl Files {
57 pub fn from_paths<P: AsRef<Path>>(
58 paths: impl IntoIterator<Item = P>,
59 prefixes: &[crate::codegen::Prefix],
60 ) -> Result<Self, (PathBuf, std::io::Error)> {
61 fn replace_prefixes(prefixes: &[Prefix], path: String) -> String {
62 for Prefix { prefix, name } in prefixes {
63 if path.starts_with(prefix) {
64 return path.replacen(prefix, name, 1);
65 }
66 }
67 path
68 }
69
70 let mut file_names = Vec::new();
71 let mut file_texts = Vec::new();
72 let mut file_line_maps = Vec::new();
73
74 for path in paths {
75 let path = path.as_ref();
76 let contents =
77 std::fs::read_to_string(path).map_err(|err| (path.to_path_buf(), err))?;
78 let name = replace_prefixes(prefixes, path.display().to_string());
79
80 file_line_maps.push(LineMap::from_str(&contents));
81 file_names.push(name);
82 file_texts.push(contents);
83 }
84
85 Ok(Self {
86 file_names,
87 file_texts,
88 file_line_maps,
89 })
90 }
91
92 pub fn from_names_and_contents(files: impl IntoIterator<Item = (String, String)>) -> Self {
93 let mut file_names = Vec::new();
94 let mut file_texts = Vec::new();
95 let mut file_line_maps = Vec::new();
96
97 for (name, contents) in files {
98 file_line_maps.push(LineMap::from_str(&contents));
99 file_names.push(name);
100 file_texts.push(contents);
101 }
102
103 Self {
104 file_names,
105 file_texts,
106 file_line_maps,
107 }
108 }
109
110 pub fn file_name(&self, file: usize) -> Option<&str> {
111 self.file_names.get(file).map(|x| x.as_str())
112 }
113
114 pub fn file_text(&self, file: usize) -> Option<&str> {
115 self.file_texts.get(file).map(|x| x.as_str())
116 }
117
118 pub fn file_line_map(&self, file: usize) -> Option<&LineMap> {
119 self.file_line_maps.get(file)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn line_map() {
129 let line_map = LineMap::from_str("");
130 assert_eq!(line_map.line_ends, &[]);
131 assert_eq!(line_map.line(0), 0);
132 assert_eq!(line_map.line(100), 0);
133
134 let line_map = LineMap::from_str("line 0");
135 assert_eq!(line_map.line_ends, &[]);
136 assert_eq!(line_map.line(0), 0);
137 assert_eq!(line_map.line(100), 0);
138
139 let line_map = LineMap::from_str("line 0\nline 1");
140 assert_eq!(line_map.line_ends, &[7]);
141 assert_eq!(line_map.line(0), 0);
142 assert_eq!(line_map.line(100), 1);
143 }
144}