use std::io::Write; #[derive(Clone, Copy)] pub enum EnvFileType { DotEnv, Yaml, } #[derive(Clone)] pub struct EnvFile { pub file_type: EnvFileType, // buffer: serde_json::Value, } impl EnvFile { pub fn new(file_type: EnvFileType) -> Self { EnvFile { file_type, buffer: serde_json::Value::Object(serde_json::Map::new()), } } pub fn write_line(&mut self, key: &str, value: &str) { self._write_line_buffer(key, value); } pub fn write(&mut self, stream: &mut dyn Write, with_prefix: bool) { self._write_buffer(stream, with_prefix); } fn key_into_buffer_key(&self, key: &str) -> Vec { key.split("__").map(String::from).collect() } fn _write_line_buffer(&mut self, key: &str, value: &str) { let buffer_key = self.key_into_buffer_key(key); let mut current = &mut self.buffer; for k in &buffer_key[0..(buffer_key.len() - 1)] { if current.get(k).is_none() { current[k] = serde_json::Value::Object(serde_json::Map::new()); } current = &mut current[k]; } current[buffer_key.last().unwrap()] = serde_json::Value::String(value.to_string()); } fn _write_buffer(&self, file: &mut dyn Write, with_prefix: bool) { match self.file_type { EnvFileType::DotEnv => self._write_buffer_env(file, with_prefix), EnvFileType::Yaml => self._write_buffer_yaml(file), } } fn _write_buffer_env(&self, file: &mut dyn Write, with_prefix: bool) { fn _write_buffer_env_layer( file: &mut dyn Write, buffer: &serde_json::Value, prefix: String, with_root_prefix: bool, ) { if let serde_json::Value::Object(map) = buffer { for (key, value) in map { let current_key = if prefix.is_empty() { if with_root_prefix { format!("YANPM__{}", key) } else { key.to_string() } } else { format!("{}__{}", prefix, key) }; match value { serde_json::Value::Object(_) => { _write_buffer_env_layer(file, value, current_key, with_root_prefix); } _ => { writeln!(file, "{}={}", current_key, value).unwrap(); } } } } } _write_buffer_env_layer(file, &self.buffer, String::new(), with_prefix); } fn _write_buffer_yaml(&self, file: &mut dyn Write) { let mut layer = 0; fn _write_buffer_yaml_layer( file: &mut dyn Write, buffer: &serde_json::Value, layer: &mut usize, ) { if let serde_json::Value::Object(map) = buffer { for (key, value) in map { let indent = " ".repeat(*layer); match value { serde_json::Value::Object(_) => { writeln!(file, "{}{}:", indent, key).unwrap(); *layer += 1; _write_buffer_yaml_layer(file, value, layer); *layer -= 1; } _ => { writeln!(file, "{}{}: {}", indent, key, value).unwrap(); } } } } } _write_buffer_yaml_layer(file, &self.buffer, &mut layer); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_env_file_write_yaml() { let mut env_file_nested = EnvFile::new(EnvFileType::Yaml); env_file_nested.write_line("DATABASE__TYPE", "SQLite"); env_file_nested.write_line("DATABASE__URL", "mysql://user:pass@localhost/db"); let mut output_stream = Vec::new(); env_file_nested.write(&mut output_stream, false); let output_string = String::from_utf8(output_stream).unwrap(); let expected_output = "\ DATABASE: TYPE: \"SQLite\" URL: \"mysql://user:pass@localhost/db\" "; assert_eq!(output_string, expected_output); } #[test] fn test_env_file_write_env() { let mut env_file_nested = EnvFile::new(EnvFileType::DotEnv); env_file_nested.write_line("DATABASE__TYPE", "PostgreSQL"); env_file_nested.write_line("DATABASE__URL", "postgres://user:pass@localhost/db"); let mut output_stream = Vec::new(); env_file_nested.write(&mut output_stream, true); let output_string = String::from_utf8(output_stream).unwrap(); let expected_output = "\ YANPM__DATABASE__TYPE=\"PostgreSQL\" YANPM__DATABASE__URL=\"postgres://user:pass@localhost/db\" "; assert_eq!(output_string, expected_output); } }