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