1#[cfg(feature = "to_lazy_static")]
2pub mod to_lazy_static;
3
4pub fn read_env<T: Transform>(env_reader: &mut EnvReader<T>) {
11 let raw = String::from_utf8(env_reader.env.clone()).unwrap();
12 let env = raw.split('\n').collect::<Vec<_>>();
13
14 let mut comments_above_key = vec![];
15
16 for line in env {
17 let trimmed = line.trim();
18
19 if trimmed.is_empty() {
20 if env_reader
21 .transformer
22 .remove_comments_if_blank_line_occurs()
23 {
24 comments_above_key = vec![];
26 }
27
28 continue;
29 }
30
31 if trimmed.starts_with('#') {
33 comments_above_key.push(trimmed.replace('#', "//"));
34 continue;
35 }
36
37 let (key, value) = {
42 let split = trimmed
43 .find('=')
44 .unwrap_or_else(|| panic!("No '=' found in: '{}'", trimmed));
45
46 (&trimmed[0..split], &trimmed[split + 1..])
47 };
48
49 let env_type = if let Ok(o) = value.parse::<i32>() {
50 EnvType::I32(o)
51 } else if let Ok(o) = value.parse::<f32>() {
52 EnvType::F32(o)
53 } else if let Ok(o) = value.parse::<bool>() {
54 EnvType::Bool(o)
55 } else {
56 let string_value = value.to_string();
57
58 EnvType::StaticStr(string_value)
61 };
62
63 env_reader
64 .transformer
65 .write(comments_above_key.clone(), key, env_type);
66
67 comments_above_key = vec![];
69 }
70}
71
72pub struct EnvReader<'a, T: Transform> {
73 pub env: Vec<u8>,
74 pub transformer: &'a mut T,
75}
76
77impl<'a, T: Transform> EnvReader<'a, T> {
78 pub fn new(env: Vec<u8>, transformer: &'a mut T) -> EnvReader<'a, T> {
79 EnvReader { env, transformer }
80 }
81}
82
83pub trait CustomMap {
84 fn rust_type(&self) -> String;
85 fn raw_value(&self) -> String;
86 fn value(&self) -> String;
87 #[cfg(feature = "to_lazy_static")]
88 fn transform(&self) -> String;
89}
90
91pub enum EnvType {
93 Bool(bool),
94 I32(i32),
95 I64(i64),
96 I128(i128),
97 U8(u8),
98 U32(u32),
99 U128(u128),
100 F32(f32),
101 F64(f64),
102 USize(usize),
103 StaticStr(String),
104 Custom(Box<dyn CustomMap>),
106}
107
108impl EnvType {
109 pub fn rust_type(&self) -> String {
111 match self {
112 EnvType::Bool(_) => "bool".to_string(),
113 EnvType::I32(_) => "i32".to_string(),
114 EnvType::I64(_) => "i64".to_string(),
115 EnvType::I128(_) => "i128".to_string(),
116 EnvType::U8(_) => "u8".to_string(),
117 EnvType::U32(_) => "u32".to_string(),
118 EnvType::U128(_) => "u128".to_string(),
119 EnvType::F32(_) => "f32".to_string(),
120 EnvType::F64(_) => "f64".to_string(),
121 EnvType::USize(_) => "usize".to_string(),
122 EnvType::StaticStr(_) => "&'static str".to_string(),
123 EnvType::Custom(c) => c.rust_type(),
124 }
125 .replace('\"', "")
126 }
127
128 pub fn raw_value(&self) -> String {
130 match self {
131 EnvType::Bool(val) => val.to_string(),
132 EnvType::I32(val) => val.to_string(),
133 EnvType::I64(val) => val.to_string(),
134 EnvType::I128(val) => val.to_string(),
135 EnvType::U8(val) => val.to_string(),
136 EnvType::U32(val) => val.to_string(),
137 EnvType::U128(val) => val.to_string(),
138 EnvType::F32(val) => val.to_string(),
139 EnvType::F64(val) => val.to_string(),
140 EnvType::USize(val) => val.to_string(),
141 EnvType::StaticStr(val) => format!("\"{}\"", val),
142 EnvType::Custom(c) => c.raw_value(),
143 }
144 }
145
146 pub fn value(&self) -> String {
157 let ty = self.raw_value();
158
159 match self {
160 EnvType::StaticStr(_) | EnvType::Bool(_) => ty,
161 EnvType::Custom(c) => c.value(),
162 _ => ty + &self.rust_type(),
163 }
164 }
165}
166
167pub trait Transform {
169 fn remove_comments_if_blank_line_occurs(&self) -> bool {
179 false
180 }
181
182 fn write(&mut self, comments: Vec<String>, key: &str, inferred_type: EnvType);
184}
185
186#[cfg(test)]
187mod locations {
188 use std::fs::File;
189 use std::io::Read;
190 use std::path::PathBuf;
191
192 pub fn src() -> PathBuf {
193 std::env::current_dir().unwrap().join("src")
194 }
195
196 pub fn env() -> Vec<u8> {
197 include_bytes!("../.env").to_vec()
198 }
199
200 pub fn temp_rs() -> PathBuf {
201 src().join("temp_rs.rs")
202 }
203
204 pub fn check_equals(to_check: &str) {
205 let mut assert_test = String::new();
207 File::open(src().join(to_check))
208 .unwrap()
209 .read_to_string(&mut assert_test)
210 .unwrap();
211
212 let mut temp_rs_string = String::new();
213 File::open(temp_rs())
214 .unwrap()
215 .read_to_string(&mut temp_rs_string)
216 .unwrap();
217
218 assert_eq!(assert_test.replace('\r', ""), temp_rs_string);
220
221 std::fs::remove_file(temp_rs()).unwrap();
222 }
223}
224
225#[test]
226fn test_write() {
227 use crate::locations::{check_equals, env, temp_rs};
228 use std::fs::File;
229 use std::io::Write;
230
231 struct TransformerImpl {
233 file: File,
234 }
235
236 impl Transform for TransformerImpl {
237 fn write(&mut self, comments: Vec<String>, key: &str, inferred_type: EnvType) {
238 for comment in comments {
239 writeln!(&self.file, "{}", comment).unwrap();
240 }
241
242 let inferred_type = if key == "SOME_I64_VAL" {
243 EnvType::I64(inferred_type.raw_value().parse().unwrap())
244 } else {
245 inferred_type
246 };
247
248 let declaration = format!(
249 "pub const {}: {} = {};",
250 key,
251 inferred_type.rust_type(),
252 inferred_type.value()
253 );
254
255 writeln!(&self.file, "{}", declaration).unwrap();
256 }
257 }
258
259 read_env(&mut EnvReader::new(
260 env(),
261 &mut TransformerImpl {
262 file: File::create(&temp_rs()).unwrap(),
263 },
264 ));
265
266 check_equals("assert_test.rs");
267}