1use std::collections::HashMap;
2use std::fs::File;
3use std::io::Read;
4
5#[derive(Debug, Clone)]
6pub struct INI;
7
8#[derive(Debug, Clone)]
9pub struct Section {
10 pub name: String,
11 pub sub: HashMap<String, Vec<String>>,
12}
13
14
15
16#[macro_export]
39macro_rules! ini_str {
40 {$($data: expr),+} => {{
41 ($($crate::from_str($data)),+)
42 }};
43}
44
45#[macro_export]
50macro_rules! ini_file {
51 {$($data: expr),+} => {{
52 ($($crate::from_file($data)),+)
53 }};
54}
55
56impl Default for Section {
57 fn default() -> Self {
58 Self {
59 name: String::new(),
60 sub: HashMap::new(),
61 }
62 }
63}
64
65impl Section {
66 pub fn is_empty(&self) -> bool {
67 self.name.is_empty()
68 }
69 pub fn clear(&mut self) {
70 self.name = String::new();
71 self.sub.clear();
72 }
73}
74
75
76pub fn from_str(s: &str) -> Result<Vec<Section>, String> {
77 let data = s
78 .trim()
79 .split("\n")
80 .filter(|x| {
81 let xx = x.trim();
82 !(xx.starts_with(";") || xx.starts_with("#"))
83 })
84 .map(|x| {
85 x.trim()
86 .split("=")
87 .map(|x| x.trim())
88 .filter(|x| {
89 !x.to_string().eq("")
90 })
91 .collect::<Vec<_>>()
92 })
93 .filter(|x| !x.is_empty())
94 .collect::<Vec<_>>();
95
96 let mut tmp_section = Section::default();
97 let mut tmp_map: HashMap<String, Vec<String>> = HashMap::new();
98 let mut amaps = Vec::new();
99 for x in data {
100 if x[0].starts_with("[") && x[0].ends_with("]") {
101 if !tmp_section.is_empty() {
102 if !tmp_map.is_empty() {
103 tmp_section.sub = tmp_map.clone();
104 tmp_map.clear();
105 }
106 amaps.push(tmp_section.clone());
107 tmp_section.clear();
108 }
109 tmp_section.name = x[0].trim_start_matches('[').trim_end_matches(']').to_string();
110 continue;
111 }
112 if x.len() < 2 {
113 tmp_map.insert(x[0].to_string(), Vec::new());
114 continue;
115 }
116
117 tmp_map.entry(x[0].to_string()).or_insert_with(Vec::new).push(x[1].to_string());
118 }
119 if !tmp_section.is_empty() {
120 if !tmp_map.is_empty() {
121 tmp_section.sub = tmp_map.clone();
122 }
123 amaps.push(tmp_section.clone());
124 }
125 Ok(amaps)
126}
127
128pub fn from_file(path: &str) -> Result<Vec<Section>, String> {
129 return match File::open(path) {
130 Ok(mut s) => {
131 let mut buf = String::new();
132 return match s.read_to_string(&mut buf) {
133 Ok(_) => { from_str(buf.as_str()) }
134 Err(err) => { Err(err.to_string()) }
135 };
136 }
137 Err(err) => { Err(err.to_string()) }
138 };
139}
140
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn it_works() {
148 let a = "[Interface]
149Address = 10.1.1.2/24
150PrivateKey = keykeykeykey
151ListenPort = 51820
152DNS = 8.8.8.8
153
154[Peer]
155PublicKey = keykeykeykeykeykeykeykey
156Endpoint = 1.1.1.1:51820
157AllowedIPs = 10.1.1.5/32
158AllowedIPs = 10.1.1.2/32
159
160[Peer]
161PublicKey = keykeykeykeykeykeykeykeykeykeykeykey
162PresharedKey = keykeykeykeykeykeykeykeykeykeykeykeykeykeykeykey
163Endpoint = 2.2.2.2:51820
164AllowedIPs = 10.13.13.0/24
165PersistentKeepalive = 25";
166
167 println!("{:#?}", ini_str!(a));
168 }
169}