ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
options/builder/handlers.c
Go to the documentation of this file.
1
14#include <ascii-chat/options/builder/internal.h>
15#include <ascii-chat/options/common.h>
16#include <ascii-chat/util/string.h>
17#include <string.h>
18#include <limits.h>
19#include <stdlib.h>
20
21// Forward declarations
22static bool is_set_bool(const void *field, const option_descriptor_t *desc);
23static bool is_set_int(const void *field, const option_descriptor_t *desc);
24static bool is_set_string(const void *field, const option_descriptor_t *desc);
25static bool is_set_double(const void *field, const option_descriptor_t *desc);
26static bool is_set_callback(const void *field, const option_descriptor_t *desc);
27static bool is_set_action(const void *field, const option_descriptor_t *desc);
28
29static void apply_env_bool(void *field, const char *env_value, const option_descriptor_t *desc);
30static void apply_env_int(void *field, const char *env_value, const option_descriptor_t *desc);
31static void apply_env_string(void *field, const char *env_value, const option_descriptor_t *desc);
32static void apply_env_double(void *field, const char *env_value, const option_descriptor_t *desc);
33static void apply_env_callback(void *field, const char *env_value, const option_descriptor_t *desc);
34static void apply_env_action(void *field, const char *env_value, const option_descriptor_t *desc);
35
36static asciichat_error_t apply_cli_bool(void *field, const char *opt_value, const option_descriptor_t *desc);
37static asciichat_error_t apply_cli_int(void *field, const char *opt_value, const option_descriptor_t *desc);
38static asciichat_error_t apply_cli_string(void *field, const char *opt_value, const option_descriptor_t *desc);
39static asciichat_error_t apply_cli_double(void *field, const char *opt_value, const option_descriptor_t *desc);
40static asciichat_error_t apply_cli_callback(void *field, const char *opt_value, const option_descriptor_t *desc);
41static asciichat_error_t apply_cli_action(void *field, const char *opt_value, const option_descriptor_t *desc);
42
43static void format_help_placeholder_bool(char *buf, size_t bufsize);
44static void format_help_placeholder_int(char *buf, size_t bufsize);
45static void format_help_placeholder_string(char *buf, size_t bufsize);
46static void format_help_placeholder_double(char *buf, size_t bufsize);
47static void format_help_placeholder_callback(char *buf, size_t bufsize);
48static void format_help_placeholder_action(char *buf, size_t bufsize);
49
50// ============================================================================
51// Handler Registry - Exported for use by other modules
52// ============================================================================
53
54const option_builder_handler_t g_builder_handlers[] = {
55 [OPTION_TYPE_BOOL] = {is_set_bool, apply_env_bool, apply_cli_bool, format_help_placeholder_bool},
56 [OPTION_TYPE_INT] = {is_set_int, apply_env_int, apply_cli_int, format_help_placeholder_int},
57 [OPTION_TYPE_STRING] = {is_set_string, apply_env_string, apply_cli_string, format_help_placeholder_string},
58 [OPTION_TYPE_DOUBLE] = {is_set_double, apply_env_double, apply_cli_double, format_help_placeholder_double},
59 [OPTION_TYPE_CALLBACK] = {is_set_callback, apply_env_callback, apply_cli_callback,
60 format_help_placeholder_callback},
61 [OPTION_TYPE_ACTION] = {is_set_action, apply_env_action, apply_cli_action, format_help_placeholder_action},
62};
63static bool is_set_bool(const void *field, const option_descriptor_t *desc) {
64 // Safely read the bool value (may be uninitialized)
65 unsigned char value_byte = 0;
66 memcpy(&value_byte, field, 1);
67 bool value = (value_byte != 0);
68
69 bool default_val = false;
70 if (desc && desc->default_value) {
71 unsigned char default_byte = 0;
72 memcpy(&default_byte, desc->default_value, 1);
73 default_val = (default_byte != 0);
74 }
75 return value != default_val;
76}
77
78static bool is_set_int(const void *field, const option_descriptor_t *desc) {
79 int value = *(const int *)field;
80 int default_val = desc->default_value ? *(const int *)desc->default_value : 0;
81 return value != default_val;
82}
83
84static bool is_set_string(const void *field, const option_descriptor_t *desc) {
85 const char *value = (const char *)field;
86 const char *default_val = NULL;
87 if (desc->default_value) {
88 default_val = *(const char *const *)desc->default_value;
89 }
90 if (!default_val) {
91 return (value && value[0] != '\0');
92 }
93 return strcmp(value, default_val) != 0;
94}
95
96static bool is_set_double(const void *field, const option_descriptor_t *desc) {
97 double value = 0.0;
98 double default_val = 0.0;
99 memcpy(&value, field, sizeof(double));
100 if (desc->default_value) {
101 memcpy(&default_val, desc->default_value, sizeof(double));
102 }
103 return value != default_val;
104}
105
106static bool is_set_callback(const void *field, const option_descriptor_t *desc) {
107 (void)desc;
108 const void *value = NULL;
109 memcpy(&value, field, sizeof(const void *));
110 return value != NULL;
111}
112
113static bool is_set_action(const void *field, const option_descriptor_t *desc) {
114 (void)field;
115 (void)desc;
116 return false;
117}
118
119// --- apply_env handlers ---
120static void apply_env_bool(void *field, const char *env_value, const option_descriptor_t *desc) {
121 // Safely read the current bool value (may be uninitialized)
122 unsigned char current_byte = 0;
123 memcpy(&current_byte, field, 1);
124 bool current_value = (current_byte != 0);
125
126 // Safely read the default value
127 bool default_val = false;
128 if (desc && desc->default_value) {
129 unsigned char default_byte = 0;
130 memcpy(&default_byte, desc->default_value, 1);
131 default_val = (default_byte != 0);
132 }
133
134 if (current_value != default_val) {
135 return; // Already set, skip env var
136 }
137
138 bool value = false;
139 if (env_value) {
140 value = (strcmp(env_value, "1") == 0 || strcmp(env_value, "true") == 0 || strcmp(env_value, "yes") == 0 ||
141 strcmp(env_value, "on") == 0);
142 } else if (desc && desc->default_value) {
143 unsigned char default_byte = 0;
144 memcpy(&default_byte, desc->default_value, 1);
145 value = (default_byte != 0);
146 }
147
148 unsigned char value_byte = value ? 1 : 0;
149 memcpy(field, &value_byte, 1);
150}
151
152static void apply_env_int(void *field, const char *env_value, const option_descriptor_t *desc) {
153 int current_value = 0;
154 memcpy(&current_value, field, sizeof(int));
155 int default_val = desc->default_value ? *(const int *)desc->default_value : 0;
156 if (current_value != default_val) {
157 return; // Already set, skip env var
158 }
159 int value = 0;
160 if (env_value) {
161 char *endptr;
162 long parsed = strtol(env_value, &endptr, 10);
163 if (*endptr == '\0' && parsed >= INT_MIN && parsed <= INT_MAX) {
164 value = (int)parsed;
165 }
166 } else if (desc->default_value) {
167 value = *(const int *)desc->default_value;
168 }
169 memcpy(field, &value, sizeof(int));
170}
171
172static void apply_env_string(void *field, const char *env_value, const option_descriptor_t *desc) {
173 const char *current_value = (const char *)field;
174 const char *default_val = NULL;
175 if (desc->default_value) {
176 default_val = *(const char *const *)desc->default_value;
177 }
178 if (current_value && current_value[0] != '\0') {
179 if (!default_val || strcmp(current_value, default_val) != 0) {
180 return; // Already set, skip
181 }
182 }
183 const char *value = NULL;
184 if (env_value) {
185 value = env_value;
186 } else if (desc->default_value) {
187 value = *(const char *const *)desc->default_value;
188 }
189 if (value && value[0] != '\0') {
190 char *dest = (char *)field;
191 safe_snprintf(dest, OPTIONS_BUFF_SIZE, "%s", value);
192 dest[OPTIONS_BUFF_SIZE - 1] = '\0';
193 }
194}
195
196static void apply_env_double(void *field, const char *env_value, const option_descriptor_t *desc) {
197 double current_value = 0.0;
198 memcpy(&current_value, field, sizeof(double));
199 double default_val = 0.0;
200 if (desc->default_value) {
201 memcpy(&default_val, desc->default_value, sizeof(double));
202 }
203 if (current_value != default_val) {
204 return; // Already set, skip env var
205 }
206 double value = 0.0;
207 if (env_value) {
208 char *endptr;
209 value = strtod(env_value, &endptr);
210 if (*endptr != '\0') {
211 value = 0.0; // Parse failed
212 }
213 } else if (desc->default_value) {
214 value = *(const double *)desc->default_value;
215 }
216 memcpy(field, &value, sizeof(double));
217}
218
219static void apply_env_callback(void *field, const char *env_value, const option_descriptor_t *desc) {
220 (void)field;
221 (void)env_value;
222 // For callbacks, would need parse_fn - handled separately in options_config_set_defaults
223 (void)desc;
224}
225
226static void apply_env_action(void *field, const char *env_value, const option_descriptor_t *desc) {
227 (void)field;
228 (void)env_value;
229 (void)desc;
230}
231
232// --- apply_cli handlers ---
233// Helper function to parse boolean value from string
234// Accepts: "true", "1", "yes", "on" → true
235// "false", "0", "no", "off" → false
236// Returns ERROR_USAGE if value is invalid
237static asciichat_error_t parse_bool_value(const char *value_str, bool *out_value, const option_descriptor_t *desc) {
238 if (!value_str || value_str[0] == '\0') {
239 return SET_ERRNO(ERROR_USAGE, "Option --%s requires a value (true/false, yes/no, 1/0, on/off)",
240 desc ? desc->long_name : "unknown");
241 }
242
243 // Check for true values
244 if (strcasecmp(value_str, "true") == 0 || strcasecmp(value_str, "yes") == 0 || strcasecmp(value_str, "1") == 0 ||
245 strcasecmp(value_str, "on") == 0) {
246 *out_value = true;
247 return ASCIICHAT_OK;
248 }
249
250 // Check for false values
251 if (strcasecmp(value_str, "false") == 0 || strcasecmp(value_str, "no") == 0 || strcasecmp(value_str, "0") == 0 ||
252 strcasecmp(value_str, "off") == 0) {
253 *out_value = false;
254 return ASCIICHAT_OK;
255 }
256
257 // Invalid value
258 return SET_ERRNO(ERROR_USAGE, "Invalid boolean value for --%s: '%s' (use: true/false, yes/no, 1/0, on/off)",
259 desc ? desc->long_name : "unknown", value_str);
260}
261
262static asciichat_error_t apply_cli_bool(void *field, const char *opt_value, const option_descriptor_t *desc) {
263 bool new_value;
264
265 if (opt_value == NULL) {
266 // No value provided (flag without =value), toggle the current value
267 unsigned char current_byte = 0;
268 memcpy(&current_byte, field, 1);
269 bool current_value = (current_byte != 0);
270 new_value = !current_value;
271 } else {
272 // Value provided (--option=value), parse it
273 asciichat_error_t parse_result = parse_bool_value(opt_value, &new_value, desc);
274 if (parse_result != ASCIICHAT_OK) {
275 return parse_result;
276 }
277 }
278
279 // Set the new value
280 unsigned char new_value_byte = new_value ? 1 : 0;
281 memcpy(field, &new_value_byte, 1);
282 return ASCIICHAT_OK;
283}
284
285static asciichat_error_t apply_cli_int(void *field, const char *opt_value, const option_descriptor_t *desc) {
286 // Reject empty strings
287 if (!opt_value || opt_value[0] == '\0') {
288 return SET_ERRNO(ERROR_USAGE, "Option --%s requires a numeric value", desc ? desc->long_name : "unknown");
289 }
290
291 char *endptr;
292 long value = strtol(opt_value, &endptr, 10);
293 if (*endptr != '\0' || value < INT_MIN || value > INT_MAX) {
294 return ERROR_USAGE;
295 }
296 int int_value = (int)value;
297
298 // Check numeric range constraints if defined in descriptor's metadata
299 if (desc && desc->metadata.numeric_range.max != 0) {
300 if (int_value < desc->metadata.numeric_range.min || int_value > desc->metadata.numeric_range.max) {
301 return SET_ERRNO(ERROR_USAGE, "Value %d out of range [%d-%d]", int_value, desc->metadata.numeric_range.min,
302 desc->metadata.numeric_range.max);
303 }
304 }
305
306 memcpy(field, &int_value, sizeof(int));
307 return ASCIICHAT_OK;
308}
309
310static asciichat_error_t apply_cli_string(void *field, const char *opt_value, const option_descriptor_t *desc) {
311 // Reject empty strings for certain important options like --key
312 if (opt_value && opt_value[0] == '\0' && desc && desc->long_name) {
313 if (strcmp(desc->long_name, "key") == 0) {
314 return SET_ERRNO(ERROR_USAGE, "Option --%s cannot be empty", desc->long_name);
315 }
316 }
317
318 char *dest = (char *)field;
319 safe_snprintf(dest, OPTIONS_BUFF_SIZE, "%s", opt_value);
320 dest[OPTIONS_BUFF_SIZE - 1] = '\0';
321 return ASCIICHAT_OK;
322}
323
324static asciichat_error_t apply_cli_double(void *field, const char *opt_value, const option_descriptor_t *desc) {
325 (void)desc;
326 char *endptr;
327 double value = strtod(opt_value, &endptr);
328 // Check: endptr must have advanced past the input, and must be at the string end
329 if (endptr == opt_value || *endptr != '\0') {
330 return ERROR_USAGE;
331 }
332 memcpy(field, &value, sizeof(double));
333 return ASCIICHAT_OK;
334}
335
336static asciichat_error_t apply_cli_callback(void *field, const char *opt_value, const option_descriptor_t *desc) {
337 if (desc->parse_fn) {
338 char *error_msg = NULL;
339 if (!desc->parse_fn(opt_value, field, &error_msg)) {
340 asciichat_error_t err = SET_ERRNO(ERROR_USAGE, "Parse error: %s", error_msg ? error_msg : "unknown");
341 free(error_msg);
342 return err;
343 }
344 }
345 return ASCIICHAT_OK;
346}
347
348static asciichat_error_t apply_cli_action(void *field, const char *opt_value, const option_descriptor_t *desc) {
349 (void)field;
350 (void)opt_value;
351 if (desc->action_fn) {
352 desc->action_fn();
353 }
354 return ASCIICHAT_OK;
355}
356
357// --- format_help_placeholder handlers ---
358static void format_help_placeholder_bool(char *buf, size_t bufsize) {
359 safe_snprintf(buf, bufsize, "[BOOLEAN]");
360}
361
362static void format_help_placeholder_int(char *buf, size_t bufsize) {
363 safe_snprintf(buf, bufsize, "INTEGER");
364}
365
366static void format_help_placeholder_string(char *buf, size_t bufsize) {
367 safe_snprintf(buf, bufsize, "STRING");
368}
369
370static void format_help_placeholder_double(char *buf, size_t bufsize) {
371 safe_snprintf(buf, bufsize, "NUMBER");
372}
373
374static void format_help_placeholder_callback(char *buf, size_t bufsize) {
375 safe_snprintf(buf, bufsize, "VAL");
376}
377
378static void format_help_placeholder_action(char *buf, size_t bufsize) {
379 (void)buf;
380 (void)bufsize;
381 // Actions don't have placeholders
382}
const option_builder_handler_t g_builder_handlers[]
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456