1#![doc(
2 html_root_url = "https://docs.rs/structdoc-derive/0.1.4/structdoc-derive/",
3 test(attr(deny(warnings)))
4)]
5#![forbid(unsafe_code)]
6
7extern crate proc_macro;
16
17use std::iter;
18
19use either::Either;
20use itertools::Itertools;
21use proc_macro2::{Span, TokenStream};
22use quote::quote;
23use syn::punctuated::Punctuated;
24use syn::token::Comma;
25use syn::{
26 Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Ident, Lit, LitStr, Meta,
27 MetaList, MetaNameValue, NestedMeta, Path, Variant,
28};
29
30macro_rules! pat_eq {
31 ($pat: pat, $val: expr) => {
32 if let $pat = $val {
33 true
34 } else {
35 false
36 }
37 };
38}
39
40#[derive(Clone, Eq, PartialEq)]
41enum RenameMode {
42 Lower,
43 Upper,
44 Pascal,
45 Camel,
46 Snake,
47 ScreamingSnake,
48 Kebab,
49 ScreamingKebab,
50}
51
52impl RenameMode {
53 fn apply(&self, s: &str) -> String {
54 use self::RenameMode::*;
55 use heck::*;
56 match self {
57 Lower => s.to_ascii_lowercase(),
58 Upper => s.to_ascii_uppercase(),
59 Pascal => s.to_camel_case(),
61 Camel => s.to_mixed_case(),
62 Snake => s.to_snake_case(),
63 ScreamingSnake => s.to_snake_case().to_ascii_uppercase(),
64 Kebab => s.to_kebab_case(),
65 ScreamingKebab => s.to_kebab_case().to_ascii_uppercase(),
66 }
67 }
68}
69
70impl From<&str> for RenameMode {
71 fn from(s: &str) -> RenameMode {
72 use self::RenameMode::*;
73 match s {
74 "lowercase" => Lower,
75 "UPPERCASE" => Upper,
76 "PascalCase" => Pascal,
77 "camelCase" => Camel,
78 "snake_case" => Snake,
79 "SCREAMING_SNAKE_CASE" => ScreamingSnake,
80 "kebab-case" => Kebab,
81 "SCREAMING-KEBAB-CASE" => ScreamingKebab,
82 s => panic!("Unknown rename-all value {}", s),
83 }
84 }
85}
86
87#[derive(Clone, Eq, PartialEq)]
88enum Tag {
89 Untagged,
90 Internal { tag: String },
91}
92
93#[derive(Clone)]
94enum Attr {
95 Hidden,
96 Flatten,
97 Leaf(String),
98 Default,
99 Doc(String),
100 RenameAll(RenameMode),
101 Rename(String),
102 Tag(Tag),
103 TagContent(String),
104 With(LitStr),
105}
106
107fn parse_paren(outer: &Ident, inner: &Ident) -> Option<Attr> {
108 match (outer.to_string().as_ref(), inner.to_string().as_ref()) {
109 ("doc", "hidden")
110 | ("serde", "skip")
111 | ("serde", "skip_deserializing")
112 | ("structdoc", "skip") => Some(Attr::Hidden),
113 ("serde", "flatten") | ("structdoc", "flatten") => Some(Attr::Flatten),
114 ("serde", "default") | ("structdoc", "default") => Some(Attr::Default),
115 ("structdoc", "leaf") => Some(Attr::Leaf(String::new())),
116 ("serde", "untagged") | ("structdoc", "untagged") => Some(Attr::Tag(Tag::Untagged)),
117 ("structdoc", attr) => panic!("Unknown structdoc attribute {}", attr),
118 _ => None,
121 }
122}
123
124fn parse_name_value(outer: &Ident, inner: &Ident, value: &Lit) -> Option<Attr> {
125 match (
126 outer.to_string().as_ref(),
127 inner.to_string().as_ref(),
128 value,
129 ) {
130 ("serde", "rename_all", Lit::Str(s)) | ("structdoc", "rename_all", Lit::Str(s)) => {
131 Some(Attr::RenameAll(RenameMode::from(&s.value() as &str)))
132 }
133 ("serde", "rename_all", _) | ("structdoc", "rename_all", _) => {
134 panic!("rename-all expects string")
135 }
136 ("serde", "rename", Lit::Str(s)) | ("structdoc", "rename", Lit::Str(s)) => {
137 Some(Attr::Rename(s.value()))
138 }
139 ("serde", "rename", _) | ("structdoc", "rename", _) => panic!("rename expects string"),
140 ("structdoc", "leaf", Lit::Str(s)) => Some(Attr::Leaf(s.value())),
141 ("structdoc", "leaf", _) => panic!("leaf expects string"),
142 ("serde", "tag", Lit::Str(s)) | ("structdoc", "tag", Lit::Str(s)) => {
143 Some(Attr::Tag(Tag::Internal { tag: s.value() }))
144 }
145 ("serde", "tag", _) | ("structdoc", "tag", _) => panic!("tag expects string"),
146 ("serde", "content", Lit::Str(s)) | ("structdoc", "content", Lit::Str(s)) => {
147 Some(Attr::TagContent(s.value()))
148 }
149 ("serde", "content", _) | ("structdoc", "content", _) => panic!("content expects string"),
150 ("structdoc", "with", Lit::Str(s)) => Some(Attr::With(s.clone())),
151 ("structdoc", "with", _) => panic!("with expects string"),
152 ("structdoc", name, _) => panic!("Unknown strucdoc attribute {}", name),
153 _ => None,
154 }
155}
156
157fn parse_nested_meta(
158 ident: Ident,
159 nested: impl IntoIterator<Item = NestedMeta>,
160) -> impl Iterator<Item = Attr> {
161 nested.into_iter().filter_map(move |nm| match nm {
162 NestedMeta::Meta(Meta::Path(path)) => {
163 parse_paren(&ident, &path.get_ident().expect("Multi-word attribute"))
164 }
165 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
166 path: name,
167 lit: value,
168 ..
169 })) => parse_name_value(&ident, name.get_ident().expect("Bad name"), &value),
170 _ => panic!("Confused by attribute syntax"),
171 })
172}
173
174fn parse_attrs(attrs: &[Attribute]) -> Vec<Attr> {
175 attrs
176 .iter()
177 .filter(|attr| {
179 attr.path.is_ident("structdoc")
180 || attr.path.is_ident("doc")
181 || attr.path.is_ident("serde")
182 })
183 .map(|attr| attr.parse_meta().expect("Unparsable attribute"))
185 .flat_map(|meta| match meta {
186 Meta::List(MetaList { path, nested, .. }) => {
187 let ident = path.get_ident().expect("Multi-word attribute").to_owned();
188 Either::Left(parse_nested_meta(ident, nested))
189 }
190 Meta::NameValue(MetaNameValue { path, lit, .. }) => {
191 assert_eq!(
192 path.get_ident().expect("Non-ident attribute"),
193 "doc",
194 "Broken attribute"
195 );
196 if let Lit::Str(string) = lit {
197 Either::Right(iter::once(Attr::Doc(string.value())))
198 } else {
199 panic!("Invalid doc text (must be string)");
200 }
201 }
202 _ => panic!("Wrong attribute"),
203 })
204 .collect()
205}
206
207fn mangle_name(name: &Ident, container_attrs: &[Attr], field_attrs: &[Attr]) -> String {
208 for attr in field_attrs {
209 if let Attr::Rename(name) = attr {
210 return name.clone();
211 }
212 }
213 for attr in container_attrs {
214 if let Attr::RenameAll(mode) = attr {
215 return mode.apply(&name.to_string());
216 }
217 }
218 name.to_string()
219}
220
221fn get_doc(attrs: &[Attr]) -> String {
222 let lines = iter::once(&Attr::Doc(String::new()))
223 .chain(attrs)
224 .filter_map(|a| if let Attr::Doc(d) = a { Some(d) } else { None })
225 .join("\n");
226 unindent::unindent(&lines)
227}
228
229fn get_mods(what: &Ident, attrs: &[Attr]) -> TokenStream {
230 let mut mods = TokenStream::new();
231 if attrs.iter().any(|a| pat_eq!(Attr::Default, a)) {
232 mods.extend(quote!(#what.set_flag(::structdoc::Flags::OPTIONAL);));
233 }
234 if attrs.iter().any(|a| pat_eq!(Attr::Flatten, a)) {
235 mods.extend(quote!(#what.set_flag(::structdoc::Flags::FLATTEN);));
236 }
237 if attrs.iter().any(|a| pat_eq!(Attr::Hidden, a)) {
238 mods.extend(quote!(#what.set_flag(::structdoc::Flags::HIDE);));
239 }
240 mods
241}
242
243fn leaf(ty: &str) -> TokenStream {
244 quote!(::structdoc::Documentation::leaf(#ty))
245}
246
247fn find_leaf(attrs: &[Attr]) -> Option<&str> {
248 for attr in attrs {
249 if let Attr::Leaf(s) = attr {
250 return Some(s);
251 }
252 }
253 None
254}
255
256fn find_with(attrs: &[Attr]) -> Option<&LitStr> {
257 for attr in attrs {
258 if let Attr::With(s) = attr {
259 return Some(s);
260 }
261 }
262 None
263}
264
265fn call_with(s: &LitStr) -> TokenStream {
266 let with: Path = s.parse().unwrap();
267 quote!(#with())
268}
269
270fn named_field(field: &Field, container_attrs: &[Attr]) -> TokenStream {
271 let ident = field
272 .ident
273 .as_ref()
274 .expect("A struct with anonymous field?!");
275 let field_attrs = parse_attrs(&field.attrs);
276 let name = mangle_name(ident, &container_attrs, &field_attrs);
277 let ty = &field.ty;
278 let doc = get_doc(&field_attrs);
279 let mods = get_mods(&Ident::new("field", Span::call_site()), &field_attrs);
280 let found_leaf = find_leaf(&field_attrs);
283 let is_leaf = found_leaf.is_some() || field_attrs.iter().any(|a| pat_eq!(Attr::Hidden, a));
284 let field_document = if let Some(with) = find_with(&field_attrs) {
285 call_with(with)
286 } else if is_leaf {
287 leaf(found_leaf.unwrap_or_default())
288 } else {
289 quote!(<#ty as ::structdoc::StructDoc>::document())
290 };
291
292 quote! {
293 let mut field = #field_document;
294 #mods
295 let field = ::structdoc::Field::new(field, #doc);
296 fields.push((#name.into(), field));
297 }
298}
299
300fn derive_struct(fields: &Punctuated<Field, Comma>, attrs: &[Attribute]) -> TokenStream {
301 let struct_attrs = parse_attrs(attrs);
302 let insert_fields = fields.iter().map(|field| named_field(field, &struct_attrs));
304
305 quote! {
306 let mut fields = ::std::vec::Vec::<(&str, ::structdoc::Field)>::new();
307 #(#insert_fields)*
308 ::structdoc::Documentation::struct_(fields)
309 }
310}
311
312fn find_tag(attrs: &[Attr]) -> Option<&Tag> {
313 for attr in attrs {
314 if let Attr::Tag(tag) = attr {
315 return Some(tag);
316 }
317 }
318 None
319}
320
321fn find_tag_content(attrs: &[Attr]) -> Option<&str> {
322 for attr in attrs {
323 if let Attr::TagContent(s) = attr {
324 return Some(s);
325 }
326 }
327 None
328}
329
330fn derive_enum(variants: &Punctuated<Variant, Comma>, attrs: &[Attribute]) -> TokenStream {
331 let enum_attrs = parse_attrs(attrs);
332 let insert_varianst = variants.iter().map(|variant| {
333 let variant_attrs = parse_attrs(&variant.attrs);
334 let name = mangle_name(&variant.ident, &enum_attrs, &variant_attrs);
335 let doc = get_doc(&variant_attrs);
336 let mods = get_mods(&Ident::new("variant", Span::call_site()), &variant_attrs);
337 let found_leaf = find_leaf(&variant_attrs);
338 let is_leaf =
339 found_leaf.is_some() || variant_attrs.iter().any(|a| pat_eq!(Attr::Hidden, a));
340 let constructor = if let Some(with) = find_with(&variant_attrs) {
341 call_with(with)
342 } else if is_leaf {
343 leaf(found_leaf.unwrap_or_default())
344 } else {
345 match &variant.fields {
346 Fields::Unit => leaf(""),
347 Fields::Named(fields) => {
348 let mut attrs = Vec::new();
349 attrs.extend(variant_attrs);
350 attrs.extend(enum_attrs.clone());
351 let insert_fields = fields.named.iter().map(|field| named_field(field, &attrs));
352 quote! {
353 {
354 let mut fields =
355 ::std::vec::Vec::<(&str, ::structdoc::Field)>::new();
356 #(#insert_fields)*
357 ::structdoc::Documentation::struct_(fields)
358 }
359 }
360 }
361 Fields::Unnamed(fields) if fields.unnamed.is_empty() => leaf(""),
362 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
363 let ty = &fields.unnamed[0].ty;
364 quote!(<#ty as ::structdoc::StructDoc>::document())
365 }
366 Fields::Unnamed(fields) => {
367 panic!(
368 "Don't know what to do with tuple variant with {} fields",
369 fields.unnamed.len(),
370 );
371 }
372 }
373 };
374 quote! {
375 let mut variant = #constructor;
376 #mods
377 let variant = ::structdoc::Field::new(variant, #doc);
378 variants.push((#name.into(), variant));
379 }
380 });
381
382 #[rustfmt::skip] let tag = match (find_tag(&enum_attrs), find_tag_content(&enum_attrs)) {
384 (None, _) => quote!(External),
385 (Some(Tag::Internal { tag }), Some(content)) => {
386 quote!(Adjacent { tag: #tag.to_owned(), content: #content.to_owned() })
387 },
388 (Some(Tag::Internal { tag }), _) => quote!(Internal { tag: #tag.to_owned() }),
389 (Some(Tag::Untagged), _) => quote!(Untagged),
390 };
391
392 quote! {
393 let mut variants = ::std::vec::Vec::<(&str, ::structdoc::Field)>::new();
394 #(#insert_varianst)*
395 ::structdoc::Documentation::enum_(variants, ::structdoc::Tagging::#tag)
396 }
397}
398
399fn derive_transparent(field: &Field) -> TokenStream {
400 let ty = &field.ty;
401 quote!(<#ty as ::structdoc::StructDoc>::document())
402}
403
404#[proc_macro_derive(StructDoc, attributes(structdoc))]
406pub fn structdoc_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
407 let mut input: DeriveInput = syn::parse(input).unwrap();
408 let types = input.generics.type_params().cloned().collect::<Vec<_>>();
409 let clause = input.generics.make_where_clause();
410 for t in types {
411 let t = t.ident;
412 clause
413 .predicates
414 .push(syn::parse(quote!(#t: ::structdoc::StructDoc).into()).unwrap());
415 }
416 let name = &input.ident;
417 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
418 let inner = match input.data {
419 Data::Struct(DataStruct {
420 fields: Fields::Named(fields),
421 ..
422 }) => derive_struct(&fields.named, &input.attrs),
423 Data::Struct(DataStruct {
424 fields: Fields::Unnamed(ref fields),
425 ..
426 }) if fields.unnamed.len() == 1 => derive_transparent(&fields.unnamed[0]),
427 Data::Enum(DataEnum { variants, .. }) => derive_enum(&variants, &input.attrs),
428 _ => unimplemented!("Only named structs and enums for now :-("),
429 };
430 (quote! {
431 impl #impl_generics ::structdoc::StructDoc for #name #ty_generics
432 #where_clause
433 {
434 fn document() -> ::structdoc::Documentation {
435 #inner
436 }
437 }
438 })
439 .into()
440}