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};