1use std::fs::{File, OpenOptions};
2use std::io::{self, BufReader, Read, Write};
3use std::collections::BTreeMap;
4use std::env::consts::OS;
5use std::path::Path;
6
7pub struct Ini {
12 pub config_map: BTreeMap<String, BTreeMap<String, String>>,
13 config_file: String,
14}
15
16const CONFIG_SECTION_START: &str = "[";
17const CONFIG_SECTION_END: &str = "]";
18const CONFIG_KVP_SPLIT: &str = "=";
19const CONFIG_COMMENT: &str = "#";
20
21const NEW_LINE_WINDOWS: &str = "\r\n";
22const NEW_LINE_LINUX: &str = "\n";
23
24fn read_lines_no_stop_on_blank(buf_read: &mut BufReader<File>) -> io::Result<Vec<String>> {
25 let mut ret: Vec<String> = Vec::new();
26 let new_line = match OS {
27 "linux" => NEW_LINE_LINUX,
28 "windows" => NEW_LINE_WINDOWS,
29 _ => return Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported OS"))
30 };
31
32 let mut data: Vec<u8> = Vec::new();
33 buf_read.read_to_end(&mut data)?;
34 let str: String = String::from_utf8_lossy(&data).to_string();
35
36 for v in str.split(new_line) {
37 ret.push(v.to_string());
38 }
39 Ok(ret)
40}
41
42impl Ini {
43 pub fn new(location: String) -> Result<Ini, io::Error> {
46 let mut ret = Ini{ config_map: BTreeMap::new(), config_file: location.clone() };
47
48 if !Path::new(&location).exists() {
49 return Ok(ret);
50 }
51 let f = File::open(&location)?;
52 let mut reader = BufReader::new(f);
53
54 let mut in_section = false;
55
56 let lines = match read_lines_no_stop_on_blank(&mut reader) {
57 Ok(x) => x,
58 Err(e) => return Err(e),
59 };
60
61 println!("Number of lines: {}", lines.len());
62
63 for line in lines {
64 if line.starts_with(CONFIG_COMMENT) {
65 println!("Comment line");
66 continue;
67 }
68 if line.len() == 0 {
69 println!("Blank line");
70 continue;
71 }
72
73 println!("{}", line);
74
75 if line.starts_with(CONFIG_SECTION_START) && line.contains(CONFIG_SECTION_END) {
77 let edit = line.replace(CONFIG_SECTION_START, "").replace(CONFIG_SECTION_END, "").trim().to_string();
78 ret.config_map.insert(edit.clone(), BTreeMap::new());
79 in_section = true;
80 println!("Found a section");
81 continue;
82 }
83 else if line.contains(CONFIG_KVP_SPLIT) {
85 if !in_section {
86 return Err(io::Error::new(io::ErrorKind::InvalidData, "Config file was invalid, KVP entry found before section."));
87 }
88 println!("Found a kvp split");
89 let kvp = match line.split_once(CONFIG_KVP_SPLIT) {
90 Some(x) => x,
91 None => return Err(io::Error::new(io::ErrorKind::InvalidData, "Config file was invalid, KVP entry couldn't be split.")),
92 };
93
94 let mut last = match ret.config_map.last_entry() {
95 Some(x) => x,
96 None => return Err(io::Error::new(io::ErrorKind::InvalidData, "Config file was invalid, KVP entry didn't have a section.")),
97 };
98
99 last.get_mut().insert(kvp.0.to_string(), kvp.1.to_string());
100
101 continue;
102 }
103 else {
104 return Err(io::Error::new(io::ErrorKind::InvalidData, "Config file was invalid, line didn't hit any requirement"));
105 }
106 }
107 println!("Number of sections {}", ret.config_map.len());
108 Ok(ret)
109 }
110
111 pub fn save(&self) -> Result<usize, io::Error> {
116 let new_line = match OS {
117 "linux" => NEW_LINE_LINUX,
118 "windows" => NEW_LINE_WINDOWS,
119 _ => return Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported OS"))
120 };
121
122 let mut file = OpenOptions::new().create(true).write(true).truncate(true).open(&self.config_file)?;
123
124 for (section_k, section_v) in &self.config_map {
125 file.write_all(CONFIG_SECTION_START.as_bytes())?;
126 file.write_all(section_k.as_bytes())?;
127 file.write_all(CONFIG_SECTION_END.as_bytes())?;
128 file.write_all(new_line.as_bytes())?;
129
130 for (k,v) in section_v {
131 file.write_all(k.as_bytes())?;
132 file.write_all(CONFIG_KVP_SPLIT.as_bytes())?;
133 file.write_all(v.as_bytes())?;
134 file.write_all(new_line.as_bytes())?;
135 }
136
137 file.flush()?;
138 }
139
140 file.flush()?;
141 file.sync_all()?;
142
143 Ok(file.metadata()?.len() as usize)
144 }
145
146
147 pub fn get(&self, section: &str, key: &str) -> Option<String> {
149 if let Some(section_map) = self.config_map.get(section) {
150 if let Some(value) = section_map.get(key) {
151 return Some(value.clone());
152 }
153 }
154 None
155 }
156
157 pub fn set(&mut self, section: &str, key: &str, value: &str) {
162 let section_map = self.config_map.entry(section.to_string()).or_insert(BTreeMap::new());
163 section_map.insert(key.to_string(), value.to_string());
164 }
165
166 pub fn remove(&mut self, section: &str, key: &str) {
171 if let Some(section_map) = self.config_map.get_mut(section) {
172 section_map.remove(key);
173 }
174 }
175
176 pub fn remove_section(&mut self, section: &str) {
179 self.config_map.remove(section);
180 }
181}