1use core::iter::Iterator;
3use std::collections::HashMap;
4use std::fmt;
5
6#[derive(Debug)]
7pub struct ParserError {
9 pub msg: String,
10 pub line: Option<usize>,
11}
12
13impl fmt::Display for ParserError {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 if let Some(num) = self.line {
16 write!(f, "{} at line '{num}'", self.msg)?
17 } else {
18 write!(f, "{}", self.msg)?
19 }
20 Ok(())
21 }
22}
23
24impl std::error::Error for ParserError {}
25
26#[derive(Debug)]
29pub struct TagSection {
30 data: HashMap<String, String>,
31}
32
33impl From<TagSection> for HashMap<String, String> {
34 fn from(value: TagSection) -> Self { value.data }
35}
36
37impl TagSection {
38 fn error(msg: &str, line: Option<usize>) -> Result<Self, ParserError> {
39 Err(ParserError {
40 msg: "E:".to_owned() + msg,
41 line,
42 })
43 }
44
45 fn line_is_key(line: &str) -> bool { !line.starts_with(' ') && !line.starts_with('\t') }
46
47 fn next_line_extends_value(lines: &[&str], current_line: usize) -> bool {
48 if let Some(next_line) = lines.get(current_line + 1) {
49 !Self::line_is_key(next_line)
50 } else {
51 false
52 }
53 }
54
55 pub fn new(section: &str) -> Result<Self, ParserError> {
60 if section.contains("\n\n") {
62 return Self::error("More than one section was found", None);
63 }
64
65 if section.is_empty() {
67 return Self::error("An empty string was passed", None);
68 }
69
70 let mut data = HashMap::new();
72 let lines = section.lines().collect::<Vec<&str>>();
73
74 let mut current_key: Option<String> = None;
76 let mut current_value = String::new();
77
78 for (index, line) in lines.iter().enumerate() {
79 let line_number = index + 1;
81
82 if line.starts_with('#') {
84 continue;
85 }
86
87 if Self::line_is_key(line) {
90 let (key, value) = match line.split_once(':') {
91 Some((key, value)) => {
92 (key.to_string(), value.strip_prefix(' ').unwrap_or(value))
93 },
94 None => {
95 return Self::error(
96 "Line doesn't contain a ':' separator",
97 Some(line_number),
98 );
99 },
100 };
101
102 current_key = Some(key);
110
111 if value.is_empty() {
112 current_value = "\n".to_string();
113 } else {
114 current_value = value.to_string();
115
116 if Self::next_line_extends_value(&lines, index) {
118 current_value += "\n";
119 }
120 }
121 }
122
123 if line.starts_with(' ') || line.starts_with('\t') {
127 current_value += line;
128
129 if Self::next_line_extends_value(&lines, index) {
133 current_value += "\n";
134 }
135 }
136
137 if !Self::next_line_extends_value(&lines, index) {
141 if current_key.is_none() {
147 return Self::error(
148 "No key defined for the currently indented line",
149 Some(line_number),
150 );
151 }
152
153 data.insert(current_key.unwrap(), current_value);
156 current_key = None;
157 current_value = String::new();
158 }
159 }
160
161 Ok(Self { data })
162 }
163
164 pub fn hashmap(&self) -> &HashMap<String, String> { &self.data }
166
167 pub fn get(&self, key: &str) -> Option<&String> { self.data.get(key) }
169
170 pub fn get_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
174 if let Some(value) = self.data.get(key) {
175 return value;
176 }
177 default
178 }
179}
180
181pub fn parse_tagfile(content: &str) -> Result<Vec<TagSection>, ParserError> {
189 let mut sections = vec![];
190 let section_strings = content.split("\n\n");
191
192 for (iter, section) in section_strings.clone().enumerate() {
193 if section.is_empty() || section.chars().all(|c| c == '\n') {
196 break;
197 }
198
199 match TagSection::new(section) {
200 Ok(section) => sections.push(section),
201 Err(mut err) => {
202 let mut line_count = 0;
208
209 for _ in 0..iter {
210 line_count += 1;
212
213 line_count += section_strings.clone().count();
215 }
216
217 if let Some(line) = err.line {
218 err.line = Some(line_count + line);
219 } else {
220 err.line = Some(line_count);
221 }
222 },
223 }
224 }
225
226 Ok(sections)
227}