You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.9 KiB
Rust
189 lines
5.9 KiB
Rust
4 years ago
|
use crate::config::{Config, Section, Entry};
|
||
4 years ago
|
use std::error::Error;
|
||
4 years ago
|
use std::fmt::{Display, Formatter};
|
||
4 years ago
|
|
||
|
#[derive(Debug)]
|
||
4 years ago
|
pub struct ParserError {
|
||
|
pub filename: String,
|
||
|
pub line_no: usize,
|
||
|
pub description: String,
|
||
4 years ago
|
}
|
||
|
|
||
|
impl Error for ParserError {}
|
||
|
|
||
|
impl Display for ParserError {
|
||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||
4 years ago
|
write!(f, "Failed to parse Unit config: {} in file {} at line {}", self.description, self.filename, self.line_no)
|
||
4 years ago
|
}
|
||
|
}
|
||
|
|
||
4 years ago
|
pub(crate) fn parse_config(config: &mut Config, filename: String, contents: String) -> Result<(), ParserError> {
|
||
4 years ago
|
let file_id = config.add_file(filename.clone());
|
||
|
let mut current_entry: Option<(&str, String)> = None;
|
||
|
let mut current_section: Option<&mut Section> = None;
|
||
|
let mut line_no = 0;
|
||
|
for line in contents.lines() {
|
||
|
line_no += 1;
|
||
|
|
||
|
let line_trimmed = line.trim();
|
||
|
|
||
|
// We can ignore comments or empty lines
|
||
|
if line_trimmed == "" || line_trimmed.starts_with("#") || line_trimmed.starts_with(";") {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Start of group
|
||
|
if line_trimmed.starts_with('[') {
|
||
|
if let Some(x) = line_trimmed.rfind(']') {
|
||
|
let name = &line_trimmed[1..x];
|
||
|
current_section = Some(
|
||
|
config
|
||
|
.sections
|
||
|
.entry(name.to_string())
|
||
|
.or_insert_with(|| Section::new(name.to_string()))
|
||
|
);
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if let Some(section) = &mut current_section {
|
||
|
let (key, value) = if let Some((key, value)) = ¤t_entry {
|
||
|
(*key, value.clone() + line_trimmed)
|
||
|
} else {
|
||
|
let split: Vec<&str> = line_trimmed.splitn(2, "=").collect();
|
||
|
if split.len() < 2 {
|
||
|
return Err(ParserError {
|
||
|
filename,
|
||
|
line_no,
|
||
|
description: "Line didn't have Key=Value syntax".to_string(),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
(split.get(0).unwrap().trim(), split.get(1).unwrap().trim().to_string())
|
||
|
};
|
||
|
|
||
|
if value.ends_with("\\") {
|
||
|
let value = value[..value.len() - 1].to_string();
|
||
|
current_entry = Some((key, value + " "))
|
||
|
} else {
|
||
|
section
|
||
|
.entries
|
||
|
.entry(key.to_string())
|
||
|
.or_insert_with(|| Entry::new(key.to_string()))
|
||
|
.add(&value, file_id)
|
||
|
}
|
||
|
} else {
|
||
|
return Err(ParserError {
|
||
|
filename,
|
||
|
line_no,
|
||
|
description: "Config should start with section header".to_string(),
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
4 years ago
|
use crate::time::FiniteTimeSpan;
|
||
|
use crate::time::TimeSpan::{Finite, Infinite};
|
||
4 years ago
|
|
||
|
#[test]
|
||
|
fn test_parse() {
|
||
|
let mut config = Config::default();
|
||
|
let res = parse_config(&mut config, "test.unit".to_string(), r"
|
||
|
[Unit]
|
||
|
Hello=no
|
||
|
Hello=
|
||
|
Hello=Hell
|
||
|
Hello=World
|
||
|
Jump=Oh no\
|
||
|
1336".to_string());
|
||
|
|
||
|
assert!(res.is_ok());
|
||
|
assert!(config.sections.get("Unit").is_some());
|
||
|
|
||
|
let section = config.sections.get("Unit").unwrap();
|
||
|
let hello_entry = section.entry("Hello").unwrap();
|
||
|
assert_eq!(hello_entry.get_string(), "World");
|
||
|
assert_eq!(hello_entry.get_list(), vec!["Hell", "World"]);
|
||
|
assert_eq!(section.entry("Jump").unwrap().get_string(), "Oh no 1336")
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_config_without_section() {
|
||
|
let mut config = Config::default();
|
||
|
let err = parse_config(&mut config, "test.unit".to_string(), r"Hello=World".to_string());
|
||
|
|
||
|
assert!(err.is_err());
|
||
|
|
||
|
let err = err.unwrap_err();
|
||
|
assert_eq!(err.line_no, 1);
|
||
|
assert_eq!(err.description, "Config should start with section header")
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_booleans() {
|
||
|
let mut config = Config::default();
|
||
|
let res = parse_config(&mut config, "test.unit".to_string(), r"
|
||
|
[Unit]
|
||
|
1=no
|
||
|
2=off
|
||
|
3=0
|
||
|
4=false
|
||
|
5=yes
|
||
|
6=on
|
||
|
7=1
|
||
|
8=true
|
||
|
9=x".to_string());
|
||
|
|
||
|
assert!(res.is_ok());
|
||
|
assert!(config.sections.get("Unit").is_some());
|
||
|
|
||
|
let unit = config.sections.get("Unit").unwrap();
|
||
|
assert_eq!(unit.get_boolean("1"), Some(false));
|
||
|
assert_eq!(unit.get_boolean("2"), Some(false));
|
||
|
assert_eq!(unit.get_boolean("3"), Some(false));
|
||
|
assert_eq!(unit.get_boolean("4"), Some(false));
|
||
|
assert_eq!(unit.get_boolean("5"), Some(true));
|
||
|
assert_eq!(unit.get_boolean("6"), Some(true));
|
||
|
assert_eq!(unit.get_boolean("7"), Some(true));
|
||
|
assert_eq!(unit.get_boolean("8"), Some(true));
|
||
|
assert_eq!(unit.get_boolean("9"), None);
|
||
|
}
|
||
|
|
||
4 years ago
|
#[test]
|
||
4 years ago
|
fn test_time_span() {
|
||
|
let mut config = Config::default();
|
||
|
let res = parse_config(&mut config, "test.unit".to_string(), r"
|
||
|
[Unit]
|
||
|
1=3 hours
|
||
|
2=1h
|
||
|
3=3 M
|
||
|
4=3m
|
||
|
5=1M1m
|
||
|
6=blaat
|
||
4 years ago
|
7=infinite
|
||
|
8=infinity
|
||
4 years ago
|
".to_string());
|
||
|
|
||
|
assert!(res.is_ok());
|
||
|
assert!(config.sections.get("Unit").is_some());
|
||
|
|
||
|
let unit = config.sections.get("Unit").unwrap();
|
||
|
|
||
4 years ago
|
assert_eq!(unit.get_time_span("1"), Some(Finite(FiniteTimeSpan::new().with_hours(3))));
|
||
|
assert_eq!(unit.get_time_span("2"), Some(Finite(FiniteTimeSpan::new().with_hours(1))));
|
||
|
assert_eq!(unit.get_time_span("3"), Some(Finite(FiniteTimeSpan::new().with_months(3))));
|
||
|
assert_eq!(unit.get_time_span("4"), Some(Finite(FiniteTimeSpan::new().with_minutes(3))));
|
||
|
assert_eq!(unit.get_time_span("5"), Some(Finite(FiniteTimeSpan::new().with_months(1).with_minutes(1))));
|
||
4 years ago
|
assert_eq!(unit.get_time_span("6"), None);
|
||
4 years ago
|
assert_eq!(unit.get_finite_time_span("8"), None);
|
||
|
assert_eq!(unit.get_finite_time_span("9"), None);
|
||
|
assert_eq!(unit.get_time_span("8"), Some(Infinite));
|
||
|
assert_eq!(unit.get_time_span("9"), Some(Infinite));
|
||
4 years ago
|
}
|
||
|
}
|