use std::{collections::HashMap, fmt::Display, str::FromStr};
use log::{debug, info, trace};
use crate::args::Args;
use crate::errors::ProconError;
use crate::line::Line;
use crate::node::{Node, NodeType};
use crate::nodes::Nodes;
#[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 {
pub(crate) content: HashMap<String, Line>,
pub(crate) last_key: String,
}
#[allow(dead_code)]
impl PropertyFileReader {
pub fn parse(args: &Args, content: &String) -> Result<Nodes, ProconError> {
info!("Use PropertyFileReader");
let config_file = Self::read_lines(args, &content);
Self::convert_property_to_nodes(&config_file)
}
fn convert_property_to_nodes(config_file: &PropertyFileReader) -> Result<Nodes, ProconError> {
let mut yaml_nodes: Nodes = Nodes::new();
for (prop_key, line) in config_file.content.iter() {
let mut node_parts = prop_key.split(".").collect::<Vec<&str>>();
trace!("Node parts: {:?}", node_parts);
if node_parts.is_empty() {
trace!("Ignore empty parts");
continue;
}
let name = node_parts[0];
node_parts.remove(0);
let mut new_node = Node::new_from_name(name);
if node_parts.len() == 0 {
new_node.value = NodeType::parse(&line.value);
yaml_nodes.merge(&mut new_node);
continue;
}
Self::create_child_nodes(&mut new_node, &mut node_parts, &line.value);
yaml_nodes.merge(&mut new_node);
}
Ok(yaml_nodes)
}
fn read_lines(args: &Args, content: &String) -> PropertyFileReader {
let mut config_file = PropertyFileReader::new();
let mut line_number = 1;
let delimiter = &args.target_format.delimiter();
for line in content.split("\n") {
config_file.process_line(line, line_number, &delimiter.unwrap());
line_number = line_number + 1;
}
config_file
}
pub fn create_child_nodes(node: &mut Node, parts: &mut Vec<&str>, value: &str) {
let mut last_node = &mut *node;
for (index, name) in parts.iter().enumerate() {
let mut new_node = Node::new_child(index + 1, last_node, name);
if index == parts.len() - 1 {
new_node.value = NodeType::parse(value);
}
let children = &mut last_node.children;
children.push(new_node.clone());
last_node = &mut children[0];
}
}
fn new() -> PropertyFileReader {
PropertyFileReader {
content: HashMap::new(),
last_key: String::from(""),
}
}
fn process_line(&mut self, line: &str, line_number: u32, delimiter: &Delimiter) {
if line.is_empty() {
return;
}
if !self.consider_multiline() && line.starts_with("#") || line.starts_with("!") {
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) {
debug!("'{}' 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);
debug!("Adding to content {:?}", line);
return self.content.insert(line.key.clone(), line.clone());
}
fn add_multiline(&mut self, line: &str) {
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;
debug!("{}", even_odd);
even_odd == 1
}
}