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