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