From 63163d57d9f6a58b89948089360b497940ac467c Mon Sep 17 00:00:00 2001 From: yuk1ty Date: Fri, 8 Jul 2022 10:35:28 +0900 Subject: [PATCH] Implement rename helper attribute --- README.md | 12 ++++++++++++ econf-derive/src/lib.rs | 41 ++++++++++++++++++++++++++++++++++++----- econf/tests/basics.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0129af9..96533b4 100644 --- a/README.md +++ b/README.md @@ -150,5 +150,17 @@ struct A { } ``` +## Renaming fields + +Load a field with the given name instead of its Rust's field name. This is helpful if the environment variable name and Rust's field name don't match: + +``` +#[derive(LoadEnv)] +struct A { + x: bool, + #[econf(rename = "ANOTHER_Y")] + y: u64, // will be loaded from an environment variable `ANOTHER_Y` +} +``` License: MIT diff --git a/econf-derive/src/lib.rs b/econf-derive/src/lib.rs index 2ed071f..d9c5839 100644 --- a/econf-derive/src/lib.rs +++ b/econf-derive/src/lib.rs @@ -4,7 +4,7 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; -use syn::{Data, DeriveInput, Field, Fields, Meta, NestedMeta, parse_macro_input}; +use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta}; #[proc_macro_derive(LoadEnv, attributes(econf))] pub fn load_env(input: TokenStream) -> TokenStream { @@ -34,6 +34,27 @@ fn is_skip(f: &Field) -> bool { }) } +fn find_renaming(f: &Field) -> Option { + f.attrs + .iter() + .filter(|attr| attr.path.is_ident("econf")) + .filter_map(|attr| match attr.parse_meta().unwrap() { + Meta::List(meta) => Some(meta.nested), + _ => None, + }) + .flat_map(|nested| nested.into_iter()) + .filter_map(|nested| match nested { + NestedMeta::Meta(Meta::NameValue(value)) if value.ident.to_string() == "rename" => { + Some(value) + } + _ => None, + }) + .find_map(|value| match value.lit { + Lit::Str(token) => Some(token.value()), + _ => None, + }) +} + fn content(name: &Ident, data: &Data) -> TokenStream2 { match data { Data::Struct(data) => match &data.fields { @@ -45,8 +66,13 @@ fn content(name: &Ident, data: &Data) -> TokenStream2 { #ident: self.#ident, }; } - quote! { - #ident: self.#ident.load(&(path.to_owned() + "_" + stringify!(#ident)), loader), + match find_renaming(f) { + Some(overwritten_name) => quote! { + #ident: self.#ident.load(&(path.to_owned() + "_" + #overwritten_name), loader), + }, + None => quote! { + #ident: self.#ident.load(&(path.to_owned() + "_" + stringify!(#ident)), loader), + } } }); quote! { @@ -62,8 +88,13 @@ fn content(name: &Ident, data: &Data) -> TokenStream2 { if is_skip(f) { return quote! { self.#i, }; } - quote! { - self.#i.load(&(path.to_owned() + "_" + &#i.to_string()), loader), + match find_renaming(f) { + Some(overwritten_name) => quote! { + self.#i.load(&(path.to_owned() + "_" + #overwritten_name), loader), + }, + None => quote! { + self.#i.load(&(path.to_owned() + "_" + &#i.to_string()), loader), + }, } }); quote! { diff --git a/econf/tests/basics.rs b/econf/tests/basics.rs index 35a8d84..c5d2e59 100644 --- a/econf/tests/basics.rs +++ b/econf/tests/basics.rs @@ -356,6 +356,40 @@ fn skipped() { assert_eq!(a.v3.s, "initial".to_string()); } +#[derive(LoadEnv)] +struct NestedRenamed { + s: String, +} + +#[derive(LoadEnv)] +struct Renamed { + v1: bool, + #[econf(rename = "example_1")] + v2: u32, + #[econf(rename = "example_2")] + v3: NestedRenamed, +} + +#[test] +fn renamed() { + std::env::set_var("RENAMED_V1", "true"); + std::env::set_var("RENAMED_EXAMPLE_1", "42"); + std::env::set_var("RENAMED_EXAMPLE_2_S", "renamed text"); + + let a = Renamed { + v1: false, + v2: 0, + v3: NestedRenamed { + s: "initial".to_string(), + }, + }; + + let a = econf::load(a, "renamed"); + assert_eq!(a.v1, true); + assert_eq!(a.v2, 42); + assert_eq!(a.v3.s, "renamed text".to_string()); +} + #[derive(LoadEnv)] struct Net { n1: IpAddr,