ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
options/builder/handlers.c
Go to the documentation of this file.
1
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
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 if (env_value) {
154 char *endptr;
155 long parsed = strtol(env_value, &endptr, 10);
156 if (*endptr == '\0' && parsed >= INT_MIN && parsed <= INT_MAX) {
157 int value = (int)parsed;
158 memcpy(field, &value, sizeof(int));
159 }
160 return;
161 }
162 // No env var — apply descriptor default only if field hasn't been changed from descriptor default
163 int current_value = 0;
164 memcpy(&current_value, field, sizeof(int));
165 int default_val = desc->default_value ? *(const int *)desc->default_value : 0;
166 if (current_value != default_val)
167 return;
168 if (desc->default_value) {
169 int value = *(const int *)desc->default_value;
170 memcpy(field, &value, sizeof(int));
171 }
172}
173
174static void apply_env_string(void *field, const char *env_value, const option_descriptor_t *desc) {
175 const char *current_value = (const char *)field;
176 const char *default_val = NULL;
177 if (desc->default_value) {
178 default_val = *(const char *const *)desc->default_value;
179 }
180 if (current_value && current_value[0] != '\0') {
181 if (!default_val || strcmp(current_value, default_val) != 0) {
182 return; // Already set, skip
183 }
184 }
185 const char *value = NULL;
186 if (env_value) {
187 value = env_value;
188 } else if (desc->default_value) {
189 value = *(const char *const *)desc->default_value;
190 }
191 if (value && value[0] != '\0') {
192 char *dest = (char *)field;
193 safe_snprintf(dest, OPTIONS_BUFF_SIZE, "%s", value);
194 dest[OPTIONS_BUFF_SIZE - 1] = '\0';
195 }
196}
197
198static void apply_env_double(void *field, const char *env_value, const option_descriptor_t *desc) {
199 double current_value = 0.0;
200 memcpy(&current_value, field, sizeof(double));
201 double default_val = 0.0;
202 if (desc->default_value) {
203 memcpy(&default_val, desc->default_value, sizeof(double));
204 }
205 if (current_value != default_val) {
206 return; // Already set, skip env var
207 }
208 double value = 0.0;
209 if (env_value) {
210 char *endptr;
211 value = strtod(env_value, &endptr);
212 if (*endptr != '\0') {
213 value = 0.0; // Parse failed
214 }
215 } else if (desc->default_value) {
216 value = *(const double *)desc->default_value;
217 }
218 memcpy(field, &value, sizeof(double));
219}
220
221static void apply_env_callback(void *field, const char *env_value, const option_descriptor_t *desc) {
222 if (!env_value || !desc || !desc->parse_fn) {
223 return;
224 }
225 char *error_msg = NULL;
226 bool ok = desc->parse_fn(env_value, field, &error_msg);
227 if (!ok) {
228 log_warn("Invalid value for environment variable '%s': %s", desc->env_var_name ? desc->env_var_name : "unknown",
229 error_msg ? error_msg : "parse failed");
230 }
231 if (error_msg) {
232 free(error_msg);
233 }
234}
235
236static void apply_env_action(void *field, const char *env_value, const option_descriptor_t *desc) {
237 (void)field;
238 (void)env_value;
239 (void)desc;
240}
241
242// --- apply_cli handlers ---
243// Helper function to parse boolean value from string
244// Accepts: "true", "1", "yes", "on" → true
245// "false", "0", "no", "off" → false
246// Returns ERROR_USAGE if value is invalid
247static asciichat_error_t parse_bool_value(const char *value_str, bool *out_value, const option_descriptor_t *desc) {
248 if (!value_str || value_str[0] == '\0') {
249 return SET_ERRNO(ERROR_USAGE, "Option --%s requires a value (true/false, yes/no, 1/0, on/off)",
250 desc ? desc->long_name : "unknown");
251 }
252
253 // Check for true values
254 if (strcasecmp(value_str, "true") == 0 || strcasecmp(value_str, "yes") == 0 || strcasecmp(value_str, "1") == 0 ||
255 strcasecmp(value_str, "on") == 0) {
256 *out_value = true;
257 return ASCIICHAT_OK;
258 }
259
260 // Check for false values
261 if (strcasecmp(value_str, "false") == 0 || strcasecmp(value_str, "no") == 0 || strcasecmp(value_str, "0") == 0 ||
262 strcasecmp(value_str, "off") == 0) {
263 *out_value = false;
264 return ASCIICHAT_OK;
265 }
266
267 // Invalid value
268 return SET_ERRNO(ERROR_USAGE, "Invalid boolean value for --%s: '%s' (use: true/false, yes/no, 1/0, on/off)",
269 desc ? desc->long_name : "unknown", value_str);
270}
271
272static asciichat_error_t apply_cli_bool(void *field, const char *opt_value, const option_descriptor_t *desc) {
273 bool new_value;
274
275 if (opt_value == NULL) {
276 // No value provided (flag without =value), toggle the current value
277 unsigned char current_byte = 0;
278 memcpy(&current_byte, field, 1);
279 bool current_value = (current_byte != 0);
280 new_value = !current_value;
281 } else {
282 // Value provided (--option=value), parse it
283 asciichat_error_t parse_result = parse_bool_value(opt_value, &new_value, desc);
284 if (parse_result != ASCIICHAT_OK) {
285 return parse_result;
286 }
287 }
288
289 // Set the new value
290 unsigned char new_value_byte = new_value ? 1 : 0;
291 memcpy(field, &new_value_byte, 1);
292
293 return ASCIICHAT_OK;
294}
295
296static asciichat_error_t apply_cli_int(void *field, const char *opt_value, const option_descriptor_t *desc) {
297 // Reject empty strings
298 if (!opt_value || opt_value[0] == '\0') {
299 return SET_ERRNO(ERROR_USAGE, "Option --%s requires a numeric value", desc ? desc->long_name : "unknown");
300 }
301
302 char *endptr;
303 long value = strtol(opt_value, &endptr, 10);
304 if (*endptr != '\0' || value < INT_MIN || value > INT_MAX) {
305 return SET_ERRNO(ERROR_USAGE, "Option --%s: '%s' is not a valid integer", desc ? desc->long_name : "unknown",
306 opt_value);
307 }
308 int int_value = (int)value;
309
310 // Check numeric range constraints if defined in descriptor's metadata
311 if (desc && desc->metadata.numeric_range.max != 0) {
312 if (int_value < desc->metadata.numeric_range.min || int_value > desc->metadata.numeric_range.max) {
313 if (desc->long_name && strcmp(desc->long_name, "webcam-index") == 0) {
314 return SET_ERRNO(ERROR_USAGE,
315 "Webcam index %d is invalid; use a non-negative index (run --list-webcams to see devices)",
316 int_value);
317 }
318 return SET_ERRNO(ERROR_USAGE, "Value %d out of range [%d-%d]", int_value, desc->metadata.numeric_range.min,
320 }
321 }
322
323 memcpy(field, &int_value, sizeof(int));
324
325 return ASCIICHAT_OK;
326}
327
328static asciichat_error_t apply_cli_string(void *field, const char *opt_value, const option_descriptor_t *desc) {
329 // Reject empty strings for certain important options like --key
330 if (opt_value && opt_value[0] == '\0' && desc && desc->long_name) {
331 if (strcmp(desc->long_name, "key") == 0) {
332 return SET_ERRNO(ERROR_USAGE, "Option --%s cannot be empty", desc->long_name);
333 }
334 }
335
336 char *dest = (char *)field;
337 safe_snprintf(dest, OPTIONS_BUFF_SIZE, "%s", opt_value);
338 dest[OPTIONS_BUFF_SIZE - 1] = '\0';
339
340 return ASCIICHAT_OK;
341}
342
343static asciichat_error_t apply_cli_double(void *field, const char *opt_value, const option_descriptor_t *desc) {
344 (void)desc;
345 // Double options require an explicit value. An empty argument must not be
346 // silently converted to the default.
347 if (!opt_value || !opt_value[0]) {
348 return ERROR_USAGE;
349 }
350
351 char *endptr;
352 double value = strtod(opt_value, &endptr);
353 // Check: endptr must have advanced past the input, and must be at the string end
354 if (endptr == opt_value || *endptr != '\0') {
355 return ERROR_USAGE;
356 }
357 memcpy(field, &value, sizeof(double));
358
359 return ASCIICHAT_OK;
360}
361
362static asciichat_error_t apply_cli_callback(void *field, const char *opt_value, const option_descriptor_t *desc) {
363 if (desc->parse_fn) {
364 char *error_msg = NULL;
365 if (!desc->parse_fn(opt_value, field, &error_msg)) {
366 asciichat_error_t err = SET_ERRNO(ERROR_USAGE, "Parse error: %s", error_msg ? error_msg : "unknown");
367 free(error_msg);
368 return err;
369 }
370 }
371
372 return ASCIICHAT_OK;
373}
374
375static asciichat_error_t apply_cli_action(void *field, const char *opt_value, const option_descriptor_t *desc) {
376 (void)field;
377 (void)opt_value;
378 if (desc->action_fn) {
379 desc->action_fn();
380 }
381
382 return ASCIICHAT_OK;
383}
384
385// --- format_help_placeholder handlers ---
386static void format_help_placeholder_bool(char *buf, size_t bufsize) {
387 safe_snprintf(buf, bufsize, "[BOOLEAN]");
388}
389
390static void format_help_placeholder_int(char *buf, size_t bufsize) {
391 safe_snprintf(buf, bufsize, "INTEGER");
392}
393
394static void format_help_placeholder_string(char *buf, size_t bufsize) {
395 safe_snprintf(buf, bufsize, "STRING");
396}
397
398static void format_help_placeholder_double(char *buf, size_t bufsize) {
399 safe_snprintf(buf, bufsize, "NUMBER");
400}
401
402static void format_help_placeholder_callback(char *buf, size_t bufsize) {
403 safe_snprintf(buf, bufsize, "VAL");
404}
405
406static void format_help_placeholder_action(char *buf, size_t bufsize) {
407 (void)buf;
408 (void)bufsize;
409 // Actions don't have placeholders
410}
@ OPTION_TYPE_INT
Integer value (–count 42)
Definition builder.h:163
@ OPTION_TYPE_DOUBLE
Floating point (–ratio 1.5)
Definition builder.h:165
@ OPTION_TYPE_STRING
String value (–name foo)
Definition builder.h:164
@ OPTION_TYPE_ACTION
Action that executes and may exit (–list-webcams, etc.)
Definition builder.h:167
@ OPTION_TYPE_CALLBACK
Custom parser function.
Definition builder.h:166
@ OPTION_TYPE_BOOL
Boolean flag (–flag, no value)
Definition builder.h:162
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_USAGE
Definition error_codes.h:53
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
Application limits and constraints.
const option_builder_handler_t g_builder_handlers[]
Internal declarations for builder implementation.
Common utilities and helpers for option parsing across all modes.
Type handler for builder operations.
Option descriptor.
Definition builder.h:241
const char * env_var_name
Environment variable fallback (or NULL)
Definition builder.h:260
const void * default_value
Pointer to default value (or NULL if required)
Definition builder.h:258
bool(* parse_fn)(const char *arg, void *dest, char **error_msg)
Definition builder.h:266
const char * long_name
Long option name (e.g., "port")
Definition builder.h:243
void(* action_fn)(void)
Action to execute (may call exit)
Definition builder.h:269
option_metadata_t metadata
Metadata for shell completions (enums, ranges, examples, etc.)
Definition builder.h:281
int max
Maximum value (or 0 if no limit)
Definition builder.h:203
int min
Minimum value (or 0 if no limit)
Definition builder.h:202
struct option_metadata_t::@19 numeric_range
🔤 String Manipulation and Shell Escaping Utilities