1use std::{
2 backtrace::Backtrace,
3 fmt::Display,
4 fs::File,
5 io::{self, BufRead, BufReader, BufWriter, Lines, Write},
6 path::Path,
7};
8
9use snafu::Snafu;
10
11use crate::util::io::{FileError, create_file, open_file};
12
13use super::{
14 ParseContext,
15 module::ModuleKind,
16 section::{Section, SectionInheritParseError, SectionParseError, Sections, SectionsError},
17};
18
19pub struct Delinks {
20 pub sections: Sections,
21 pub files: Vec<DelinkFile>,
22 module_kind: ModuleKind,
23}
24
25#[derive(Debug, Snafu)]
26pub enum DelinksParseError {
27 #[snafu(transparent)]
28 File { source: FileError },
29 #[snafu(transparent)]
30 Io { source: io::Error },
31 #[snafu(transparent)]
32 SectionParse { source: SectionParseError },
33 #[snafu(display("{context}: {error}"))]
34 Sections { context: ParseContext, error: Box<SectionsError> },
35 #[snafu(transparent)]
36 DelinkFileParse { source: DelinkFileParseError },
37}
38
39#[derive(Debug, Snafu)]
40pub enum DelinksWriteError {
41 #[snafu(transparent)]
42 File { source: FileError },
43 #[snafu(transparent)]
44 Io { source: io::Error },
45}
46
47impl Delinks {
48 pub fn new(sections: Sections, files: Vec<DelinkFile>, module_kind: ModuleKind) -> Self {
49 Self { sections, files, module_kind }
50 }
51
52 pub fn from_file<P: AsRef<Path>>(path: P, module_kind: ModuleKind) -> Result<Self, DelinksParseError> {
53 let path = path.as_ref();
54 let mut context = ParseContext { file_path: path.to_str().unwrap().to_string(), row: 0 };
55
56 let file = open_file(path)?;
57 let reader = BufReader::new(file);
58
59 let mut sections: Sections = Sections::new();
60 let mut files = vec![];
61
62 let mut lines = reader.lines();
63 while let Some(line) = lines.next() {
64 context.row += 1;
65
66 let line = line?;
67 let comment_start = line.find("//").unwrap_or(line.len());
68 let line = &line[..comment_start];
69
70 if Self::try_parse_delink_file(line, &mut lines, &mut context, &mut files, §ions)? {
71 break;
72 }
73 let Some(section) = Section::parse(line, &context)? else {
74 continue;
75 };
76 sections.add(section).map_err(|error| SectionsSnafu { context: context.clone(), error }.build())?;
77 }
78
79 while let Some(line) = lines.next() {
80 context.row += 1;
81
82 let line = line?;
83 let comment_start = line.find("//").unwrap_or(line.len());
84 let line = &line[..comment_start];
85
86 Self::try_parse_delink_file(line, &mut lines, &mut context, &mut files, §ions)?;
87 }
88
89 Ok(Self { sections, files, module_kind })
90 }
91
92 fn try_parse_delink_file(
93 line: &str,
94 lines: &mut Lines<BufReader<File>>,
95 context: &mut ParseContext,
96 files: &mut Vec<DelinkFile>,
97 sections: &Sections,
98 ) -> Result<bool, DelinkFileParseError> {
99 if line.chars().next().is_some_and(|c| !c.is_whitespace()) {
100 let delink_file = DelinkFile::parse(line, lines, context, sections)?;
101 files.push(delink_file);
102 Ok(true)
103 } else {
104 Ok(false)
105 }
106 }
107
108 pub fn to_file<P: AsRef<Path>>(path: P, sections: &Sections) -> Result<(), DelinksWriteError> {
109 let path = path.as_ref();
110
111 let file = create_file(path)?;
112 let mut writer = BufWriter::new(file);
113
114 write!(writer, "{}", DisplayDelinks { sections, files: &[] })?;
115
116 Ok(())
117 }
118
119 pub fn display(&self) -> DisplayDelinks {
120 DisplayDelinks { sections: &self.sections, files: &self.files }
121 }
122
123 pub fn module_kind(&self) -> ModuleKind {
124 self.module_kind
125 }
126}
127pub struct DisplayDelinks<'a> {
128 sections: &'a Sections,
129 files: &'a [DelinkFile],
130}
131
132impl Display for DisplayDelinks<'_> {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 for section in self.sections.sorted_by_address() {
135 writeln!(f, " {section}")?;
136 }
137 writeln!(f)?;
138 for file in self.files {
139 writeln!(f, "{file}")?;
140 }
141 Ok(())
142 }
143}
144
145pub struct DelinkFile {
146 pub name: String,
147 pub sections: Sections,
148 pub complete: bool,
149 pub gap: bool,
150}
151
152#[derive(Debug, Snafu)]
153pub enum DelinkFileParseError {
154 #[snafu(display("{context}: expected file path to end with ':':\n{backtrace}"))]
155 MissingColon { context: ParseContext, backtrace: Backtrace },
156 #[snafu(transparent)]
157 Io { source: io::Error },
158 #[snafu(transparent)]
159 SectionInheritParse { source: SectionInheritParseError },
160 #[snafu(transparent)]
161 Sections { source: SectionsError },
162}
163
164impl DelinkFile {
165 pub fn new(name: String, sections: Sections, complete: bool) -> Self {
166 Self { name, sections, complete, gap: false }
167 }
168
169 pub fn parse(
170 first_line: &str,
171 lines: &mut Lines<BufReader<File>>,
172 context: &mut ParseContext,
173 inherit_sections: &Sections,
174 ) -> Result<Self, DelinkFileParseError> {
175 let name = first_line
176 .trim()
177 .strip_suffix(':')
178 .ok_or_else(|| MissingColonSnafu { context: context.clone() }.build())?
179 .to_string();
180
181 let mut complete = false;
182 let mut sections = Sections::new();
183 for line in lines.by_ref() {
184 context.row += 1;
185 let line = line?;
186 let line = line.trim();
187 if line.is_empty() {
188 break;
189 }
190 if line == "complete" {
191 complete = true;
192 continue;
193 }
194 let section = Section::parse_inherit(line, context, inherit_sections)?.unwrap();
195 sections.add(section)?;
196 }
197
198 Ok(DelinkFile { name, sections, complete, gap: false })
199 }
200
201 pub fn split_file_ext(&self) -> (&str, &str) {
202 self.name.rsplit_once('.').unwrap_or((&self.name, ""))
203 }
204
205 pub fn gap(&self) -> bool {
206 self.gap
207 }
208}
209
210impl Display for DelinkFile {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 writeln!(f, "{}:", self.name)?;
213 for section in self.sections.sorted_by_address() {
214 writeln!(f, " {section}")?;
215 }
216 Ok(())
217 }
218}