/src/neomutt/config/number.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Type representing a number |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2017-2025 Richard Russon <rich@flatcap.org> |
7 | | * Copyright (C) 2020 Jakub Jindra <jakub.jindra@socialbakers.com> |
8 | | * Copyright (C) 2021 Pietro Cerutti <gahr@gahr.ch> |
9 | | * Copyright (C) 2023 наб <nabijaczleweli@nabijaczleweli.xyz> |
10 | | * |
11 | | * @copyright |
12 | | * This program is free software: you can redistribute it and/or modify it under |
13 | | * the terms of the GNU General Public License as published by the Free Software |
14 | | * Foundation, either version 2 of the License, or (at your option) any later |
15 | | * version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
20 | | * details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License along with |
23 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
24 | | */ |
25 | | |
26 | | /** |
27 | | * @page config_number Type: Number |
28 | | * |
29 | | * Config type representing a number. |
30 | | * |
31 | | * - Backed by `short` |
32 | | * - Validator is passed `short` |
33 | | * - Implementation: #CstNumber |
34 | | */ |
35 | | |
36 | | #include "config.h" |
37 | | #include <limits.h> |
38 | | #include <stdbool.h> |
39 | | #include <stddef.h> |
40 | | #include <stdint.h> |
41 | | #include "mutt/lib.h" |
42 | | #include "number.h" |
43 | | #include "set.h" |
44 | | #include "subset.h" |
45 | | #include "types.h" |
46 | | |
47 | 336k | #define TOGGLE_BIT ((SHRT_MAX + 1) << 1) |
48 | | /** |
49 | | * native_get - Get an int from a Number config item |
50 | | */ |
51 | | static intptr_t native_get(void *var) |
52 | 336k | { |
53 | | // take care of endianess and always read intptr_t value |
54 | 336k | intptr_t v = *(intptr_t *) var; |
55 | 336k | return (v & TOGGLE_BIT) ? 0 : (short) v; |
56 | 336k | } |
57 | | |
58 | | /** |
59 | | * native_set - Set an int into a Number config item |
60 | | */ |
61 | | static void native_set(void *var, intptr_t val) |
62 | 22 | { |
63 | | // cast to unsigned short to clear any pending toggle status bits |
64 | 22 | val = (unsigned short) val; |
65 | 22 | *(intptr_t *) var = val; |
66 | 22 | } |
67 | | |
68 | | /** |
69 | | * native_toggle - Toggle a Number config item |
70 | | */ |
71 | | static void native_toggle(void *var) |
72 | 0 | { |
73 | 0 | *(intptr_t *) var = *(uintptr_t *) var ^ TOGGLE_BIT; |
74 | 0 | } |
75 | | |
76 | | /** |
77 | | * number_string_set - Set a Number by string - Implements ConfigSetType::string_set() - @ingroup cfg_type_string_set |
78 | | */ |
79 | | static int number_string_set(void *var, struct ConfigDef *cdef, |
80 | | const char *value, struct Buffer *err) |
81 | 0 | { |
82 | 0 | int num = 0; |
83 | 0 | if (value && *value && !mutt_str_atoi_full(value, &num)) |
84 | 0 | { |
85 | 0 | buf_printf(err, _("Invalid number: %s"), value); |
86 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
87 | 0 | } |
88 | | |
89 | 0 | if ((num < SHRT_MIN) || (num > SHRT_MAX)) |
90 | 0 | { |
91 | 0 | buf_printf(err, _("Number is too big: %s"), value); |
92 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
93 | 0 | } |
94 | | |
95 | 0 | if ((num < 0) && (cdef->type & D_INTEGER_NOT_NEGATIVE)) |
96 | 0 | { |
97 | 0 | buf_printf(err, _("Option %s may not be negative"), cdef->name); |
98 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
99 | 0 | } |
100 | | |
101 | 0 | if (var) |
102 | 0 | { |
103 | 0 | if (num == native_get(var)) |
104 | 0 | return CSR_SUCCESS | CSR_SUC_NO_CHANGE; |
105 | | |
106 | 0 | if (cdef->validator) |
107 | 0 | { |
108 | 0 | int rc = cdef->validator(cdef, (intptr_t) num, err); |
109 | |
|
110 | 0 | if (CSR_RESULT(rc) != CSR_SUCCESS) |
111 | 0 | return rc | CSR_INV_VALIDATOR; |
112 | 0 | } |
113 | | |
114 | 0 | if (startup_only(cdef, err)) |
115 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
116 | | |
117 | 0 | native_set(var, num); |
118 | 0 | } |
119 | 0 | else |
120 | 0 | { |
121 | 0 | cdef->initial = num; |
122 | 0 | } |
123 | | |
124 | 0 | return CSR_SUCCESS; |
125 | 0 | } |
126 | | |
127 | | /** |
128 | | * number_string_get - Get a Number as a string - Implements ConfigSetType::string_get() - @ingroup cfg_type_string_get |
129 | | */ |
130 | | static int number_string_get(void *var, const struct ConfigDef *cdef, struct Buffer *result) |
131 | 0 | { |
132 | 0 | int value; |
133 | |
|
134 | 0 | if (var) |
135 | 0 | value = native_get(var); |
136 | 0 | else |
137 | 0 | value = (int) cdef->initial; |
138 | |
|
139 | 0 | buf_printf(result, "%d", value); |
140 | 0 | return CSR_SUCCESS; |
141 | 0 | } |
142 | | |
143 | | /** |
144 | | * number_native_set - Set a Number config item by int - Implements ConfigSetType::native_set() - @ingroup cfg_type_native_set |
145 | | */ |
146 | | static int number_native_set(void *var, const struct ConfigDef *cdef, |
147 | | intptr_t value, struct Buffer *err) |
148 | 0 | { |
149 | 0 | if ((value < SHRT_MIN) || (value > SHRT_MAX)) |
150 | 0 | { |
151 | 0 | buf_printf(err, _("Invalid number: %ld"), (long) value); |
152 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
153 | 0 | } |
154 | | |
155 | 0 | if ((value < 0) && (cdef->type & D_INTEGER_NOT_NEGATIVE)) |
156 | 0 | { |
157 | 0 | buf_printf(err, _("Option %s may not be negative"), cdef->name); |
158 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
159 | 0 | } |
160 | | |
161 | 0 | if (value == native_get(var)) |
162 | 0 | return CSR_SUCCESS | CSR_SUC_NO_CHANGE; |
163 | | |
164 | 0 | if (cdef->validator) |
165 | 0 | { |
166 | 0 | int rc = cdef->validator(cdef, value, err); |
167 | |
|
168 | 0 | if (CSR_RESULT(rc) != CSR_SUCCESS) |
169 | 0 | return rc | CSR_INV_VALIDATOR; |
170 | 0 | } |
171 | | |
172 | 0 | if (startup_only(cdef, err)) |
173 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
174 | | |
175 | 0 | native_set(var, value); |
176 | 0 | return CSR_SUCCESS; |
177 | 0 | } |
178 | | |
179 | | /** |
180 | | * number_native_get - Get an int from a Number config item - Implements ConfigSetType::native_get() - @ingroup cfg_type_native_get |
181 | | */ |
182 | | static intptr_t number_native_get(void *var, const struct ConfigDef *cdef, struct Buffer *err) |
183 | 0 | { |
184 | 0 | return native_get(var); |
185 | 0 | } |
186 | | |
187 | | /** |
188 | | * number_string_plus_equals - Add to a Number by string - Implements ConfigSetType::string_plus_equals() - @ingroup cfg_type_string_plus_equals |
189 | | */ |
190 | | static int number_string_plus_equals(void *var, const struct ConfigDef *cdef, |
191 | | const char *value, struct Buffer *err) |
192 | 0 | { |
193 | 0 | int num = 0; |
194 | 0 | if (!mutt_str_atoi_full(value, &num)) |
195 | 0 | { |
196 | 0 | buf_printf(err, _("Invalid number: %s"), NONULL(value)); |
197 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
198 | 0 | } |
199 | | |
200 | 0 | int result = number_native_get(var, NULL, NULL) + num; |
201 | 0 | if ((result < SHRT_MIN) || (result > SHRT_MAX)) |
202 | 0 | { |
203 | 0 | buf_printf(err, _("Number is too big: %s"), value); |
204 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
205 | 0 | } |
206 | | |
207 | 0 | if ((result < 0) && (cdef->type & D_INTEGER_NOT_NEGATIVE)) |
208 | 0 | { |
209 | 0 | buf_printf(err, _("Option %s may not be negative"), cdef->name); |
210 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
211 | 0 | } |
212 | | |
213 | 0 | if (cdef->validator) |
214 | 0 | { |
215 | 0 | int rc = cdef->validator(cdef, (intptr_t) result, err); |
216 | |
|
217 | 0 | if (CSR_RESULT(rc) != CSR_SUCCESS) |
218 | 0 | return rc | CSR_INV_VALIDATOR; |
219 | 0 | } |
220 | | |
221 | 0 | if (startup_only(cdef, err)) |
222 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
223 | | |
224 | 0 | native_set(var, result); |
225 | 0 | return CSR_SUCCESS; |
226 | 0 | } |
227 | | |
228 | | /** |
229 | | * number_string_minus_equals - Subtract from a Number by string - Implements ConfigSetType::string_minus_equals() - @ingroup cfg_type_string_minus_equals |
230 | | */ |
231 | | static int number_string_minus_equals(void *var, const struct ConfigDef *cdef, |
232 | | const char *value, struct Buffer *err) |
233 | 0 | { |
234 | 0 | int num = 0; |
235 | 0 | if (!mutt_str_atoi(value, &num)) |
236 | 0 | { |
237 | 0 | buf_printf(err, _("Invalid number: %s"), NONULL(value)); |
238 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
239 | 0 | } |
240 | | |
241 | 0 | int result = native_get(var) - num; |
242 | 0 | if ((result < SHRT_MIN) || (result > SHRT_MAX)) |
243 | 0 | { |
244 | 0 | buf_printf(err, _("Number is too big: %s"), value); |
245 | 0 | return CSR_ERR_INVALID | CSR_INV_TYPE; |
246 | 0 | } |
247 | | |
248 | 0 | if ((result < 0) && (cdef->type & D_INTEGER_NOT_NEGATIVE)) |
249 | 0 | { |
250 | 0 | buf_printf(err, _("Option %s may not be negative"), cdef->name); |
251 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
252 | 0 | } |
253 | | |
254 | 0 | if (cdef->validator) |
255 | 0 | { |
256 | 0 | int rc = cdef->validator(cdef, (intptr_t) result, err); |
257 | |
|
258 | 0 | if (CSR_RESULT(rc) != CSR_SUCCESS) |
259 | 0 | return rc | CSR_INV_VALIDATOR; |
260 | 0 | } |
261 | | |
262 | 0 | if (startup_only(cdef, err)) |
263 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
264 | | |
265 | 0 | native_set(var, result); |
266 | 0 | return CSR_SUCCESS; |
267 | 0 | } |
268 | | |
269 | | /** |
270 | | * number_has_been_set - Is the config value different to its initial value? - Implements ConfigSetType::has_been_set() - @ingroup cfg_type_has_been_set |
271 | | */ |
272 | | static bool number_has_been_set(void *var, const struct ConfigDef *cdef) |
273 | 0 | { |
274 | 0 | return (cdef->initial != native_get(var)); |
275 | 0 | } |
276 | | |
277 | | /** |
278 | | * number_reset - Reset a Number to its initial value - Implements ConfigSetType::reset() - @ingroup cfg_type_reset |
279 | | */ |
280 | | static int number_reset(void *var, const struct ConfigDef *cdef, struct Buffer *err) |
281 | 336k | { |
282 | 336k | if (cdef->initial == native_get(var)) |
283 | 336k | return CSR_SUCCESS | CSR_SUC_NO_CHANGE; |
284 | | |
285 | 22 | if (cdef->validator) |
286 | 0 | { |
287 | 0 | int rc = cdef->validator(cdef, cdef->initial, err); |
288 | |
|
289 | 0 | if (CSR_RESULT(rc) != CSR_SUCCESS) |
290 | 0 | return rc | CSR_INV_VALIDATOR; |
291 | 0 | } |
292 | | |
293 | 22 | if (startup_only(cdef, err)) |
294 | 0 | return CSR_ERR_INVALID | CSR_INV_VALIDATOR; |
295 | | |
296 | 22 | native_set(var, cdef->initial); |
297 | 22 | return CSR_SUCCESS; |
298 | 22 | } |
299 | | |
300 | | /** |
301 | | * number_he_toggle - Toggle the value of a number (value <-> 0) |
302 | | * @param sub Config Subset |
303 | | * @param he HashElem representing config item |
304 | | * @param err Buffer for error messages |
305 | | * @retval num Result, e.g. #CSR_SUCCESS |
306 | | */ |
307 | | int number_he_toggle(struct ConfigSubset *sub, struct HashElem *he, struct Buffer *err) |
308 | 0 | { |
309 | 0 | if (!sub || !he || !he->data) |
310 | 0 | return CSR_ERR_CODE; |
311 | | |
312 | 0 | struct HashElem *he_base = cs_get_base(he); |
313 | 0 | if (CONFIG_TYPE(he_base->type) != DT_NUMBER) |
314 | 0 | return CSR_ERR_CODE; |
315 | | |
316 | 0 | struct ConfigDef *cdef = he_base->data; |
317 | 0 | native_toggle(&cdef->var); |
318 | |
|
319 | 0 | cs_subset_notify_observers(sub, he, NT_CONFIG_SET); |
320 | |
|
321 | 0 | return CSR_SUCCESS; |
322 | 0 | } |
323 | | |
324 | | /** |
325 | | * CstNumber - Config type representing a number |
326 | | */ |
327 | | const struct ConfigSetType CstNumber = { |
328 | | DT_NUMBER, |
329 | | "number", |
330 | | number_string_set, |
331 | | number_string_get, |
332 | | number_native_set, |
333 | | number_native_get, |
334 | | number_string_plus_equals, |
335 | | number_string_minus_equals, |
336 | | number_has_been_set, |
337 | | number_reset, |
338 | | NULL, // destroy |
339 | | }; |