use std::collections::HashMap;
use std::path::Path;
use crate::compose::types::EnvFileEntry;
use crate::error::{ComposeError, Result};
pub fn load_env_files(paths: &[String], base_dir: &Path) -> Result<HashMap<String, String>> {
let entries: Vec<EnvFileEntry> = paths
.iter()
.map(|p| EnvFileEntry::Path(p.clone()))
.collect();
load_env_file_entries(&entries, base_dir)
}
pub fn load_env_file_entries(
entries: &[EnvFileEntry],
base_dir: &Path,
) -> Result<HashMap<String, String>> {
let mut result: HashMap<String, String> = HashMap::new();
for entry in entries {
if let EnvFileEntry::Config {
format: Some(fmt), ..
} = entry
{
if fmt != "dotenv" {
return Err(ComposeError::Unsupported(format!(
"env_file format '{fmt}' not supported (only 'dotenv')"
)));
}
}
let abs = base_dir.join(entry.path());
let content = match std::fs::read_to_string(&abs) {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if entry.required() {
return Err(ComposeError::FileNotFound(abs.display().to_string()));
} else {
continue;
}
}
Err(e) => return Err(ComposeError::Io(e)),
};
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let (key, value) = if let Some(eq) = trimmed.find('=') {
let k = trimmed[..eq].trim().to_string();
let v = trimmed[eq + 1..].to_string();
(k, v)
} else {
(trimmed.to_string(), String::new())
};
if key.is_empty() {
continue;
}
result.entry(key).or_insert(value);
}
}
Ok(result)
}
pub fn merge_env(
service_env: HashMap<String, Option<String>>,
env_file_vars: HashMap<String, String>,
) -> Vec<String> {
let mut merged = service_env;
for (k, v) in env_file_vars {
merged.entry(k).or_insert(Some(v));
}
merged
.into_iter()
.map(|(k, v)| match v {
Some(val) => format!("{k}={val}"),
None => k,
})
.collect()
}