code_docs/
documented_struct.rs1#[allow(dead_code)]
2pub trait DocumentedStruct {
3 fn struct_docs_raw() -> Vec<&'static str>;
4 fn field_docs_raw() -> Vec<Vec<&'static str>>;
5
6 fn field_types() -> Vec<&'static str>;
8
9 fn field_names() -> Vec<&'static str>;
11
12 fn struct_docs() -> Vec<&'static str> {
14 Self::struct_docs_raw()
15 .into_iter()
16 .filter_map(super::filter_docs)
17 .collect::<Vec<_>>()
18 }
19
20 fn field_docs() -> Vec<Vec<&'static str>> {
22 Self::field_docs_raw().into_iter().map(|x| {
23 x.into_iter()
24 .filter_map(super::filter_docs)
25 .collect::<Vec<_>>()
26 })
27 .collect::<Vec<_>>()
28 }
29
30 fn commented_fields() -> String {
32 use std::fmt::Write;
33
34 let mut output = String::new();
37 let names = Self::field_names();
38 let docs = Self::field_docs();
39 let types = Self::field_types();
40
41 assert_eq!(names.len(), docs.len(), "Field names and field docs are not equal");
43 assert_eq!(names.len(), types.len(), "Field names and field types are not equal");
44
45 for (i, field) in names.iter().enumerate() {
46 if i != 0 {
48 write!(output, "\n").unwrap();
49 }
50
51 for comment in docs.get(i).unwrap() {
53 write!(output, "///{}\n", comment).unwrap();
54 }
55
56 write!(output, "{}: {}\n", field, types.get(i).unwrap()).unwrap();
57 }
58
59 output
60 }
61}
62
63#[macro_export]
64macro_rules! code_docs_struct {
65 (
66 $(#[$meta:meta])*
67 $vis:vis struct $name:ident {
68 $(
69 $(#[$f_meta:meta])*
70 $f_vis:vis $f_ident:ident : $f_type:ty ,
71 )*
72 }
73 ) => {
74 $(#[$meta])*
75 $vis struct $name {
76 $(
77 $(#[$f_meta])*
78 $f_vis $f_ident : $f_type ,
79 )*
80 }
81
82 impl DocumentedStruct for $name {
83 fn struct_docs_raw() -> Vec<&'static str> {
84 vec![
85 $(
86 stringify!($meta),
87 )*
88 ]
89 }
90
91 fn field_types() -> Vec<&'static str> {
92 vec![
93 $(
94 stringify!($f_type),
95 )*
96 ]
97 }
98
99 fn field_names() -> Vec<&'static str> {
100 vec![
101 $(
102 stringify!($f_ident),
103 )*
104 ]
105 }
106
107 fn field_docs_raw() -> Vec<Vec<&'static str>> {
108 vec![
109 $(
110 vec![
111 $(
112 stringify!($f_meta),
113 )*
114 ],
115 )*
116 ]
117 }
118 }
119 }
120}
121
122#[cfg(test)]
125mod tests {
126 use super::*;
127
128 code_docs_struct! {
130
131 #[derive(PartialEq, Eq, Debug)]
134 struct TestStruct {
135 pub field_a: u8,
137
138 #[allow(unused)]
141 field_b: String,
142 }
143 }
144
145 #[test]
146 fn test_struct_docs() {
147 let docs = TestStruct::struct_docs();
148 let docs_raw: Vec<&'static str> = vec![
149 " This is a testing struct",
150 " With two lines of docstring",
151 ];
152
153 assert_eq!(docs, docs_raw);
154 }
155
156 #[test]
157 fn test_field_names() {
158 let names = TestStruct::field_names();
159 let names_raw: Vec<&'static str> = vec![
160 "field_a",
161 "field_b",
162 ];
163
164 assert_eq!(names, names_raw);
165 }
166
167 #[test]
168 fn test_field_types() {
169 let types = TestStruct::field_types();
170 let types_raw: Vec<&'static str> = vec![
171 "u8",
172 "String",
173 ];
174
175 assert_eq!(types, types_raw);
176 }
177
178 #[test]
179 fn test_field_docs() {
180 let docs = TestStruct::field_docs();
181 let docs_raw: Vec<Vec<&'static str>> = vec![
182 vec![" This field is u8"],
183 vec![
184 " This field is a String",
185 " Also two lines of comments",
186 ],
187 ];
188
189 assert_eq!(docs, docs_raw);
190 }
191
192 #[test]
193 fn test_commented_fields() {
194 let output = TestStruct::commented_fields();
195 assert_eq!(
196 output,
197 r#"/// This field is u8
198field_a: u8
199
200/// This field is a String
201/// Also two lines of comments
202field_b: String
203"#
204 );
205 }
206}