config-plus-macro 0.2.2

对config库的增强
Documentation
use proc_macro::TokenStream;
use std::env;
use std::fmt::{Display, Formatter};
use darling::{FromDeriveInput, FromMeta};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Configuration, attributes(config))]
pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
    let input: DeriveInput = parse_macro_input!(input);

    // parse the input to the ValidationData struct defined above
    let configuration = ConfigurationData::from_derive_input(&input).unwrap();
    expand(configuration).into()
}

fn expand(configuration: ConfigurationData) -> TokenStream2 {
    let ident = configuration.ident;
    // let generics = configuration.generics;
    let prefix = configuration.prefix;
    let files = configuration.file;
    let envs = configuration.env;
    let environment = configuration.environment;
    let https = configuration.http;
    // let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let file_sources = file_source(envs, files);
    let environment_source = environment_source(environment);
    let http_sources = http_source(https);
    let get_result = process_prefix(prefix, &ident);
    quote! {
        // impl #impl_generics #ident #ty_generics #where_clause {
        impl #ident {
            pub fn config()  -> &'static Self {
                static CONFIG: ::std::sync::LazyLock<#ident>  = ::std::sync::LazyLock::new(|| {
                    ::config_plus::Config::builder()
                        #(#file_sources)* 
                        #environment_source
                        #(#http_sources)*
                        .build()
                        .unwrap()
                        #get_result
                });
                &CONFIG
            }
        }
    }
}

/// 添加文件源
fn file_source(envs:Vec<EnvData>, files: Vec<FileData>) -> Vec<TokenStream2> {
    let mut ts = vec![];

    for file in files {
        let path = file.path;
        let require = file.require;
        let format = file.format;
        match format { 
            Some(format) => {
                let format = format.to_string();
                ts.push(quote! {
                    .add_source(
                        ::config_plus::get_file(#path, #format).required(#require),
                    )
                })
            },
            None => {
                ts.push(quote! {
                    .add_source(
                        ::config_plus::File::with_name(#path).required(#require),
                    )
                })
            }
        }
    }

    for env in envs {
        let name = env.name;
        let require = env.require;
        let format = env.format;
        // 获取 HOME 环境变量
        match env::var(name) {
            Ok(path) => {
                let path = path.trim();
                match format {
                    Some(format) => {
                        let format = format.to_string();
                        ts.push(quote! {
                            .add_source(
                                ::config_plus::get_file(#path, #format).required(#require),
                            )
                        })
                    },
                    None => {
                        ts.push(quote! {
                            .add_source(
                                ::config_plus::File::with_name(#path).required(#require),
                            )
                        })
                    }
                }
            },
            Err(_) => {},
        }
    }
    ts
}

/// 添加环境源
fn environment_source(environment: bool) -> TokenStream2 {
    let mut ts = quote! {};

    if environment {
        ts.extend(quote! {
            .add_source(
                ::config_plus::Environment::with_prefix("")
                        .separator(".")
                        .prefix_separator("")
                        .list_separator(",")
            )
        })
    }
    ts
}

/// 添加http源
fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
    let mut ts = vec![];

    for http in https {
        let url = http.url;
        let format = http.format.to_string();
        let method = http.method.to_string();
        ts.push(quote! {
            .add_source(
                ::config_plus::Http::with(#url, #format, #method)
            )
        })
    }
    ts
}

/// 处理前缀
fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
    let mut ts = quote! {};

    if prefix.is_empty() {
        return quote! {
            .try_deserialize()
            .unwrap()
        };
    }
    let split = prefix.split(".");

    // 元素个数
    let count = split.clone().count();
    for (index, str) in split.enumerate() {
        if count == 1 {
            ts.extend(quote! {
                .get::<#ident>(#str)
                .unwrap()
            });
            return ts;
        }

        if index == 0 {
            ts.extend(quote! {
                .get_table(#str)
                .unwrap()
            })
        } else if count == index + 1 {
            ts.extend(quote! {
                .get(#str)
                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
                .unwrap()
                .clone()
                .try_deserialize()
                .unwrap()
            })
        } else {
            ts.extend(quote! {
                .get(#str)
                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
                .unwrap()
                .clone()
                .into_table()
                .unwrap()
            })
        }
    }
    ts
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(config), supports(struct_named))]
struct ConfigurationData {
    /// 结构体名
    ident: syn::Ident,
    // /// 泛型参数
    // generics: syn::Generics,

    /// 配置前缀
    #[darling(default)]
    prefix: String,

    /// 配置文件路径
    #[darling(multiple, default)]
    file: Vec<FileData>,

    /// 通过指定环境变量来指定文件路径
    #[darling(multiple, default)]
    env: Vec<EnvData>,

    /// 配置http
    #[darling(multiple, default)]
    http: Vec<HttpData>,

    /// 是否从环境变量中读取,默认为true
    #[darling(default = env_default)]
    environment: bool,
}

/// 文件
#[derive(Debug, FromMeta)]
struct FileData{
    path: String,
    #[darling(default)]
    require: bool,
    format: Option<Format>,
}

/// 环境变量
#[derive(Debug, FromMeta)]
struct EnvData{
    name: String,
    #[darling(default)]
    require: bool,
    format: Option<Format>,
}

/// http
#[derive(Debug, FromMeta)]
struct HttpData{
    url: String,
    #[darling(default)]
    method: Method,
    format: Format,
}

#[derive(Debug, FromMeta)]
enum Method {
    Get,
    Post,
}

impl Default for Method {
    fn default() -> Self {
        Method::Get
    }
}

impl Display for Method{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Method::Get =>  write!(f, "get"),
            Method::Post =>  write!(f, "post"),
        }
    }
}

#[derive(Debug, FromMeta)]
enum Format {
    /// TOML (parsed with toml)
    Toml,

    /// JSON (parsed with `serde_json`)
    Json,

    /// YAML (parsed with `yaml_rust2`)
    Yaml,

    /// YAML (parsed with `yaml_rust2`)
    Yml,

    /// INI (parsed with `rust_ini`)
    Ini,

    /// RON (parsed with ron)
    Ron,

    /// JSON5 (parsed with json5)
    Json5,
}


impl Display for Format{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Format::Toml =>  write!(f, "toml"),
            Format::Json =>  write!(f, "json"),
            Format::Yaml =>  write!(f, "yaml"),
            Format::Yml =>  write!(f, "yaml"),
            Format::Ini =>  write!(f, "ini"),
            Format::Ron =>  write!(f, "ron"),
            Format::Json5 =>  write!(f, "json5"),
        }
    }
}

fn env_default() -> bool {
    true
}