1extern crate proc_macro;
2
3use darling::{
4 ast::{self, Data, Fields},
5 FromField, FromVariant,
6};
7use darling::{FromDeriveInput, FromMeta};
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10use syn::{
11 parse_macro_input, Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, Meta, PathArguments,
12 Type, TypePath,
13};
14mod serde_parse;
15
16#[proc_macro_derive(TomlInput, attributes(toml_input))]
17pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
18 let input = parse_macro_input!(tokens as DeriveInput);
19 let StructRaw {
20 ident,
21 attrs,
22 data,
23 enum_style,
24 } = StructRaw::from_derive_input(&input).unwrap();
25 let config = Config {
26 enum_style,
27 ..Default::default()
28 };
29 let schema_token;
30 let value_token;
31 match data {
32 Data::Enum(variants) => {
33 schema_token = quote_enum_schema(&ident, &attrs, variants, config);
34 value_token = quote_enum_value();
35 }
36 Data::Struct(fields) => {
37 schema_token = quote_struct_schema(&ident, &attrs, fields.clone(), config);
38 value_token = quote_struct_value(&attrs, fields.clone());
39 }
40 }
41 let token = quote! {
42 impl toml_input::TomlInput for #ident {
43 fn schema() -> Result<toml_input::Schema, toml_input::Error> {
44 use toml;
45 use toml_input::schema;
46 use toml_input::config::EnumStyle;
47 #schema_token
48 }
49 fn into_value(self) -> Result<toml_input::Value, toml_input::Error> {
50 #value_token
51 }
52 }
53 };
54 token.into()
55}
56
57fn quote_enum_schema(
58 ident: &Ident,
59 attrs: &[Attribute],
60 variants: Vec<VariantRaw>,
61 config: Config,
62) -> TokenStream {
63 let enum_ident = ident;
64 let enum_docs = parse_docs(attrs);
65 let inner_type = enum_ident.to_string();
66 let mut tokens = Vec::new();
67 for variant in variants {
68 let VariantRaw { attrs, enum_style } = variant;
69 let variant_docs = parse_docs(&attrs);
70 let variant_config = Config {
71 enum_style: enum_style.or(config.enum_style.clone()),
72 ..Default::default()
73 };
74 let enum_style_token = variant_config.enum_style_token(quote! {variant});
75 let variant_token = quote! {
76 let mut variant = schema::VariantSchema::default();
77 variant.docs = #variant_docs.to_string();
78 let value = variant_iter.next().ok_or(toml_input::Error::EnumEmpty)?;
79 let tag = std::convert::AsRef::as_ref(&value).to_string();
80 let raw = toml::Value::try_from(value)?;
81 let prim_value = toml_input::PrimValue {tag, raw: Some(raw)};
82 variant.value = prim_value;
83 #enum_style_token
84 prim_schema.variants.push(variant);
85 };
86 tokens.push(variant_token);
87 }
88 let enum_style_token = config.enum_style_token(quote! {meta});
89 let enum_token = quote! {
90 use strum::IntoEnumIterator;
91 let default = <#enum_ident as Default>::default();
92 let mut prim_schema = schema::PrimSchema::default();
93 let mut meta = schema::Meta::default();
94 meta.wrap_type = "".to_string();
95 meta.inner_type = #inner_type.to_string();
96 let tag = default.as_ref().to_string();
97 let raw = toml::Value::try_from(default)?;
98 meta.inner_default = toml_input::PrimValue{tag, raw: Some(raw)};
99 meta.defined_docs = #enum_docs.to_string();
100 #enum_style_token;
101 prim_schema.meta = meta;
102 let mut variant_iter = #enum_ident::iter();
103 prim_schema.variants = Vec::new();
104 #(#tokens)*
105 Ok(schema::Schema::Prim(prim_schema))
106 };
107 enum_token
108}
109
110fn quote_enum_value() -> TokenStream {
111 let enum_token = quote! {
112 let tag = self.as_ref().to_string();
113 let raw = toml::Value::try_from(self)?;
114 let prim = toml_input::PrimValue {tag, raw: Some(raw)};
115 Ok(toml_input::Value::Prim(prim))
116 };
117 enum_token
118}
119
120fn quote_struct_schema(
121 ident: &Ident,
122 attrs: &[Attribute],
123 fields: Fields<FieldRaw>,
124 config: Config,
125) -> TokenStream {
126 let struct_ident = ident;
127 let struct_docs = parse_docs(attrs);
128 let inner_type = struct_ident.to_string();
129 let struct_rule = serde_parse::rename_rule(attrs);
130 let mut tokens = Vec::new();
131 for field in fields {
132 let FieldRaw {
133 ident,
134 attrs,
135 ty,
136 enum_style,
137 inner_default,
138 } = field;
139 let field_ident = ident.unwrap();
140 let field_docs = parse_docs(&attrs);
141 let field_rule = serde_parse::rename_rule(&attrs);
142 let field_name = field_ident.to_string();
143 let field_name = struct_rule.case_to(field_name);
144 let field_name = field_rule.alias(field_name);
145 let field_flatten = serde_parse::flatten(&attrs);
146 let field_config = Config {
147 enum_style: enum_style.or(config.enum_style.clone()),
148 inner_default,
149 };
150 let enum_style_token = field_config.enum_style_token(quote! {field});
151 let inner_type = extract_inner_type(&ty);
152 let inner_default_token = field_config.inner_default_token(quote! {field}, inner_type);
153 let field_token = quote! {
154 let mut field = schema::FieldSchema::default();
155 field.ident = #field_name.to_string();
156 field.docs = #field_docs.to_string();
157 field.flat = #field_flatten;
158 field.schema = <#ty as toml_input::TomlInput>::schema()?;
159 #enum_style_token
160 #inner_default_token
161 table.fields.push(field);
162 };
163 tokens.push(field_token);
164 }
165 let enum_style_token = config.enum_style_token(quote! {table});
166 let struct_token = quote! {
167 use std::str::FromStr;
168 let default = <#struct_ident as Default>::default();
169 let mut table = schema::TableSchema::default();
170 let mut meta = schema::Meta::default();
171 meta.wrap_type = "".to_string();
172 meta.inner_type = #inner_type.to_string();
173 let raw = toml::Value::try_from(default)?;
174 meta.inner_default = toml_input::PrimValue::new(raw);
175 meta.defined_docs = #struct_docs.to_string();
176 table.meta = meta;
177 #enum_style_token
178 table.fields = Vec::new();
179 #(#tokens)*
180 Ok(schema::Schema::Table(table))
181 };
182 struct_token
183}
184
185fn quote_struct_value(attrs: &[Attribute], fields: Fields<FieldRaw>) -> TokenStream {
186 let struct_rule = serde_parse::rename_rule(attrs);
187 let mut tokens = Vec::new();
188 for field in fields {
189 let FieldRaw { ident, attrs, .. } = field;
190 let field_ident = ident.unwrap();
191 let field_rule = serde_parse::rename_rule(&attrs);
192 let field_name = field_ident.to_string();
193 let field_name = struct_rule.case_to(field_name);
194 let field_name = field_rule.alias(field_name);
195 let field_flatten = serde_parse::flatten(&attrs);
196 let field_token = quote! {
197 let mut field = toml_input::FieldValue::default();
198 field.ident = #field_name.to_string();
199 field.flat = #field_flatten;
200 field.value = self.#field_ident.into_value()?;
201 table.fields.push(field);
202 };
203 tokens.push(field_token);
204 }
205 let struct_token = quote! {
206 let mut table = toml_input::TableValue::default();
207 #(#tokens)*
208 Ok(toml_input::Value::Table(table))
209 };
210 struct_token
211}
212
213#[derive(Debug, Clone, FromDeriveInput)]
214#[darling(
215 supports(struct_named, enum_any),
216 attributes(toml_input),
217 forward_attrs(doc, serde)
218)]
219struct StructRaw {
220 ident: Ident,
221 attrs: Vec<Attribute>,
222 data: ast::Data<VariantRaw, FieldRaw>,
223 enum_style: Option<EnumStyle>,
224}
225
226#[derive(Debug, Clone, FromField)]
227#[darling(attributes(toml_input), forward_attrs(doc, serde))]
228struct FieldRaw {
229 ident: Option<Ident>,
230 attrs: Vec<Attribute>,
231 ty: Type,
232 enum_style: Option<EnumStyle>,
233 inner_default: Option<String>,
234}
235
236#[derive(Debug, Clone, FromVariant)]
237#[darling(attributes(toml_input), forward_attrs(doc, serde))]
238struct VariantRaw {
239 attrs: Vec<Attribute>,
240 enum_style: Option<EnumStyle>,
241}
242
243fn parse_docs(attrs: &[Attribute]) -> String {
244 let mut docs = Vec::new();
245 for attr in attrs {
246 if !attr.path().is_ident("doc") {
247 continue;
248 }
249 if let Meta::NameValue(name_value) = &attr.meta {
250 if let Expr::Lit(ExprLit {
251 lit: Lit::Str(lit_str),
252 ..
253 }) = name_value.value.clone()
254 {
255 docs.push(lit_str.value());
256 }
257 }
258 }
259 docs.join("\n").to_string()
260}
261
262fn extract_inner_type(ty: &syn::Type) -> TokenStream {
263 if let Type::Path(TypePath { path, .. }) = ty {
264 if let Some(segment) = path.segments.last() {
265 if let PathArguments::AngleBracketed(args) = &segment.arguments {
266 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
267 return inner_ty.into_token_stream();
268 }
269 }
270 }
271 }
272 TokenStream::new()
273}
274
275#[derive(Clone, Default)]
276struct Config {
277 enum_style: Option<EnumStyle>,
278 inner_default: Option<String>,
279}
280
281impl Config {
282 fn enum_style_token(&self, tag: TokenStream) -> TokenStream {
283 let mut token = TokenStream::new();
284 if let Some(enum_style) = &self.enum_style {
285 token = quote! {
286 #tag.config.enum_style = Some(#enum_style);
287 };
288 }
289 token
290 }
291
292 fn inner_default_token(&self, tag: TokenStream, inner_type: TokenStream) -> TokenStream {
293 let mut token = TokenStream::new();
294 if inner_type.is_empty() {
295 return token;
296 }
297 if let Some(text) = &self.inner_default {
298 token = quote! {
299 let value = #inner_type::from_str(#text).map_err(|err| toml_input::Error::FromStrError(err.to_string()))?;
300 let raw = toml::Value::try_from(value)?;
301 #tag.set_inner_default(raw);
302 };
303 }
304 token
305 }
306}
307
308#[derive(Debug, Clone, FromMeta)]
309#[derive(Default)]
310enum EnumStyle {
311 Single,
312 #[default]
313 Expand,
314 Fold,
315 Flex,
316 Flex4,
317 Flex5,
318 Flex6,
319 Flex7,
320 Flex8,
321 Flex9,
322 Flex10,
323 Flex11,
324 Flex12,
325}
326
327
328impl ToTokens for EnumStyle {
329 fn to_tokens(&self, tokens: &mut TokenStream) {
330 let token = match self {
331 EnumStyle::Single => quote! { EnumStyle::Single },
332 EnumStyle::Expand => quote! { EnumStyle::Expand },
333 EnumStyle::Fold => quote! { EnumStyle::Fold },
334 EnumStyle::Flex => quote! { EnumStyle::Flex(4) },
335 EnumStyle::Flex4 => quote! { EnumStyle::Flex(4) },
336 EnumStyle::Flex5 => quote! { EnumStyle::Flex(5) },
337 EnumStyle::Flex6 => quote! { EnumStyle::Flex(6) },
338 EnumStyle::Flex7 => quote! { EnumStyle::Flex(7) },
339 EnumStyle::Flex8 => quote! { EnumStyle::Flex(8) },
340 EnumStyle::Flex9 => quote! { EnumStyle::Flex(9) },
341 EnumStyle::Flex10 => quote! { EnumStyle::Flex(10) },
342 EnumStyle::Flex11 => quote! { EnumStyle::Flex(11) },
343 EnumStyle::Flex12 => quote! { EnumStyle::Flex(12) },
344 };
345 tokens.extend(token);
346 }
347}