use std::collections::BTreeMap;
use std::io;
use std::path::{Path, PathBuf};
const USER_ENV_FILE_ENV: &str = "AMAGI_USER_ENV_FILE";
pub type DotenvMap = BTreeMap<String, String>;
pub fn dotenv_values() -> io::Result<DotenvMap> {
dotenv_values_from_layers(user_dotenv_path(), Some(PathBuf::from(".env")))
}
pub fn user_dotenv_values() -> io::Result<DotenvMap> {
match user_dotenv_path() {
Some(path) => dotenv_values_from_path(path),
None => Ok(DotenvMap::new()),
}
}
pub fn user_dotenv_path() -> Option<PathBuf> {
let override_path = std::env::var(USER_ENV_FILE_ENV).ok();
let appdata = std::env::var("APPDATA").ok();
let home = std::env::var("HOME").ok();
resolve_user_dotenv_path(
override_path.as_deref(),
appdata.as_deref(),
home.as_deref(),
)
}
pub fn dotenv_values_from_layers<U, P>(
user_path: Option<U>,
project_path: Option<P>,
) -> io::Result<DotenvMap>
where
U: AsRef<Path>,
P: AsRef<Path>,
{
let mut values = DotenvMap::new();
if let Some(path) = user_path {
values.extend(dotenv_values_from_path(path)?);
}
if let Some(path) = project_path {
values.extend(dotenv_values_from_path(path)?);
}
Ok(values)
}
pub fn dotenv_values_from_path(path: impl AsRef<Path>) -> io::Result<DotenvMap> {
match dotenvy::from_path_iter(path.as_ref()) {
Ok(iter) => iter
.map(|entry| entry.map_err(dotenv_error_to_io))
.collect::<io::Result<DotenvMap>>(),
Err(error) if error.not_found() => Ok(DotenvMap::new()),
Err(error) => Err(dotenv_error_to_io(error)),
}
}
pub fn env_or_dotenv(name: &str, dotenv: &DotenvMap) -> Option<String> {
std::env::var(name)
.ok()
.or_else(|| dotenv.get(name).cloned())
}
fn resolve_user_dotenv_path(
override_path: Option<&str>,
appdata: Option<&str>,
home: Option<&str>,
) -> Option<PathBuf> {
if let Some(path) = override_path.map(str::trim).filter(|path| !path.is_empty()) {
return Some(PathBuf::from(path));
}
#[cfg(windows)]
{
let _ = home;
appdata
.map(str::trim)
.filter(|path| !path.is_empty())
.map(PathBuf::from)
.map(|path| path.join("amagi").join(".env"))
}
#[cfg(not(windows))]
{
let _ = appdata;
home.map(str::trim)
.filter(|path| !path.is_empty())
.map(PathBuf::from)
.map(|path| path.join(".config").join("amagi").join(".env"))
}
}
fn dotenv_error_to_io(error: dotenvy::Error) -> io::Error {
let kind = if error.not_found() {
io::ErrorKind::NotFound
} else {
io::ErrorKind::InvalidData
};
io::Error::new(kind, error)
}