use core::iter::Iterator;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug)]
pub struct ParserError {
pub msg: String,
pub line: Option<usize>,
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(num) = self.line {
write!(f, "{} at line '{num}'", self.msg)?
} else {
write!(f, "{}", self.msg)?
}
Ok(())
}
}
impl std::error::Error for ParserError {}
#[derive(Debug)]
pub struct TagSection {
data: HashMap<String, String>,
}
impl From<TagSection> for HashMap<String, String> {
fn from(value: TagSection) -> Self { value.data }
}
impl TagSection {
fn error(msg: &str, line: Option<usize>) -> Result<Self, ParserError> {
Err(ParserError {
msg: "E:".to_owned() + msg,
line,
})
}
fn line_is_key(line: &str) -> bool { !line.starts_with(' ') && !line.starts_with('\t') }
fn next_line_extends_value(lines: &[&str], current_line: usize) -> bool {
if let Some(next_line) = lines.get(current_line + 1) {
!Self::line_is_key(next_line)
} else {
false
}
}
pub fn new(section: &str) -> Result<Self, ParserError> {
if section.contains("\n\n") {
return Self::error("More than one section was found", None);
}
if section.is_empty() {
return Self::error("An empty string was passed", None);
}
let mut data = HashMap::new();
let lines = section.lines().collect::<Vec<&str>>();
let mut current_key: Option<String> = None;
let mut current_value = String::new();
for (index, line) in lines.iter().enumerate() {
let line_number = index + 1;
if line.starts_with('#') {
continue;
}
if Self::line_is_key(line) {
let (key, value) = match line.split_once(':') {
Some((key, value)) => {
(key.to_string(), value.strip_prefix(' ').unwrap_or(value))
},
None => {
return Self::error(
"Line doesn't contain a ':' separator",
Some(line_number),
);
},
};
current_key = Some(key);
if value.is_empty() {
current_value = "\n".to_string();
} else {
current_value = value.to_string();
if Self::next_line_extends_value(&lines, index) {
current_value += "\n";
}
}
}
if line.starts_with(' ') || line.starts_with('\t') {
current_value += line;
if Self::next_line_extends_value(&lines, index) {
current_value += "\n";
}
}
if !Self::next_line_extends_value(&lines, index) {
if current_key.is_none() {
return Self::error(
"No key defined for the currently indented line",
Some(line_number),
);
}
data.insert(current_key.unwrap(), current_value);
current_key = None;
current_value = String::new();
}
}
Ok(Self { data })
}
pub fn hashmap(&self) -> &HashMap<String, String> { &self.data }
pub fn get(&self, key: &str) -> Option<&String> { self.data.get(key) }
pub fn get_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
if let Some(value) = self.data.get(key) {
return value;
}
default
}
}
pub fn parse_tagfile(content: &str) -> Result<Vec<TagSection>, ParserError> {
let mut sections = vec![];
let section_strings = content.split("\n\n");
for (iter, section) in section_strings.clone().enumerate() {
if section.is_empty() || section.chars().all(|c| c == '\n') {
break;
}
match TagSection::new(section) {
Ok(section) => sections.push(section),
Err(mut err) => {
let mut line_count = 0;
for _ in 0..iter {
line_count += 1;
line_count += section_strings.clone().count();
}
if let Some(line) = err.line {
err.line = Some(line_count + line);
} else {
err.line = Some(line_count);
}
},
}
}
Ok(sections)
}