1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5use tera::{Context, Tera};
6use toml::Value;
7
8pub type Config = std::collections::HashMap<String, Group>;
9pub type Group = std::collections::HashMap<String, toml::Value>;
10
11#[derive(Debug)]
12pub enum TmpTomlErr {
13    File(ReadFileErr),
14    GroupNotFound(String),
15    Config(toml::de::Error),
16    Render(TeraRenderErr),
17}
18impl From<toml::de::Error> for TmpTomlErr {
19    fn from(err: toml::de::Error) -> Self {
20        TmpTomlErr::Config(err)
21    }
22}
23
24impl From<ReadFileErr> for TmpTomlErr {
25    fn from(err: ReadFileErr) -> Self {
26        TmpTomlErr::File(err)
27    }
28}
29
30impl From<TeraRenderErr> for TmpTomlErr {
31    fn from(render_err: TeraRenderErr) -> Self {
32        TmpTomlErr::Render(render_err)
33    }
34}
35
36#[derive(Debug)]
37pub enum ReadFileErr {
38    FileNotFound(String),
39}
40
41#[derive(Debug)]
42pub enum TeraRenderErr {
43    TemplateNotFound(String),
44    InvalidTemplate(String),
45    RenderError(tera::Error),
46}
47
48impl From<ReadFileErr> for TeraRenderErr {
49    fn from(err: ReadFileErr) -> Self {
50        match err {
51            ReadFileErr::FileNotFound(path) => TeraRenderErr::TemplateNotFound(path),
52        }
53    }
54}
55
56fn read_file(path: Option<&str>) -> Result<String, ReadFileErr> {
57    match path {
58        Some(path) => {
59            return fs::read_to_string(path)
60                .map_err(|_| ReadFileErr::FileNotFound(path.to_string()));
61        }
62        None => return Err(ReadFileErr::FileNotFound("".to_string())),
63    }
64}
65
66fn build_tera_context(template_values: std::collections::HashMap<String, String>) -> Context {
67    let mut context = Context::new();
68    for (key, value) in template_values {
69        context.insert(key, &value);
70    }
71    return context;
72}
73
74fn render_tera_template(
75    template_file_path: &Path,
76    context: Context,
77) -> Result<String, TeraRenderErr> {
78    let mut tera = Tera::default();
79    let template_name = "template";
80
81    tera.add_template_file(template_file_path, Some(template_name))
82        .map_err(|err| {
83            TeraRenderErr::InvalidTemplate(format!(
84                "Failed to parse template file with error: {}",
85                &err
86            ))
87        })?;
88    return tera
89        .render(template_name, &context)
90        .map_err(|err| TeraRenderErr::RenderError(err));
91}
92
93fn flatten_sections(
94    group_section: &std::collections::HashMap<String, Value>,
95    secondary_group_section_name: &String,
96) -> std::collections::HashMap<String, String> {
97    let mut flattened: std::collections::HashMap<String, String> = std::collections::HashMap::new();
98
99    group_section.iter().for_each(|(key, value)| {
100        if key == secondary_group_section_name {
101            if let toml::Value::Table(table) = value {
102                table.iter().for_each(|(key, value)| {
103                    if !flattened.contains_key(key) {
104                        flattened.insert(key.to_string(), value.to_string());
105                    }
106                });
107            }
108        } else {
109            if let toml::Value::Table(_) = value {
110                } else {
113                if !flattened.contains_key(key) {
114                    flattened.insert(key.to_string(), value.to_string());
115                }
116            }
117        }
118    });
119
120    return flattened;
121}
122
123pub fn parse_toml_to_config(path: Option<&str>) -> Result<Config, TmpTomlErr> {
124    let file_content = read_file(path)?;
125    let toml_config: Config = toml::from_str(&file_content)?;
126    return Ok(toml_config);
127}
128
129pub fn render_template(
130    config_file_path: &PathBuf,
131    template_file_path: &PathBuf,
132    group_id: String,
133    sec_group_id: String,
134) -> Result<String, TmpTomlErr> {
135    let debug_print = false;
136    let toml_config = parse_toml_to_config(config_file_path.to_str())?;
137    if debug_print {
138        println!("Config File:\n{:?}\n", toml_config);
139    }
140
141    if !toml_config.contains_key(&group_id) {
142        return Err(TmpTomlErr::GroupNotFound(group_id));
143    }
144
145    if !toml_config[&group_id].contains_key(&sec_group_id) {
146        return Err(TmpTomlErr::GroupNotFound(sec_group_id));
147    }
148
149    let group_section = &toml_config[&group_id];
150    let sec_group_section = &toml_config[&group_id][&sec_group_id];
151
152    if debug_print {
153        println!("Cofnig File:\n{:?}\n", toml_config);
154        println!("Group\n{:?}\n", &group_id);
155        println!("Group Section\n{:?}\n", &group_section);
156        println!("Secondary Group Group\n{:?}\n", &sec_group_id);
157        println!("{:?} Section\n{:?}\n", sec_group_id, sec_group_section);
158        let sub_group_table = sec_group_section.as_table();
159        match sub_group_table {
160            Some(table) => {
161                table.iter().for_each(|(key, value)| {
162                    println!("{:?} {:?}", key, value);
163                });
164            }
165            None => println!("{:?}", sec_group_section),
166        }
167    }
168
169    let template_values = flatten_sections(group_section, &sec_group_id);
170    if debug_print {
171        println!("Template Values:\n{:?}\n", template_values);
172    }
173    let tera_context = build_tera_context(template_values);
174    let rendered_template = render_tera_template(template_file_path.as_path(), tera_context)?;
175    return Ok(rendered_template);
176}