Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ee02cdf

Browse files
committed
Add config_proc_macro
1 parent aa53d2d commit ee02cdf

File tree

12 files changed

+429
-0
lines changed

12 files changed

+429
-0
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ dirs = "1.0.4"
6060
ignore = "0.4.6"
6161
annotate-snippets = { version = "0.5.0", features = ["ansi_term"] }
6262

63+
config_proc_macro = { path = "config_proc_macro" }
64+
6365
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
6466
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
6567
# for more information.

config_proc_macro/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target/

config_proc_macro/Cargo.lock

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config_proc_macro/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "config_proc_macro"
3+
version = "0.1.0"
4+
authors = ["topecongiro <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
proc-macro2 = "0.4"
12+
quote = "0.6"
13+
syn = { version = "0.15", features = ["full", "visit"] }
14+
15+
[dev-dependencies]
16+
serde = { version = "1.0", features = ["derive"] }
17+
18+
[features]
19+
default = []
20+
debug-with-rustfmt = []

config_proc_macro/src/attrs.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option<String> {
2+
attrs.iter().filter_map(doc_hint).next()
3+
}
4+
5+
pub fn is_doc_hint(attr: &syn::Attribute) -> bool {
6+
is_attr_name_value(attr, "doc_hint")
7+
}
8+
9+
pub fn doc_hint(attr: &syn::Attribute) -> Option<String> {
10+
get_name_value_str_lit(attr, "doc_hint")
11+
}
12+
13+
pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> {
14+
attrs.iter().filter_map(config_value).next()
15+
}
16+
17+
pub fn config_value(attr: &syn::Attribute) -> Option<String> {
18+
get_name_value_str_lit(attr, "value")
19+
}
20+
21+
pub fn is_config_value(attr: &syn::Attribute) -> bool {
22+
is_attr_name_value(attr, "value")
23+
}
24+
25+
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
26+
attr.interpret_meta().map_or(false, |meta| match meta {
27+
syn::Meta::NameValue(syn::MetaNameValue { ref ident, .. }) if ident == name => true,
28+
_ => false,
29+
})
30+
}
31+
32+
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
33+
attr.interpret_meta().and_then(|meta| match meta {
34+
syn::Meta::NameValue(syn::MetaNameValue {
35+
ref ident,
36+
lit: syn::Lit::Str(ref lit_str),
37+
..
38+
}) if ident == name => Some(lit_str.value()),
39+
_ => None,
40+
})
41+
}

config_proc_macro/src/config_type.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use proc_macro2::TokenStream;
2+
3+
use crate::item_enum::define_config_type_on_enum;
4+
use crate::item_struct::define_config_type_on_struct;
5+
6+
pub fn define_config_type(input: &syn::Item) -> TokenStream {
7+
match input {
8+
syn::Item::Struct(st) => define_config_type_on_struct(st),
9+
syn::Item::Enum(en) => define_config_type_on_enum(en),
10+
_ => panic!("Expected enum or struct"),
11+
}
12+
.unwrap()
13+
}

config_proc_macro/src/item_enum.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
4+
use crate::attrs::*;
5+
use crate::utils::*;
6+
7+
type Variants = syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>;
8+
9+
pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream> {
10+
let syn::ItemEnum {
11+
vis,
12+
enum_token,
13+
ident,
14+
generics,
15+
variants,
16+
..
17+
} = em;
18+
19+
let mod_name_str = format!("__define_config_type_on_enum_{}", ident);
20+
let mod_name = syn::Ident::new(&mod_name_str, ident.span());
21+
let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,));
22+
23+
let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants);
24+
let impl_from_str = impl_from_str(&em.ident, &em.variants);
25+
let impl_serde = impl_serde(&em.ident, &em.variants);
26+
let impl_deserialize = impl_deserialize(&em.ident, &em.variants);
27+
28+
Ok(quote! {
29+
#[allow(non_snake_case)]
30+
mod #mod_name {
31+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
32+
pub #enum_token #ident #generics { #variants }
33+
#impl_doc_hint
34+
#impl_from_str
35+
#impl_serde
36+
#impl_deserialize
37+
}
38+
#vis use #mod_name::#ident;
39+
})
40+
}
41+
42+
/// Remove attributes specific to `config_proc_macro` from enum variant fields.
43+
fn process_variant(variant: &syn::Variant) -> TokenStream {
44+
let metas = variant
45+
.attrs
46+
.iter()
47+
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr));
48+
let attrs = fold_quote(metas, |meta| quote!(#meta));
49+
let syn::Variant { ident, fields, .. } = variant;
50+
quote!(#attrs #ident #fields)
51+
}
52+
53+
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
54+
let doc_hint = variants
55+
.iter()
56+
.map(doc_hint_of_variant)
57+
.collect::<Vec<_>>()
58+
.join("|");
59+
let doc_hint = format!("[{}]", doc_hint);
60+
quote! {
61+
use crate::config::ConfigType;
62+
impl ConfigType for #ident {
63+
fn doc_hint() -> String {
64+
#doc_hint.to_owned()
65+
}
66+
}
67+
}
68+
}
69+
70+
fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream {
71+
let vs = variants
72+
.iter()
73+
.filter(|v| is_unit(v))
74+
.map(|v| (config_value_of_variant(v), &v.ident));
75+
let if_patterns = fold_quote(vs, |(s, v)| {
76+
quote! {
77+
if #s.eq_ignore_ascii_case(s) {
78+
return Ok(#ident::#v);
79+
}
80+
}
81+
});
82+
quote! {
83+
impl ::std::str::FromStr for #ident {
84+
type Err = &'static str;
85+
86+
fn from_str(s: &str) -> Result<Self, Self::Err> {
87+
#if_patterns
88+
return Err("Bad variant");
89+
}
90+
}
91+
}
92+
}
93+
94+
fn doc_hint_of_variant(variant: &syn::Variant) -> String {
95+
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string())
96+
}
97+
98+
fn config_value_of_variant(variant: &syn::Variant) -> String {
99+
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string())
100+
}
101+
102+
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
103+
let arms = fold_quote(variants.iter(), |v| {
104+
let v_ident = &v.ident;
105+
let pattern = match v.fields {
106+
syn::Fields::Named(..) => quote!(#ident::v_ident{..}),
107+
syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)),
108+
syn::Fields::Unit => quote!(#ident::#v_ident),
109+
};
110+
let option_value = config_value_of_variant(v);
111+
quote! {
112+
#pattern => serializer.serialize_str(&#option_value),
113+
}
114+
});
115+
116+
quote! {
117+
impl ::serde::ser::Serialize for #ident {
118+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119+
where
120+
S: ::serde::ser::Serializer,
121+
{
122+
use serde::ser::Error;
123+
match self {
124+
#arms
125+
_ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))),
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
// Currently only unit variants are supported.
133+
fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream {
134+
let supported_vs = variants.iter().filter(|v| is_unit(v));
135+
let if_patterns = fold_quote(supported_vs, |v| {
136+
let config_value = config_value_of_variant(v);
137+
let variant_ident = &v.ident;
138+
quote! {
139+
if #config_value.eq_ignore_ascii_case(s) {
140+
return Ok(#ident::#variant_ident);
141+
}
142+
}
143+
});
144+
145+
let supported_vs = variants.iter().filter(|v| is_unit(v));
146+
let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,));
147+
148+
quote! {
149+
impl<'de> serde::de::Deserialize<'de> for #ident {
150+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
151+
where
152+
D: serde::Deserializer<'de>,
153+
{
154+
use serde::de::{Error, Visitor};
155+
use std::marker::PhantomData;
156+
use std::fmt;
157+
struct StringOnly<T>(PhantomData<T>);
158+
impl<'de, T> Visitor<'de> for StringOnly<T>
159+
where T: serde::Deserializer<'de> {
160+
type Value = String;
161+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162+
formatter.write_str("string")
163+
}
164+
fn visit_str<E>(self, value: &str) -> Result<String, E> {
165+
Ok(String::from(value))
166+
}
167+
}
168+
let s = &d.deserialize_string(StringOnly::<D>(PhantomData))?;
169+
170+
#if_patterns
171+
172+
static ALLOWED: &'static[&str] = &[#allowed];
173+
Err(D::Error::unknown_variant(&s, ALLOWED))
174+
}
175+
}
176+
}
177+
}

config_proc_macro/src/item_struct.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use proc_macro2::TokenStream;
2+
3+
pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result<TokenStream> {
4+
unimplemented!()
5+
}

config_proc_macro/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! This crate provides a derive macro for `ConfigType`.
2+
3+
#![recursion_limit = "256"]
4+
5+
extern crate proc_macro;
6+
7+
mod attrs;
8+
mod config_type;
9+
mod item_enum;
10+
mod item_struct;
11+
mod utils;
12+
13+
use proc_macro::TokenStream;
14+
use syn::parse_macro_input;
15+
16+
#[proc_macro_attribute]
17+
pub fn config_type(_args: TokenStream, input: TokenStream) -> TokenStream {
18+
let input = parse_macro_input!(input as syn::Item);
19+
let output = config_type::define_config_type(&input);
20+
21+
#[cfg(feature = "debug-with-rustfmt")]
22+
{
23+
utils::debug_with_rustfmt(&output);
24+
}
25+
26+
TokenStream::from(output)
27+
}

0 commit comments

Comments
 (0)