use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::{env, fs, path::Path};
pub fn generate_env() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let project_dir = Path::new(&manifest_dir).parent().unwrap();
let env_path = project_dir.join(".env");
let output_path = Path::new(&manifest_dir).join("src/env_generated.rs");
println!("cargo:rerun-if-changed=../.env");
let env_vars = parse_env_file(&env_path);
let tokens = generate_env_code(&env_vars);
let syntax_tree = syn::parse2::<syn::File>(tokens).expect("Failed to parse generated env code");
let formatted = prettyplease::unparse(&syntax_tree);
let current_content = fs::read_to_string(&output_path).unwrap_or_default();
if current_content != formatted {
fs::write(&output_path, formatted).unwrap();
}
}
#[derive(Debug)]
struct EnvVar {
key: String,
}
fn parse_env_file(path: &Path) -> Vec<EnvVar> {
let mut vars = Vec::new();
let Ok(content) = fs::read_to_string(path) else {
return vars;
};
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, _)) = line.split_once('=') {
vars.push(EnvVar { key: key.trim().to_string() });
}
}
vars
}
fn to_snake_case(s: &str) -> String {
s.to_lowercase()
}
fn generate_env_code(vars: &[EnvVar]) -> TokenStream {
if vars.is_empty() {
return quote! {
};
}
let fn_declarations: Vec<TokenStream> = vars
.iter()
.map(|var| {
let fn_name = format_ident!("{}", to_snake_case(&var.key));
let static_name = format_ident!("{}", var.key.to_uppercase());
let key_str = &var.key;
quote! {
pub fn #fn_name() -> &'static str {
static #static_name: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
std::env::var(#key_str).unwrap()
});
&#static_name
}
}
})
.collect();
quote! {
#![allow(dead_code)]
#(#fn_declarations)*
}
}