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        $(#[$meta])*
119        pub struct $name {
120            $(
121                $(#[doc = $doc])*
122                $(#[$field_meta])*
123                pub $field: $field_ty,
124            )*
125            $($(
126                $(#[doc = $s_doc])*
127                $(#[$s_field_meta])*
128                pub $s_field: $s_field_ty,
129            )+)?
130        }
131
132        impl $name {
133            #[allow(unused_labels, clippy::allow_attributes)]
134            pub fn write_docs(doc: &mut dyn ::toml_edit::TableLike) {
135                $(
136                    crate::config::macros::__write_docs!(
137                        $(#[child = $child])?
138                        $(#[inline = $inline])?
139                        $(#[comment_out = $comment_out])?
140                        $(#[doc = $doc])*
141                        $(##[$field_meta])*
142                        pub $field: $field_ty,
143                        doc,
144                    );
145                )*
146
147                $($(
148                    crate::config::macros::__write_docs!(
149                        $(#[child = $s_child])?
150                        $(#[inline = $s_inline])?
151                        $(#[comment_out = $s_comment_out])?
152                        $(#[doc = $s_doc])*
153                        $(##[$s_field_meta])*
154                        pub $s_field: $s_field_ty,
155                        doc,
156                    );
157                )+)?
158            }
159        }
160
161        crate::config::macros::config_struct!{
162            $(
163               Shared {
164                   $(
165                   $(#[child = $s_child])?
166                    $(#[inline = $s_inline])?
167                    $(#[comment_out = $s_comment_out])?
168                    $(#[doc = $s_doc])*
169                    $(##[$s_field_meta])*
170                    pub $s_field: $s_field_ty,
171                   )+
172               }
173            )?
174            $($tt)*
175        }
176    };
177}
178
179macro_rules! __write_docs {
180    (
181        $(#[child = $child:literal])?
182        $(#[inline = $inline:literal])?
183        $(#[comment_out = $comment_out:literal])?
184        $(#[doc = $doc:expr])*
185        $(##[$field_meta:meta])*
186        pub $field:ident: $field_ty:ty,
187
188        $document: ident,
189    ) => {
190        'write_field: {
191            let key_str = &stringify!($field);
192
193            let mut field_prefix = [ $(
194              format!("##{}\n", $doc),
195            )*].concat();
196
197            $(
198            if $comment_out {
199                field_prefix.push('#');
200            }
201            )?
202
203            $(
204            if $child {
205                <$field_ty>::write_docs($document.get_key_value_mut(&key_str).unwrap().1.as_table_like_mut().unwrap());
206            }
207            )?
208
209            if let Some(table) = $document.entry(&key_str).or_insert_with(|| panic!()).as_table_mut() {
210                $(
211                    if $inline {
212                        let mut table = table.clone().into_inline_table();
213                        $document.insert(&key_str, ::toml_edit::Item::Value(::toml_edit::Value::InlineTable(table)));
214                        $document.key_mut(&key_str).unwrap().leaf_decor_mut().set_prefix(field_prefix);
215                        break 'write_field;
216                    }
217                )?
218                table.decor_mut().set_prefix(format!("\n{}", field_prefix));
219            }else {
220                $document.key_mut(&key_str).unwrap().leaf_decor_mut().set_prefix(field_prefix);
221            }
222        }
223    };
224}
225
226pub(crate) use {__write_docs, config_struct};