1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
extern crate heck; extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; mod attrs; use std::env; use std::fmt::Write; use heck::ShoutySnakeCase; use proc_macro::TokenStream; use quote::Tokens; use syn::*; use attrs::{CfgAttrs, FieldAttrs}; #[proc_macro_derive(Configure, attributes(configure))] pub fn derive_configure(input: TokenStream) -> TokenStream { let ast = parse_derive_input(&input.to_string()).unwrap(); let gen = impl_configure(ast); gen.parse().unwrap() } fn impl_configure(ast: DeriveInput) -> Tokens { let ty = &ast.ident; let generics = &ast.generics; let cfg_attrs = CfgAttrs::new(&ast.attrs[..]); let fields = assert_ast_is_struct(&ast); let project = cfg_attrs.name.or_else(|| env::var("CARGO_PKG_NAME").ok()).unwrap(); let docs = if cfg_attrs.docs { Some(docs(fields, &project)) } else { None }; quote!{ impl #generics ::configure::Configure for #ty #generics { fn generate() -> ::std::result::Result<Self, ::configure::DeserializeError> { let deserializer = ::configure::source::CONFIGURATION.get(#project); ::serde::Deserialize::deserialize(deserializer) } } #docs } } fn assert_ast_is_struct(ast: &DeriveInput) -> &[Field] { match ast.body { Body::Struct(VariantData::Struct(ref fields)) => fields, Body::Struct(VariantData::Unit) => &[], Body::Struct(VariantData::Tuple(_)) => { panic!("Cannot derive `Configure` for tuple struct") } Body::Enum(_) => { panic!("Cannot derive `Configure` for enum") } } } fn docs(fields: &[Field], project: &str) -> Tokens { let mut docs = format!("These environment variables can be used to configure {}.\n\n", project); for field in fields { let name = field.ident.as_ref().unwrap(); let ty = &field.ty; let attrs = FieldAttrs::new(field); let var_name = format!("{}_{}", project, name).to_shouty_snake_case(); let var_type = quote! { #ty }; if let Some(field_docs) = attrs.docs { let _ = writeln!(docs, "- **{}** ({}): {}", var_name, var_type, field_docs); } else { let _ = writeln!(docs, "- **{}** ({})", var_name, var_type); } } docs.push_str("\nThis library uses the configure crate to manage its configuration; you can\ also override how configuration is handled using the API in that crate."); quote! { #[doc = #docs] pub mod environment_variables { } } }