cuprated/config/
macros.rs

1use toml_edit::TableLike;
2
3/// A macro for config structs defined in `cuprated`. This macro generates a function that
4/// can insert toml comments created from doc comments on fields.
5///
6/// # Attributes
7/// - `#[child = true]`: writes the doc comments for all fields in the child struct.
8/// - `#[inline = true]`: inlines the struct into `{}` instead of having a separate `[]` header.
9/// - `#[comment_out = true]`: comments out the field.
10///
11/// # Invariants
12/// Required for this macro to work:
13///
14/// - struct must implement [`Default`] and `serde`
15/// - None of the fields can be [`Option`]
16///
17/// # Documentation
18/// Consider using the following style when adding documentation:
19///
20/// ```rust
21/// struct Config {
22///     /// BRIEF DESCRIPTION.
23///     ///
24///     /// (optional) LONGER DESCRIPTION.
25///     ///
26///     /// Type         | (optional) FIELD TYPE
27///     /// Valid values | EXPRESSION REPRESENTING VALID VALUES
28///     /// Examples     | (optional) A FEW EXAMPLE VALUES
29///     field: (),
30/// }
31/// ```
32///
33/// For example:
34/// ```rust
35/// struct Config {
36///     /// Enable/disable fast sync.
37///     ///
38///     /// Fast sync skips verification of old blocks by
39///     /// comparing block hashes to a built-in hash file,
40///     /// disabling this will significantly increase sync time.
41///     /// New blocks are still fully validated.
42///     ///
43///     /// Type         | boolean
44///     /// Valid values | true, false
45///     fast_sync: bool,
46/// }
47/// ```
48///
49/// Language for types:
50///
51/// | Rust type    | Wording used in user-book |
52/// |--------------|---------------------------|
53/// | bool         | boolean
54/// | u{8-64}      | Number
55/// | i{8-64}      | Signed number
56/// | f{32,64}     | Floating point number
57/// | str, String  | String
58/// | enum, struct | `DataStructureName` (e.g. `Duration`) or $DESCRIPTION (e.g. `IP address`)
59///
60/// If some fields are redundant or unnecessary, do not add them.
61///
62/// # Field documentation length
63/// In order to prevent wrapping/scrollbars in the user book and in editors,
64/// add newlines when a documentation line crosses ~70 characters, around this long:
65///
66/// `----------------------------------------------------------------------`
67///
68/// # Shared Values
69/// Sometimes multiple different configs will overlap in their fields. Manually duplicating the fields
70/// between the structs can lead to maintenance issues as the docs need to be kept in sync. Also using
71/// serde's `flatten` is not supported with `deny_unknown_fields`: <https://serde.rs/field-attrs.html#flatten>.
72///
73/// So this macro provides a way to define shared fields between configs, just add a `Shared` object
74/// with the wanted fields at the top and all structs afterwards will have those fields inserted.
75macro_rules! config_struct {
76        (
77        $(
78        Shared {
79             $(
80                $(#[child = $s_child:literal])?
81                $(#[inline = $s_inline:literal])?
82                $(#[comment_out = $s_comment_out:literal])?
83                $(#[doc = $s_doc:expr])*
84                $(##[$s_field_meta:meta])*
85                pub $s_field:ident: $s_field_ty:ty,
86            )+
87        }
88        )?
89    ) => {  };
90    (
91        $(
92        Shared {
93             $(
94                $(#[child = $s_child:literal])?
95                $(#[inline = $s_inline:literal])?
96                $(#[comment_out = $s_comment_out:literal])?
97                $(#[doc = $s_doc:expr])*
98                $(##[$s_field_meta:meta])*
99                pub $s_field:ident: $s_field_ty:ty,
100            )+
101        }
102        )?
103
104        $(#[$meta:meta])*
105        pub struct $name:ident {
106            $(
107                $(#[child = $child:literal])?
108                $(#[inline = $inline:literal])?
109                $(#[comment_out = $comment_out:literal])?
110                $(#[doc = $doc:expr])*
111                $(##[$field_meta:meta])*
112                pub $field:ident: $field_ty:ty,
113            )*
114        }
115
116        $($tt: tt)*
117    ) => {
118        #[allow(clippy::doc_markdown, clippy::allow_attributes)]
119        $(#[$meta])*
120        pub struct $name {
121            $(
122                $(#[doc = $doc])*
123                $(#[$field_meta])*
124                pub $field: $field_ty,
125            )*
126            $($(
127                $(#[doc = $s_doc])*
128                $(#[$s_field_meta])*
129                pub $s_field: $s_field_ty,
130            )+)?
131        }
132
133        impl $name {
134            #[allow(unused_labels, clippy::allow_attributes)]
135            pub fn write_docs(doc: &mut dyn ::toml_edit::TableLike) {
136                $(
137                    crate::config::macros::__write_docs!(
138                        $(#[child = $child])?
139                        $(#[inline = $inline])?
140                        $(#[comment_out = $comment_out])?
141                        $(#[doc = $doc])*
142                        $(##[$field_meta])*
143                        pub $field: $field_ty,
144                        doc,
145                    );
146                )*
147
148                $($(
149                    crate::config::macros::__write_docs!(
150                        $(#[child = $s_child])?
151                        $(#[inline = $s_inline])?
152                        $(#[comment_out = $s_comment_out])?
153                        $(#[doc = $s_doc])*
154                        $(##[$s_field_meta])*
155                        pub $s_field: $s_field_ty,
156                        doc,
157                    );
158                )+)?
159            }
160        }
161
162        crate::config::macros::config_struct!{
163            $(
164               Shared {
165                   $(
166                   $(#[child = $s_child])?
167                    $(#[inline = $s_inline])?
168                    $(#[comment_out = $s_comment_out])?
169                    $(#[doc = $s_doc])*
170                    $(##[$s_field_meta])*
171                    pub $s_field: $s_field_ty,
172                   )+
173               }
174            )?
175            $($tt)*
176        }
177    };
178}
179
180macro_rules! __write_docs {
181    (
182        $(#[child = $child:literal])?
183        $(#[inline = $inline:literal])?
184        $(#[comment_out = $comment_out:literal])?
185        $(#[doc = $doc:expr])*
186        $(##[$field_meta:meta])*
187        pub $field:ident: $field_ty:ty,
188
189        $document: ident,
190    ) => {
191        'write_field: {
192            let key_str = &stringify!($field);
193
194            let mut field_prefix = [ $(
195              format!("##{}\n", $doc),
196            )*].concat();
197
198            $(
199            if $comment_out {
200                field_prefix.push('#');
201            }
202            )?
203
204            $(
205            if $child {
206                <$field_ty>::write_docs($document.get_key_value_mut(&key_str).unwrap().1.as_table_like_mut().unwrap());
207            }
208            )?
209
210            if let Some(table) = $document.entry(&key_str).or_insert_with(|| panic!()).as_table_mut() {
211                $(
212                    if $inline {
213                        let mut table = table.clone().into_inline_table();
214                        $document.insert(&key_str, ::toml_edit::Item::Value(::toml_edit::Value::InlineTable(table)));
215                        $document.key_mut(&key_str).unwrap().leaf_decor_mut().set_prefix(field_prefix);
216                        break 'write_field;
217                    }
218                )?
219                table.decor_mut().set_prefix(format!("\n{}", field_prefix));
220            }else {
221                $document.key_mut(&key_str).unwrap().leaf_decor_mut().set_prefix(field_prefix);
222            }
223        }
224    };
225}
226
227pub(crate) use {__write_docs, config_struct};