use crate::line::Line;
use crate::node::Node;
use crate::nodes::Nodes;
use log::trace;
use std::{
collections::HashMap,
fmt::Display,
fs::File,
io::{BufRead, BufReader},
str::FromStr,
};
#[cfg(test)]
#[path = "./property_file_reader_test.rs"]
mod property_file_reader_test;
#[derive(Debug, Clone)]
pub enum Delimiter {
Equals,
Colon,
Whitespace,
}
impl Display for Delimiter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl Delimiter {
fn value(&self) -> char {
match self {
Delimiter::Equals => '=',
Delimiter::Colon => ':',
Delimiter::Whitespace => ' ',
}
}
}
impl FromStr for Delimiter {
type Err = String;
fn from_str(input: &str) -> Result<Delimiter, Self::Err> {
match input.to_lowercase().as_str() {
"=" => Ok(Delimiter::Equals),
":" => Ok(Delimiter::Colon),
" " => Ok(Delimiter::Whitespace),
&_ => Ok(Delimiter::Equals),
}
}
}
#[derive(Debug)]
pub struct PropertyFileReader {
content: HashMap<String, Line>,
error_lines: Vec<Line>,
comments: Vec<Line>,
blank_lines: Vec<u32>,
last_key: String,
}
#[allow(dead_code)]
impl PropertyFileReader {
pub fn parse(
file: &File,
filename: &str,
delimiter: &Delimiter,
) -> Result<Nodes, std::io::Error> {
let reader = BufReader::new(file);
let mut config_file = PropertyFileReader::new();
let mut line_number = 1;
for result_line in reader.lines() {
let line = result_line.unwrap();
config_file.process_line(line, line_number, delimiter);
line_number = line_number + 1;
}
trace!("Read {} successfully", filename);
let property_map: &HashMap<String, Line> = config_file.get_content();
let mut yaml_nodes: Nodes = Nodes::new(
property_map.len(),
config_file.comments.to_owned(),
config_file.blank_lines.to_owned(),
);
let mut new_node: Node;
for (prop_key, line) in property_map.iter() {
let mut node_parts = prop_key.split(".").collect::<Vec<&str>>();
trace!("Node parts {:?}", node_parts);
if node_parts.is_empty() {
trace!("Ignoring empty parts");
continue;
}
new_node = Node::new(&mut node_parts, &line.value);
yaml_nodes.merge(&mut new_node);
}
Ok(yaml_nodes)
}
fn new() -> PropertyFileReader {
PropertyFileReader {
content: HashMap::new(),
error_lines: Vec::new(),
comments: Vec::new(),
blank_lines: Vec::new(),
last_key: String::from(""),
}
}
fn get_content(&self) -> &HashMap<String, Line> {
&self.content
}
fn process_line(&mut self, line: String, line_number: u32, delimiter: &Delimiter) {
if line.is_empty() {
self.add_blank_line(line_number);
return;
}
if !self.consider_multiline() && line.starts_with("#") || line.starts_with("!") {
self.add_comment(line, line_number);
return;
}
let parts = line.split_once(delimiter.value());
let (key, value) = match parts {
None => ("", ""),
Some(key_value) => key_value,
};
if key.is_empty() {
if self.content.contains_key(&self.last_key) {
self.add_multiline(&line);
return;
}
self.add(&line, value, line_number);
return;
}
if self.is_multiline(value) {
trace!("'{}' is a multiline", value);
self.last_key = self.sanitize_key(key);
}
self.add(key, value, line_number);
}
fn add(&mut self, key: &str, value: &str, line_number: u32) -> Option<Line> {
let line = Line::new(key, value, line_number);
trace!("Adding to content {:?}", line);
return self.content.insert(line.key.clone(), line.clone());
}
fn add_multiline(&mut self, line: &String) {
let previous_line = self.content.get_mut(&self.last_key).unwrap();
previous_line.add_multiline(line);
self.last_key = String::from("");
}
fn sanitize_key(&self, key: &str) -> String {
key.trim_end().to_string()
}
fn sanitize_value(&self, value: &str) -> String {
value.trim_start().to_string()
}
fn consider_multiline(&self) -> bool {
!self.last_key.is_empty()
}
fn is_multiline(&self, value: &str) -> bool {
if !value.ends_with("\\") {
return false;
}
let mut counter = 0;
for c in value.chars().rev() {
if c != '\\' {
break;
}
counter = counter + 1;
}
let even_odd = counter % 2;
trace!("{}", even_odd);
even_odd == 1
}
fn add_comment(&mut self, value: String, line_number: u32) {
let line = Line::new("", &value, line_number);
trace!("Ignoring commented line {:?}", &line);
self.comments.push(line)
}
fn add_error_line(&mut self, value: String, line_number: u32) {
let line = Line::new("", &value, line_number);
trace!("Add error line {:?}", &line);
self.error_lines.push(line);
}
fn add_blank_line(&mut self, line_number: u32) {
trace!("Ignoring empty line at {}", line_number);
self.blank_lines.push(line_number);
}
}