Skip to main content

code_docs/
documented_struct.rs

1#[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    /// Returns all struct field types, ex. `Option<String>`
7    fn field_types() -> Vec<&'static str>;
8
9    /// Returns all struct field names
10    fn field_names() -> Vec<&'static str>;
11
12    /// Returns all docstrings of the struct itself
13    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    /// Returns all docstrings of each field, each line is a separate string
21    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    /// Returns string where each field is shown, its type and the docstrings
31    fn commented_fields() -> String {
32        use std::fmt::Write;
33
34        // NOTE write! is infallable on String
35
36        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        // just in case so the errors are not so cryptic below
42        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            // remove the extra newline
47            if i != 0 {
48                write!(output, "\n").unwrap();
49            }
50
51            // imitate rust comments
52            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// TODO test raw output as well
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    // NOTE: the newline below is intentional
129    code_docs_struct! {
130
131        /// This is a testing struct
132        /// With two lines of docstring
133        #[derive(PartialEq, Eq, Debug)]
134        struct TestStruct {
135            /// This field is u8
136            pub field_a: u8,
137
138            /// This field is a String
139            /// Also two lines of comments
140            #[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}