forte-sdk 0.1.3

Core SDK for the Forte web framework
Documentation
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! {
            // Auto-generated by build.rs - no environment variables found in .env
        };
    }

    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)*
    }
}