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) {
89 let (key, value) = match line.split_once(':') {
90 Some((key, value)) => {
91 (key.to_string(), value.strip_prefix(' ').unwrap_or(value))
92 },
93 None => {
94 return Self::error(
95 "Line doesn't contain a ':' separator",
96 Some(line_number),
97 );
98 },
99 };
100
101 current_key = Some(key);
109
110 if value.is_empty() {
111 current_value = "\n".to_string();
112 } else {
113 current_value = value.to_string();
114
115 if Self::next_line_extends_value(&lines, index) {
117 current_value += "\n";
118 }
119 }
120 }
121
122 if line.starts_with(' ') || line.starts_with('\t') {
125 current_value += line;
126
127 if Self::next_line_extends_value(&lines, index) {
130 current_value += "\n";
131 }
132 }
133
134 if !Self::next_line_extends_value(&lines, index) {
138 if current_key.is_none() {
143 return Self::error(
144 "No key defined for the currently indented line",
145 Some(line_number),
146 );
147 }
148
149 data.insert(current_key.unwrap(), current_value);
151 current_key = None;
152 current_value = String::new();
153 }
154 }
155
156 Ok(Self { data })
157 }
158
159 pub fn hashmap(&self) -> &HashMap<String, String> { &self.data }
161
162 pub fn get(&self, key: &str) -> Option<&String> { self.data.get(key) }
164
165 pub fn get_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
169 if let Some(value) = self.data.get(key) {
170 return value;
171 }
172 default
173 }
174}
175
176pub fn parse_tagfile(content: &str) -> Result<Vec<TagSection>, ParserError> {
184 let mut sections = vec![];
185 let section_strings = content.split("\n\n");
186
187 for (iter, section) in section_strings.clone().enumerate() {
188 if section.is_empty() || section.chars().all(|c| c == '\n') {
191 break;
192 }
193
194 match TagSection::new(section) {
195 Ok(section) => sections.push(section),
196 Err(mut err) => {
197 let mut line_count = 0;
202
203 for _ in 0..iter {
204 line_count += 1;
206
207 line_count += section_strings.clone().count();
209 }
210
211 if let Some(line) = err.line {
212 err.line = Some(line_count + line);
213 } else {
214 err.line = Some(line_count);
215 }
216 },
217 }
218 }
219
220 Ok(sections)
221}