use std::{
collections::HashMap,
env,
fs::File,
io::{self, BufRead},
path::Path,
};
use regex::Regex;
use crate::utils::{get_bool, get_string, get_vec, must_get_bool, must_get_string};
#[derive(Debug)]
enum LineType {
Data(String, String),
Section(String),
SubSection(String),
SectionEnd,
Comment,
Empty,
Invalid,
}
#[derive(Debug)]
pub struct Section {
pub key: String,
pub parent: Option<String>,
pub extends: Vec<String>,
pub data: HashMap<String, String>,
}
impl Clone for Section {
fn clone(&self) -> Self {
Section { key: self.key.clone(), parent: self.parent.clone(), data: self.data.clone(), extends: vec![] }
}
}
impl Section {
pub fn new(key: String, parent: Option<String>) -> Section {
Section { key, parent, data: HashMap::new(), extends: vec![] }
}
pub fn get_string(&self, key: &str) -> String {
get_string(&self.data, key)
}
pub fn get_bool(&self, key: &str) -> bool {
get_bool(&self.data, key)
}
pub fn get_vec(&self, key: &str) -> Vec<String> {
get_vec(&self.data, key)
}
pub fn must_get_string(&self, key: &str) -> String {
must_get_string(&self.data, key)
}
pub fn must_get_bool(&self, key: &str) -> bool {
must_get_bool(&self.data, key)
}
}
pub fn load_env(file: &str) -> (HashMap<String, String>, Vec<Section>) {
let mut config: HashMap<String, String> = HashMap::new();
if !std::path::Path::new(file).exists() {
return (config, vec![]);
}
let match_title = Regex::new(r"(?m)([\w-]+)").unwrap();
let mut sections: Vec<Section> = vec![];
let mut current_section: Option<Section> = None;
let mut current_sec_title: Option<String> = None;
if let Ok(lines) = read_lines(file) {
for line in lines {
if let Ok(line) = line {
let line = handle_line(line);
match line {
LineType::Data(key, val) => {
if let Some(sec) = current_section.as_mut() {
sec.data.insert(key, val);
} else {
config.insert(key, val);
}
}
LineType::Section(title) => {
if let Some(sec) = current_section {
sections.push(sec);
}
let mut key = title;
let mut extends: Vec<&str> = vec![];
let key2 = key.clone();
if key.contains("+") {
let matches: Vec<&str> = match_title.find_iter(key2.as_str()).map(|f| f.as_str()).collect();
if matches.len() > 0 {
key = matches[0].to_string();
extends.extend_from_slice(&matches[1..])
}
}
let mut section = Section::new(key.clone(), None);
section.extends = extends.iter().map(|f| f.to_string()).collect();
current_section = Some(section);
current_sec_title = Some(key);
}
LineType::SubSection(title) => {
if let Some(sec) = current_section {
sections.push(sec);
}
let mut key = title;
let mut extends: Vec<&str> = vec![];
let key2 = key.clone();
if key.contains("+") {
let matches: Vec<&str> = match_title.find_iter(key2.as_str()).map(|f| f.as_str()).collect();
if matches.len() > 0 {
key = matches[0].to_string();
extends.extend_from_slice(&matches[1..])
}
}
let mut section = Section::new(key.clone(), current_sec_title.clone());
section.extends = extends.iter().map(|f| f.to_string()).collect();
current_section = Some(section)
}
LineType::SectionEnd => {
if let Some(sec) = current_section {
sections.push(sec);
current_section = None;
} else if current_sec_title.is_some() {
current_sec_title = None;
}
}
_ => {}
}
}
}
if let Some(sec) = current_section {
sections.push(sec);
}
}
let section_copy = sections.clone();
for sec in sections.iter_mut() {
for ext in sec.extends.iter() {
for sec2 in section_copy.iter() {
if sec2.key == *ext {
sec.data.extend(sec2.data.clone());
}
}
}
}
(config, sections)
}
fn handle_line(line: String) -> LineType {
let not_empty = Regex::new(r"[\w\-]").expect("not empty");
let is_comment = Regex::new(r"^\s*?#").expect("is comment");
if line.len() == 0 || !not_empty.is_match(&line) {
return LineType::Empty;
}
let line = line.replace("\"", "");
if is_comment.is_match(&line) {
let is_section = Regex::new(r"(?m)^#[ \t]*\[{1}([\w+-]+)").unwrap();
let is_sub_section = Regex::new(r"(?m)^#[ \t]*\[{2}([\w+-]+)").unwrap();
if line.starts_with("#---") {
return LineType::SectionEnd;
} else if is_section.is_match(&line) {
let title = extract_title(is_section, line);
if let Some(title) = title {
return LineType::Section(title);
}
return LineType::Invalid;
} else if is_sub_section.is_match(&line) {
let title = extract_title(is_sub_section, line);
if let Some(title) = title {
return LineType::SubSection(title);
}
return LineType::Invalid;
} else {
return LineType::Comment;
}
}
if line.contains("=") {
let arr: Vec<&str> = line.split("=").collect();
let key = arr[0].to_string().trim().to_string();
if !not_empty.is_match(&key) {
return LineType::Invalid;
}
let mut value = arr[1].trim().to_string();
if value.contains("#") {
let arr: Vec<&str> = value.split("#").collect();
value = arr[0].trim().to_string();
}
if value.len() == 0 || !not_empty.is_match(&value) {
value = String::new();
} else if value.contains("$") {
let r = Regex::new(r"(?m)\$\{(\w+)\}").expect("re");
for cap in r.captures_iter(&value.clone()) {
let env_key = &cap[1];
let val = env::var_os(env_key);
if let Some(val) = val {
let val2 = val.to_str().expect("os str to str");
let repl_key = format!("${{{}}}", env_key);
value = value.replace(&repl_key, val2);
} else {
println!("cannot find env {}", env_key);
}
}
}
return LineType::Data(key, value);
} else {
return LineType::Data(line.trim().to_string(), "true".to_string());
}
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
pub fn extract_title(re: Regex, line: String) -> Option<String> {
let result = re.captures(&line);
match result {
Some(val) => {
return Some(val[1].to_string());
}
None => {}
}
return None;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_load_env() {
let result = load_env("./.easy_arg.env");
println!("{:?}", result.0);
for sec in result.1.into_iter() {
println!("{:?}", sec);
}
}
#[test]
pub fn test_title() {
let re = Regex::new(r"(?m)^#[ \t]*\[{1}([\w+-]+)").unwrap();
let result = extract_title(re, "#[abc_-+ddd]".to_string()).unwrap();
assert_eq!(result, "abc_-+ddd".to_string());
let re = Regex::new(r"(?m)^#[ \t]*\[{2}([\w+-]+)").unwrap();
let result = extract_title(re, "# [[efg_-+ccc]]".to_string()).unwrap();
assert_eq!(result, "efg_-+ccc".to_string())
}
#[test]
pub fn test_captures() {
let re = Regex::new(r"(?m)([\w]+)").unwrap();
let a = "#[abc+xyz+123]";
re.find_iter(a).for_each(|f| {
println!("result {:?}", f.as_str());
})
}
}