ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
builder.c
Go to the documentation of this file.
1
10#include <ascii-chat/common.h>
11#include <ascii-chat/log/log.h>
18#include <stdlib.h>
19#include <string.h>
20#include <limits.h>
21
22// Use platform utilities
24#define asprintf platform_asprintf
25
26// ============================================================================
27// Static counters for default value storage
28// ============================================================================
29static int g_int_defaults[256];
30static size_t g_int_defaults_counter = 0;
31
32// ============================================================================
33// ============================================================================
34// Help Formatting Helper Functions
35// ============================================================================
36
44 if (!desc) {
45 return "";
46 }
47 // Check for custom placeholder first
48 if (desc->arg_placeholder != NULL) {
49 return desc->arg_placeholder;
50 }
51
52 // For callback options, check if it's a boolean based on enum values
53 if (desc->type == OPTION_TYPE_CALLBACK && desc->metadata.enum_values) {
54 // Check if enum values contain "true" and "false" (boolean-like)
55 const char *const *values = desc->metadata.enum_values;
56 bool has_true = false, has_false = false;
57
58 for (int i = 0; values[i] != NULL; i++) {
59 if (strcmp(values[i], "true") == 0 || strcmp(values[i], "yes") == 0 || strcmp(values[i], "on") == 0) {
60 has_true = true;
61 }
62 if (strcmp(values[i], "false") == 0 || strcmp(values[i], "no") == 0 || strcmp(values[i], "off") == 0) {
63 has_false = true;
64 }
65 }
66
67 // If it has both true-like and false-like values, it's boolean
68 if (has_true && has_false) {
69 return "BOOLEAN";
70 }
71 }
72
74}
75
83int format_option_default_value_str(const option_descriptor_t *desc, char *buf, size_t bufsize) {
84 if (!desc || !buf || bufsize == 0) {
85 return 0;
86 }
87
88 // For callback options with enums, look up the enum string by matching the default value
89 if (desc->type == OPTION_TYPE_CALLBACK && desc->metadata.enum_values && desc->default_value) {
90 int default_int_val = 0;
91 memcpy(&default_int_val, desc->default_value, sizeof(int));
92
93 // Try to find matching enum value
94 if (desc->metadata.enum_integer_values) {
95 for (size_t i = 0; desc->metadata.enum_values[i] != NULL; i++) {
96 if (desc->metadata.enum_integer_values[i] == default_int_val) {
97 return safe_snprintf(buf, bufsize, "%s", desc->metadata.enum_values[i]);
98 }
99 }
100 } else {
101 // Fallback: assume sequential 0-based indices if integer values not provided
102 // Count enum values to check bounds
103 size_t enum_count = 0;
104 while (desc->metadata.enum_values[enum_count] != NULL) {
105 enum_count++;
106 }
107 if (default_int_val >= 0 && (size_t)default_int_val < enum_count) {
108 return safe_snprintf(buf, bufsize, "%s", desc->metadata.enum_values[default_int_val]);
109 }
110 }
111 }
112
113 // For callback options storing numeric types (int/double/float), format them as numbers
114 if (desc->type == OPTION_TYPE_CALLBACK && desc->default_value && desc->metadata.enum_values == NULL) {
115 // Check if this is a numeric callback by looking for numeric_range metadata
116 // Numeric callbacks have min/max range constraints set (max != 0 indicates a range was set)
117 if (desc->metadata.numeric_range.max != 0 || desc->metadata.numeric_range.min != 0) {
118 // This is a numeric callback option - extract and format the value
119 // Determine if it's int or double based on range (int max is 2147483647)
120 double default_double = 0.0;
121 if (desc->metadata.numeric_range.max <= 2147483647.0 && desc->metadata.numeric_range.min >= -2147483648.0) {
122 // Integer-ranged value (like port 1-65535) - stored as int
123 int default_int = 0;
124 memcpy(&default_int, desc->default_value, sizeof(int));
125 default_double = (double)default_int;
126 } else {
127 // Double-ranged value - stored as double
128 memcpy(&default_double, desc->default_value, sizeof(double));
129 }
130
131 // Format with appropriate precision (remove trailing zeros for integers)
132 char formatted[32];
133 safe_snprintf(formatted, sizeof(formatted), "%.1f", default_double);
134
135 // Remove trailing zeros after decimal point
136 char *dot = strchr(formatted, '.');
137 if (dot) {
138 char *end = formatted + strlen(formatted) - 1;
139 while (end > dot && *end == '0') {
140 end--;
141 }
142 if (*end == '.') {
143 *end = '\0';
144 } else {
145 *(end + 1) = '\0';
146 }
147 }
148
149 return safe_snprintf(buf, bufsize, "%s", formatted);
150 }
151 }
152
153 // For callback options with string defaults (no enum, no numeric range)
154 // Treat default_value as a const char* string pointer
155 if (desc->type == OPTION_TYPE_CALLBACK && desc->default_value && desc->metadata.enum_values == NULL &&
156 desc->metadata.numeric_range.max == 0 && desc->metadata.numeric_range.min == 0) {
157 const char *str_default = (const char *)desc->default_value;
158 if (str_default && str_default[0] != '\0') {
159 return safe_snprintf(buf, bufsize, "%s", str_default);
160 }
161 }
162
163 return options_format_default_value(desc->type, desc->default_value, buf, bufsize);
164}
165
166// ============================================================================
167// Internal Helper Functions
168// ============================================================================
169
177bool option_applies_to_mode(const option_descriptor_t *desc, asciichat_mode_t mode, bool for_binary_help) {
178 if (!desc) {
179 SET_ERRNO(ERROR_INVALID_PARAM, "Descriptor is NULL");
180 return false;
181 }
182
183 // When for_binary_help is true (i.e., for 'ascii-chat --help'),
184 // show options that apply to the default mode (DISCOVERY) or binary-level options only.
185 // Don't show mode-specific options for other modes (server, client, mirror, discovery-service).
186 if (for_binary_help) {
187 // Binary-level options or options that apply to discovery mode (the default)
189 return (desc->mode_bitmask & default_modes) != 0 && !desc->hide_from_binary_help;
190 }
191
192 // For mode-specific help (non-discovery modes), show only options for that mode.
193 // Do not show binary options here unless it also specifically applies to the mode.
194 // The 'mode' parameter is the actual mode (e.g., MODE_CLIENT, MODE_SERVER).
195 if (mode < 0 || mode > MODE_DISCOVERY) { // Use MODE_INVALID as upper bound for valid modes
196 return false;
197 }
198 option_mode_bitmask_t mode_bit = (1 << mode);
199
200 // Check if it's a binary option. If so, only show if it also explicitly applies to this mode.
201 if ((desc->mode_bitmask & OPTION_MODE_BINARY) && !(desc->mode_bitmask & mode_bit)) {
202 return false; // Binary options not shown in mode-specific help unless also mode-specific
203 }
204
205 bool applies = (desc->mode_bitmask & mode_bit) != 0;
206
207 return applies && !desc->hide_from_mode_help;
208}
209
214 if (builder->num_descriptors >= builder->descriptor_capacity) {
215 size_t new_capacity = builder->descriptor_capacity * 2;
216 option_descriptor_t *new_descriptors =
217 SAFE_REALLOC(builder->descriptors, new_capacity * sizeof(option_descriptor_t), option_descriptor_t *);
218 if (!new_descriptors) {
219 log_fatal("Failed to reallocate descriptors array");
220 return;
221 }
222 builder->descriptors = new_descriptors;
223 builder->descriptor_capacity = new_capacity;
224 }
225}
226
231 if (builder->num_dependencies >= builder->dependency_capacity) {
232 size_t new_capacity = builder->dependency_capacity * 2;
233 option_dependency_t *new_dependencies =
234 SAFE_REALLOC(builder->dependencies, new_capacity * sizeof(option_dependency_t), option_dependency_t *);
235 if (!new_dependencies) {
236 log_fatal("Failed to reallocate dependencies array");
237 return;
238 }
239 builder->dependencies = new_dependencies;
240 builder->dependency_capacity = new_capacity;
241 }
242}
243
248 if (builder->num_positional_args >= builder->positional_arg_capacity) {
249 size_t new_capacity = builder->positional_arg_capacity * 2;
250 if (new_capacity == 0)
251 new_capacity = INITIAL_POSITIONAL_ARG_CAPACITY;
252
255 if (!new_positional) {
256 log_fatal("Failed to reallocate positional_args array");
257 return;
258 }
259 builder->positional_args = new_positional;
260 builder->positional_arg_capacity = new_capacity;
261 }
262}
263
267static void ensure_usage_line_capacity(options_builder_t *builder) {
268 if (builder->num_usage_lines >= builder->usage_line_capacity) {
269 size_t new_capacity = builder->usage_line_capacity * 2;
270 if (new_capacity == 0)
271 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
272
273 usage_descriptor_t *new_usage =
274 SAFE_REALLOC(builder->usage_lines, new_capacity * sizeof(usage_descriptor_t), usage_descriptor_t *);
275 if (!new_usage) {
276 log_fatal("Failed to reallocate usage_lines array");
277 return;
278 }
279 builder->usage_lines = new_usage;
280 builder->usage_line_capacity = new_capacity;
281 }
282}
283
287static void ensure_example_capacity(options_builder_t *builder) {
288 if (builder->num_examples >= builder->example_capacity) {
289 size_t new_capacity = builder->example_capacity * 2;
290 if (new_capacity == 0)
291 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
292
293 example_descriptor_t *new_examples =
294 SAFE_REALLOC(builder->examples, new_capacity * sizeof(example_descriptor_t), example_descriptor_t *);
295 if (!new_examples) {
296 log_fatal("Failed to reallocate examples array");
297 return;
298 }
299 builder->examples = new_examples;
300 builder->example_capacity = new_capacity;
301 }
302}
303
307static void ensure_mode_capacity(options_builder_t *builder) {
308 if (builder->num_modes >= builder->mode_capacity) {
309 size_t new_capacity = builder->mode_capacity * 2;
310 if (new_capacity == 0)
311 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
312
313 help_mode_descriptor_t *new_modes =
314 SAFE_REALLOC(builder->modes, new_capacity * sizeof(help_mode_descriptor_t), help_mode_descriptor_t *);
315 if (!new_modes) {
316 log_fatal("Failed to reallocate modes array");
317 return;
318 }
319 builder->modes = new_modes;
320 builder->mode_capacity = new_capacity;
321 }
322}
323
324static void ensure_custom_section_capacity(options_builder_t *builder) {
325 if (builder->num_custom_sections >= builder->custom_section_capacity) {
326 size_t new_capacity = builder->custom_section_capacity * 2;
327 if (new_capacity == 0)
328 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
329
332 if (!new_sections) {
333 log_fatal("Failed to reallocate custom sections array");
334 return;
335 }
336 builder->custom_sections = new_sections;
337 builder->custom_section_capacity = new_capacity;
338 }
339}
340
344const option_descriptor_t *find_option(const options_config_t *config, const char *long_name) {
345 for (size_t i = 0; i < config->num_descriptors; i++) {
346 if (strcmp(config->descriptors[i].long_name, long_name) == 0) {
347 return &config->descriptors[i];
348 }
349 }
350 return NULL;
351}
352
356bool is_option_set(const options_config_t *config, const void *options_struct, const char *option_name) {
357 const option_descriptor_t *desc = find_option(config, option_name);
358 if (!desc)
359 return false;
360
361 const char *base = (const char *)options_struct;
362 const void *field = base + desc->offset;
363
364 // Use handler registry to check if option is set
365 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
366 if (g_builder_handlers[desc->type].is_set) {
367 return g_builder_handlers[desc->type].is_set(field, desc);
368 }
369 }
370
371 return false;
372}
373
374// ============================================================================
375// Builder Lifecycle
376// ============================================================================
377
379 // Reset int defaults counter for new builder
380 g_int_defaults_counter = 0;
381
383 if (!builder) {
384 SET_ERRNO(ERROR_MEMORY, "Failed to allocate options builder");
385 return NULL;
386 }
387
389 if (!builder->descriptors) {
390 free(builder);
391 SET_ERRNO(ERROR_MEMORY, "Failed to allocate descriptors array");
392 return NULL;
393 }
394
396 if (!builder->dependencies) {
397 SAFE_FREE(builder->descriptors);
398 SAFE_FREE(builder);
399 SET_ERRNO(ERROR_MEMORY, "Failed to allocate dependencies array");
400 return NULL;
401 }
402
403 builder->num_descriptors = 0;
405 builder->num_dependencies = 0;
407 builder->positional_args = NULL;
408 builder->num_positional_args = 0;
409 builder->positional_arg_capacity = 0;
410 builder->usage_lines = NULL;
411 builder->num_usage_lines = 0;
412 builder->usage_line_capacity = 0;
413 builder->examples = NULL;
414 builder->num_examples = 0;
415 builder->example_capacity = 0;
416 builder->modes = NULL;
417 builder->num_modes = 0;
418 builder->mode_capacity = 0;
419 builder->custom_sections = NULL;
420 builder->num_custom_sections = 0;
421 builder->custom_section_capacity = 0;
422 builder->struct_size = struct_size;
423 builder->program_name = NULL;
424 builder->description = NULL;
425 builder->owned_strings_builder = NULL;
426 builder->num_owned_strings_builder = 0;
428
429 return builder;
430}
431
433 if (!preset) {
434 SET_ERRNO(ERROR_INVALID_PARAM, "Preset config is NULL");
435 return NULL;
436 }
437
439 if (!builder) {
440 return NULL;
441 }
442
443 builder->program_name = preset->program_name;
444 builder->description = preset->description;
445
446 // Copy all descriptors
447 for (size_t i = 0; i < preset->num_descriptors; i++) {
448 options_builder_add_descriptor(builder, &preset->descriptors[i]);
449 }
450
451 // Copy all dependencies
452 for (size_t i = 0; i < preset->num_dependencies; i++) {
453 options_builder_add_dependency(builder, &preset->dependencies[i]);
454 }
455
456 // Copy all positional arguments
457 for (size_t i = 0; i < preset->num_positional_args; i++) {
458 const positional_arg_descriptor_t *pos_arg = &preset->positional_args[i];
459 options_builder_add_positional(builder, pos_arg->name, pos_arg->help_text, pos_arg->required,
460 pos_arg->section_heading, pos_arg->examples, pos_arg->num_examples,
461 pos_arg->mode_bitmask, pos_arg->parse_fn);
462 }
463
464 return builder;
465}
466
468 if (!builder)
469 return;
470
471 SAFE_FREE(builder->descriptors);
472 SAFE_FREE(builder->dependencies);
473 SAFE_FREE(builder->positional_args);
474 SAFE_FREE(builder->usage_lines);
475 SAFE_FREE(builder->examples);
476 SAFE_FREE(builder->modes);
477 SAFE_FREE(builder->custom_sections);
478 SAFE_FREE(builder->owned_strings_builder); // Free the builder's owned strings
479 SAFE_FREE(builder);
480}
481
483 if (!builder) {
484 SET_ERRNO(ERROR_INVALID_PARAM, "Builder is NULL");
485 return NULL;
486 }
487
489 if (!config) {
490 SET_ERRNO(ERROR_MEMORY, "Failed to allocate options config");
491 return NULL;
492 }
493
494 // Allocate and copy descriptors
496 if (!config->descriptors && builder->num_descriptors > 0) {
497 SAFE_FREE(config);
498 SET_ERRNO(ERROR_MEMORY, "Failed to allocate descriptors");
499 return NULL;
500 }
501 memcpy(config->descriptors, builder->descriptors, builder->num_descriptors * sizeof(option_descriptor_t));
502 config->num_descriptors = builder->num_descriptors;
503
504 // Allocate and copy dependencies
506 if (!config->dependencies && builder->num_dependencies > 0) {
507 SAFE_FREE(config->descriptors);
508 SAFE_FREE(config);
509 SET_ERRNO(ERROR_MEMORY, "Failed to allocate dependencies");
510 return NULL;
511 }
512 memcpy(config->dependencies, builder->dependencies, builder->num_dependencies * sizeof(option_dependency_t));
513 config->num_dependencies = builder->num_dependencies;
514
515 // Allocate and copy positional args
516 if (builder->num_positional_args > 0) {
517 config->positional_args =
519 if (!config->positional_args) {
520 SAFE_FREE(config->descriptors);
521 SAFE_FREE(config->dependencies);
522 SAFE_FREE(config);
523 SET_ERRNO(ERROR_MEMORY, "Failed to allocate positional args");
524 return NULL;
525 }
526 memcpy(config->positional_args, builder->positional_args,
528 config->num_positional_args = builder->num_positional_args;
529 } else {
530 config->positional_args = NULL;
531 config->num_positional_args = 0;
532 }
533
534 // Allocate and copy usage lines
535 if (builder->num_usage_lines > 0) {
537 if (!config->usage_lines) {
538 SAFE_FREE(config->descriptors);
539 SAFE_FREE(config->dependencies);
540 SAFE_FREE(config->positional_args);
541 SAFE_FREE(config);
542 SET_ERRNO(ERROR_MEMORY, "Failed to allocate usage lines");
543 return NULL;
544 }
545 memcpy(config->usage_lines, builder->usage_lines, builder->num_usage_lines * sizeof(usage_descriptor_t));
546 config->num_usage_lines = builder->num_usage_lines;
547 } else {
548 config->usage_lines = NULL;
549 config->num_usage_lines = 0;
550 }
551
552 // Allocate and copy examples
553 if (builder->num_examples > 0) {
555 if (!config->examples) {
556 SAFE_FREE(config->descriptors);
557 SAFE_FREE(config->dependencies);
558 SAFE_FREE(config->positional_args);
559 SAFE_FREE(config->usage_lines);
560 SAFE_FREE(config);
561 SET_ERRNO(ERROR_MEMORY, "Failed to allocate examples");
562 return NULL;
563 }
564 memcpy(config->examples, builder->examples, builder->num_examples * sizeof(example_descriptor_t));
565 config->num_examples = builder->num_examples;
566 } else {
567 config->examples = NULL;
568 config->num_examples = 0;
569 }
570
571 // Allocate and copy modes
572 if (builder->num_modes > 0) {
574 if (!config->modes) {
575 SAFE_FREE(config->descriptors);
576 SAFE_FREE(config->dependencies);
577 SAFE_FREE(config->positional_args);
578 SAFE_FREE(config->usage_lines);
579 SAFE_FREE(config->examples);
580 SAFE_FREE(config);
581 SET_ERRNO(ERROR_MEMORY, "Failed to allocate modes");
582 return NULL;
583 }
584 memcpy(config->modes, builder->modes, builder->num_modes * sizeof(help_mode_descriptor_t));
585 config->num_modes = builder->num_modes;
586 } else {
587 config->modes = NULL;
588 config->num_modes = 0;
589 }
590
591 // Allocate and copy custom sections
592 if (builder->num_custom_sections > 0) {
593 config->custom_sections =
595 if (!config->custom_sections) {
596 SAFE_FREE(config->descriptors);
597 SAFE_FREE(config->dependencies);
598 SAFE_FREE(config->positional_args);
599 SAFE_FREE(config->usage_lines);
600 SAFE_FREE(config->examples);
601 SAFE_FREE(config->modes);
602 SAFE_FREE(config);
603 SET_ERRNO(ERROR_MEMORY, "Failed to allocate custom sections");
604 return NULL;
605 }
606 memcpy(config->custom_sections, builder->custom_sections,
608 config->num_custom_sections = builder->num_custom_sections;
609 } else {
610 config->custom_sections = NULL;
611 config->num_custom_sections = 0;
612 }
613
614 config->struct_size = builder->struct_size;
615 config->program_name = builder->program_name;
616 config->description = builder->description;
617
618 // Initialize memory management
619 config->owned_strings = builder->owned_strings_builder;
622
623 // Clear builder's ownership so it doesn't free them
624 builder->owned_strings_builder = NULL;
625 builder->num_owned_strings_builder = 0;
627
628 return config;
629}
630
632 if (!config)
633 return;
634
635 SAFE_FREE(config->descriptors);
636 SAFE_FREE(config->dependencies);
637 SAFE_FREE(config->positional_args);
638 SAFE_FREE(config->usage_lines);
639 SAFE_FREE(config->examples);
640 SAFE_FREE(config->modes);
641 SAFE_FREE(config->custom_sections);
642
643 // Free all owned strings before freeing the array
644 // Use SAFE_FREE because platform_strdup uses SAFE_MALLOC (mimalloc on Windows)
645 for (size_t i = 0; i < config->num_owned_strings; i++) {
646 SAFE_FREE(config->owned_strings[i]);
647 }
648 SAFE_FREE(config->owned_strings);
649
650 SAFE_FREE(config);
651}
652
653// ============================================================================
654// Adding Options
655// ============================================================================
656
657void options_builder_add_bool(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
658 bool default_value, const char *help_text, const char *group, bool required,
659 const char *env_var_name) {
661
662 static bool default_vals[2] = {false, true};
663
664 option_descriptor_t desc = {.long_name = long_name,
665 .short_name = short_name,
666 .type = OPTION_TYPE_BOOL,
667 .offset = offset,
668 .help_text = help_text,
669 .group = group,
670 .default_value = &default_vals[default_value ? 1 : 0],
671 .required = required,
672 .env_var_name = env_var_name,
673 .mode_bitmask = OPTION_MODE_NONE,
674 .validate = NULL,
675 .parse_fn = NULL,
676 .owns_memory = false};
677
678 builder->descriptors[builder->num_descriptors++] = desc;
679}
680
681void options_builder_add_int(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
682 int default_value, const char *help_text, const char *group, bool required,
683 const char *env_var_name, bool (*validate)(const void *, char **)) {
685
686 if (g_int_defaults_counter >= 256) {
687 log_error("Too many int options (max 256)");
688 return;
689 }
690
691 g_int_defaults[g_int_defaults_counter] = default_value;
692
693 option_descriptor_t desc = {.long_name = long_name,
694 .short_name = short_name,
695 .type = OPTION_TYPE_INT,
696 .offset = offset,
697 .help_text = help_text,
698 .group = group,
699 .default_value = &g_int_defaults[g_int_defaults_counter++],
700 .required = required,
701 .env_var_name = env_var_name,
702 .validate = validate,
703 .parse_fn = NULL,
704 .owns_memory = false,
705 .mode_bitmask = OPTION_MODE_NONE};
706
707 builder->descriptors[builder->num_descriptors++] = desc;
708}
709
710void options_builder_add_int_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
711 size_t offset, int default_value, const char *help_text, const char *group,
712 bool required, const char *env_var_name,
713 bool (*validate)(const void *options_struct, char **error_msg),
714 const option_metadata_t *metadata) {
716
717 if (g_int_defaults_counter >= 256) {
718 log_error("Too many int options (max 256)");
719 return;
720 }
721
722 g_int_defaults[g_int_defaults_counter] = default_value;
723
724 option_descriptor_t desc = {.long_name = long_name,
725 .short_name = short_name,
726 .type = OPTION_TYPE_INT,
727 .offset = offset,
728 .help_text = help_text,
729 .group = group,
730 .default_value = &g_int_defaults[g_int_defaults_counter++],
731 .required = required,
732 .env_var_name = env_var_name,
733 .validate = validate,
734 .parse_fn = NULL,
735 .owns_memory = false,
736 .mode_bitmask = OPTION_MODE_NONE,
737 .metadata = metadata ? *metadata : (option_metadata_t){0}};
738
739 builder->descriptors[builder->num_descriptors++] = desc;
740}
741
742void options_builder_add_string(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
743 const char *default_value, const char *help_text, const char *group, bool required,
744 const char *env_var_name, bool (*validate)(const void *, char **)) {
746
747 // Store default string pointer in static storage to avoid stack-use-after-return
748 // Increased from 256 to 1024 to support multiple test builders without overflow
749 static const char *defaults[1024];
750 static size_t num_defaults = 0;
751
752 const void *default_ptr = NULL;
753 if (default_value) {
754 if (num_defaults >= 1024) {
755 log_error("Too many string options (max 1024)");
756 return;
757 }
758 defaults[num_defaults] = default_value;
759 default_ptr = &defaults[num_defaults++];
760 }
761
762 option_descriptor_t desc = {.long_name = long_name,
763 .short_name = short_name,
764 .type = OPTION_TYPE_STRING,
765 .offset = offset,
766 .help_text = help_text,
767 .group = group,
768 .default_value = default_ptr,
769 .required = required,
770 .env_var_name = env_var_name,
771 .validate = validate,
772 .parse_fn = NULL,
773 .owns_memory = true, // Strings are always owned
774 .mode_bitmask = OPTION_MODE_NONE};
775
776 builder->descriptors[builder->num_descriptors++] = desc;
777}
778
779void options_builder_add_double(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
780 double default_value, const char *help_text, const char *group, bool required,
781 const char *env_var_name, bool (*validate)(const void *, char **), bool optional_arg) {
783
784 // Increased from 256 to 1024 to support multiple test builders without overflow
785 static double defaults[1024];
786 static size_t num_defaults = 0;
787
788 if (num_defaults >= 1024) {
789 log_error("Too many double options (max 1024)");
790 return;
791 }
792
793 defaults[num_defaults] = default_value;
794
795 option_descriptor_t desc = {.long_name = long_name,
796 .short_name = short_name,
797 .type = OPTION_TYPE_DOUBLE,
798 .offset = offset,
799 .help_text = help_text,
800 .group = group,
801 .default_value = &defaults[num_defaults++],
802 .required = required,
803 .env_var_name = env_var_name,
804 .validate = validate,
805 .parse_fn = NULL,
806 .owns_memory = false,
807 .mode_bitmask = OPTION_MODE_NONE,
808 .optional_arg = optional_arg};
809
810 builder->descriptors[builder->num_descriptors++] = desc;
811}
812
813void options_builder_add_double_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
814 size_t offset, double default_value, const char *help_text,
815 const char *group, bool required, const char *env_var_name,
816 bool (*validate)(const void *, char **),
817 const option_metadata_t *metadata, bool optional_arg) {
819
820 static double defaults[1024];
821 static size_t num_defaults = 0;
822
823 if (num_defaults >= 1024) {
824 log_error("Too many double options (max 1024)");
825 return;
826 }
827
828 defaults[num_defaults] = default_value;
829
830 option_descriptor_t desc = {.long_name = long_name,
831 .short_name = short_name,
832 .type = OPTION_TYPE_DOUBLE,
833 .offset = offset,
834 .help_text = help_text,
835 .group = group,
836 .default_value = &defaults[num_defaults++],
837 .required = required,
838 .env_var_name = env_var_name,
839 .validate = validate,
840 .parse_fn = NULL,
841 .owns_memory = false,
842 .mode_bitmask = OPTION_MODE_NONE,
843 .metadata = metadata ? *metadata : (option_metadata_t){0},
844 .optional_arg = optional_arg};
845
846 builder->descriptors[builder->num_descriptors++] = desc;
847}
848
849void options_builder_add_callback(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
850 const void *default_value, size_t value_size,
851 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
852 const char *group, bool required, const char *env_var_name) {
853 (void)value_size; // Unused parameter
855
856 option_descriptor_t desc = {.long_name = long_name,
857 .short_name = short_name,
858 .type = OPTION_TYPE_CALLBACK,
859 .offset = offset,
860 .help_text = help_text,
861 .group = group,
862 .default_value = default_value,
863 .required = required,
864 .env_var_name = env_var_name,
865 .validate = NULL,
866 .parse_fn = parse_fn,
867 .owns_memory = false,
868 .optional_arg = false,
869 .mode_bitmask = OPTION_MODE_NONE};
870
871 builder->descriptors[builder->num_descriptors++] = desc;
872}
873
890void options_builder_add_callback_optional(options_builder_t *builder, const char *long_name, char short_name,
891 size_t offset, const void *default_value, size_t value_size,
892 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
893 const char *group, bool required, const char *env_var_name,
894 bool optional_arg) {
895 (void)value_size; // Unused parameter
897
898 option_descriptor_t desc = {.long_name = long_name,
899 .short_name = short_name,
900 .type = OPTION_TYPE_CALLBACK,
901 .offset = offset,
902 .help_text = help_text,
903 .group = group,
904 .default_value = default_value,
905 .required = required,
906 .env_var_name = env_var_name,
907 .validate = NULL,
908 .parse_fn = parse_fn,
909 .owns_memory = false,
910 .optional_arg = optional_arg,
911 .mode_bitmask = OPTION_MODE_NONE};
912
913 builder->descriptors[builder->num_descriptors++] = desc;
914}
915
916void options_builder_add_callback_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
917 size_t offset, const void *default_value, size_t value_size,
918 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
919 const char *group, bool required, const char *env_var_name,
920 bool optional_arg, const option_metadata_t *metadata) {
921 (void)value_size; // Unused parameter
923
924 option_descriptor_t desc = {.long_name = long_name,
925 .short_name = short_name,
926 .type = OPTION_TYPE_CALLBACK,
927 .offset = offset,
928 .help_text = help_text,
929 .group = group,
930 .default_value = default_value,
931 .required = required,
932 .env_var_name = env_var_name,
933 .validate = NULL,
934 .parse_fn = parse_fn,
935 .owns_memory = false,
936 .optional_arg = optional_arg,
937 .mode_bitmask = OPTION_MODE_NONE,
938 .metadata = metadata ? *metadata : (option_metadata_t){0}};
939
940 builder->descriptors[builder->num_descriptors++] = desc;
941}
942
943void options_builder_add_action(options_builder_t *builder, const char *long_name, char short_name,
944 void (*action_fn)(void), const char *help_text, const char *group) {
946
947 option_descriptor_t desc = {.long_name = long_name,
948 .short_name = short_name,
949 .type = OPTION_TYPE_ACTION,
950 .offset = 0, // Actions don't store values
951 .help_text = help_text,
952 .group = group,
953 .default_value = NULL,
954 .required = false, // Actions are never required
955 .env_var_name = NULL,
956 .validate = NULL,
957 .parse_fn = NULL,
958 .action_fn = action_fn,
959 .owns_memory = false,
960 .hide_from_mode_help = false,
961 .hide_from_binary_help = false,
962 .mode_bitmask = OPTION_MODE_NONE};
963
964 builder->descriptors[builder->num_descriptors++] = desc;
965
966 // Set hide_from_binary_help after adding (so we can check the option name)
967 if (strcmp(long_name, "create-man-page") == 0) {
968 // Always hide from help and man page (this is a development tool)
969 builder->descriptors[builder->num_descriptors - 1].hide_from_binary_help = true;
970 }
971}
972
974 if (!builder || !descriptor) {
975 SET_ERRNO(ERROR_INVALID_PARAM, "Builder is NULL or descriptor is NULL");
976 return;
977 }
978 return;
979
981 builder->descriptors[builder->num_descriptors++] = *descriptor;
982}
983
985 if (!builder || builder->num_descriptors == 0) {
986 SET_ERRNO(ERROR_INVALID_STATE, "Builder is NULL or has no descriptors");
987 return;
988 }
989 builder->descriptors[builder->num_descriptors - 1].mode_bitmask = mode_bitmask;
990}
991
992void options_builder_set_arg_placeholder(options_builder_t *builder, const char *arg_placeholder) {
993 if (!builder || builder->num_descriptors == 0) {
994 SET_ERRNO(ERROR_INVALID_STATE, "Builder is NULL or has no descriptors");
995 return;
996 }
997 builder->descriptors[builder->num_descriptors - 1].arg_placeholder = arg_placeholder;
998}
999
1000// ============================================================================
1001// Completion Metadata (Phase 2 Implementation)
1002// ============================================================================
1003
1010static int find_descriptor_in_builder(const options_builder_t *builder, const char *option_name) {
1011 if (!builder || !option_name) {
1012 return -1;
1013 }
1014 for (size_t i = 0; i < builder->num_descriptors; i++) {
1015 if (builder->descriptors[i].long_name && strcmp(builder->descriptors[i].long_name, option_name) == 0) {
1016 return (int)i;
1017 }
1018 }
1019 return -1;
1020}
1021
1022void options_builder_set_enum_values(options_builder_t *builder, const char *option_name, const char **values,
1023 const char **descriptions) {
1024 if (!builder || !option_name || !values || !descriptions) {
1025 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or arguments are NULL");
1026 return;
1027 }
1028
1029 int idx = find_descriptor_in_builder(builder, option_name);
1030 if (idx < 0) {
1031 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1032 return;
1033 }
1034
1035 option_descriptor_t *desc = &builder->descriptors[idx];
1036 desc->metadata.enum_values = values;
1037 desc->metadata.enum_descriptions = descriptions;
1038}
1039
1040void options_builder_set_numeric_range(options_builder_t *builder, const char *option_name, int min, int max,
1041 int step) {
1042 if (!builder || !option_name) {
1043 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1044 return;
1045 }
1046
1047 int idx = find_descriptor_in_builder(builder, option_name);
1048 if (idx < 0) {
1049 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1050 return;
1051 }
1052
1053 option_descriptor_t *desc = &builder->descriptors[idx];
1054 desc->metadata.numeric_range.min = min;
1055 desc->metadata.numeric_range.max = max;
1056 desc->metadata.numeric_range.step = step;
1057}
1058
1059void options_builder_set_examples(options_builder_t *builder, const char *option_name, const char **examples) {
1060 if (!builder || !option_name || !examples) {
1061 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or arguments are NULL");
1062 return;
1063 }
1064
1065 int idx = find_descriptor_in_builder(builder, option_name);
1066 if (idx < 0) {
1067 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1068 return;
1069 }
1070
1071 option_descriptor_t *desc = &builder->descriptors[idx];
1072 desc->metadata.examples = examples;
1073 // Note: examples array must be null-terminated
1074}
1075
1076void options_builder_set_input_type(options_builder_t *builder, const char *option_name,
1077 option_input_type_t input_type) {
1078 if (!builder || !option_name) {
1079 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1080 return;
1081 }
1082
1083 int idx = find_descriptor_in_builder(builder, option_name);
1084 if (idx < 0) {
1085 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1086 return;
1087 }
1088
1089 option_descriptor_t *desc = &builder->descriptors[idx];
1090 desc->metadata.input_type = input_type;
1091}
1092
1093void options_builder_mark_as_list(options_builder_t *builder, const char *option_name) {
1094 if (!builder || !option_name) {
1095 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1096 return;
1097 }
1098
1099 int idx = find_descriptor_in_builder(builder, option_name);
1100 if (idx < 0) {
1101 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1102 return;
1103 }
1104
1105 option_descriptor_t *desc = &builder->descriptors[idx];
1106 desc->metadata.is_list = true;
1107}
1108
1110 const char *default_value) {
1111 if (!builder || !option_name) {
1112 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1113 return;
1114 }
1115
1116 int idx = find_descriptor_in_builder(builder, option_name);
1117 if (idx < 0) {
1118 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1119 return;
1120 }
1121
1122 option_descriptor_t *desc = &builder->descriptors[idx];
1123 desc->metadata.default_value = default_value;
1124}
1125
1126// ============================================================================
1127// Managing Dependencies
1128// ============================================================================
1129
1130void options_builder_add_dependency_requires(options_builder_t *builder, const char *option_name,
1131 const char *depends_on, const char *error_message) {
1133
1134 option_dependency_t dep = {.option_name = option_name,
1135 .type = DEPENDENCY_REQUIRES,
1136 .depends_on = depends_on,
1137 .error_message = error_message};
1138
1139 builder->dependencies[builder->num_dependencies++] = dep;
1140}
1141
1143 const char *conflicts_with, const char *error_message) {
1145
1146 option_dependency_t dep = {.option_name = option_name,
1147 .type = DEPENDENCY_CONFLICTS,
1148 .depends_on = conflicts_with,
1149 .error_message = error_message};
1150
1151 builder->dependencies[builder->num_dependencies++] = dep;
1152}
1153
1154void options_builder_add_dependency_implies(options_builder_t *builder, const char *option_name, const char *implies,
1155 const char *error_message) {
1157
1158 option_dependency_t dep = {
1159 .option_name = option_name, .type = DEPENDENCY_IMPLIES, .depends_on = implies, .error_message = error_message};
1160
1161 builder->dependencies[builder->num_dependencies++] = dep;
1162}
1163
1165 if (!builder || !dependency)
1166 return;
1167
1169 builder->dependencies[builder->num_dependencies++] = *dependency;
1170}
1171
1172void options_builder_mark_binary_only(options_builder_t *builder, const char *option_name) {
1173 if (!builder || !option_name)
1174 return;
1175
1176 // Find the option by name and mark it
1177 for (size_t i = 0; i < builder->num_descriptors; i++) {
1178 if (strcmp(builder->descriptors[i].long_name, option_name) == 0) {
1179 builder->descriptors[i].hide_from_mode_help = true;
1180 return;
1181 }
1182 }
1183
1184 // Option not found - this is a programming error
1185 log_warn("Attempted to mark non-existent option '%s' as binary-only", option_name);
1186}
1187
1188// ============================================================================
1189// Positional Arguments
1190// ============================================================================
1191
1192void options_builder_add_positional(options_builder_t *builder, const char *name, const char *help_text, bool required,
1193 const char *section_heading, const char **examples, size_t num_examples,
1194 option_mode_bitmask_t mode_bitmask,
1195 int (*parse_fn)(const char *arg, void *config, char **remaining, int num_remaining,
1196 char **error_msg)) {
1197 if (!builder || !name || !parse_fn)
1198 return;
1199
1201
1202 positional_arg_descriptor_t pos_arg = {.name = name,
1203 .help_text = help_text,
1204 .required = required,
1205 .section_heading = section_heading,
1206 .examples = examples,
1207 .num_examples = num_examples,
1208 .mode_bitmask = mode_bitmask,
1209 .parse_fn = parse_fn};
1210
1211 builder->positional_args[builder->num_positional_args++] = pos_arg;
1212}
1213
1214// ============================================================================
1215// Programmatic Help Generation
1216// ============================================================================
1217
1221static void track_owned_string_builder(options_builder_t *builder, char *str) {
1222 if (!str)
1223 return;
1224
1226 size_t new_capacity = builder->owned_strings_builder_capacity * 2;
1227 if (new_capacity == 0)
1228 new_capacity = INITIAL_OWNED_STRINGS_CAPACITY;
1229
1230 char **new_owned = SAFE_REALLOC(builder->owned_strings_builder, new_capacity * sizeof(char *), char **);
1231 if (!new_owned) {
1232 log_fatal("Failed to reallocate builder owned_strings array");
1233 return;
1234 }
1235 builder->owned_strings_builder = new_owned;
1236 builder->owned_strings_builder_capacity = new_capacity;
1237 }
1238
1239 builder->owned_strings_builder[builder->num_owned_strings_builder++] = str;
1240}
1241
1242void options_builder_add_usage(options_builder_t *builder, const char *mode, const char *positional, bool show_options,
1243 const char *description) {
1244 if (!builder || !description)
1245 return;
1246
1247 ensure_usage_line_capacity(builder);
1248
1250 .mode = mode, .positional = positional, .show_options = show_options, .description = description};
1251
1252 builder->usage_lines[builder->num_usage_lines++] = usage;
1253}
1254
1255void options_builder_add_example(options_builder_t *builder, uint32_t mode_bitmask, const char *args,
1256 const char *description, bool owns_args) {
1257 if (!builder || !description)
1258 return;
1259
1260 ensure_example_capacity(builder);
1261
1262 example_descriptor_t example = {.mode_bitmask = mode_bitmask,
1263 .description = description,
1264 .owns_args_memory = owns_args,
1265 .is_utility_command = false};
1266
1267 if (owns_args) {
1268 // If owning, duplicate the string and track it
1269 char *owned_args = platform_strdup(args);
1270 if (!owned_args) {
1271 log_fatal("Failed to duplicate example args string");
1272 return;
1273 }
1274 example.args = owned_args;
1275 track_owned_string_builder(builder, owned_args);
1276 } else {
1277 example.args = args;
1278 }
1279
1280 builder->examples[builder->num_examples++] = example;
1281}
1282
1290 const char *description, bool is_utility_command) {
1291 if (!builder || !description)
1292 return;
1293
1294 ensure_example_capacity(builder);
1295
1296 example_descriptor_t example = {.mode_bitmask = mode_bitmask,
1297 .description = description,
1298 .owns_args_memory = false,
1299 .is_utility_command = is_utility_command};
1300
1301 if (args) {
1302 example.args = args;
1303 }
1304
1305 builder->examples[builder->num_examples++] = example;
1306}
1307
1308void options_builder_add_mode(options_builder_t *builder, const char *name, const char *description) {
1309 if (!builder || !name || !description)
1310 return;
1311
1312 ensure_mode_capacity(builder);
1313
1314 help_mode_descriptor_t mode = {.name = name, .description = description};
1315
1316 builder->modes[builder->num_modes++] = mode;
1317}
1318
1319void options_builder_add_custom_section(options_builder_t *builder, const char *heading, const char *content,
1320 option_mode_bitmask_t mode_bitmask) {
1321 if (!builder || !heading || !content)
1322 return;
1323
1324 ensure_custom_section_capacity(builder);
1325
1326 custom_section_descriptor_t section = {.heading = heading, .content = content, .mode_bitmask = mode_bitmask};
1327
1328 builder->custom_sections[builder->num_custom_sections++] = section;
1329}
1330
1332 char **remaining_argv, void *options_struct) {
1333 if (!config || !options_struct) {
1334 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1335 }
1336
1337 if (remaining_argc < 0 || (remaining_argc > 0 && !remaining_argv)) {
1338 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid remaining args");
1339 }
1340
1341 // No positional args defined - but we got some positional arguments
1342 if (config->num_positional_args == 0 && remaining_argc > 0) {
1343 log_error("Error: Unexpected positional argument '%s'", remaining_argv[0]);
1344 return ERROR_USAGE;
1345 }
1346
1347 // Check if required positional args are missing
1348 for (size_t i = 0; i < config->num_positional_args; i++) {
1349 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1350 if (pos_arg->required && remaining_argc == 0) {
1351 log_error("Error: Missing required positional argument '%s'", pos_arg->name);
1352 if (pos_arg->help_text) {
1353 log_error(" %s", pos_arg->help_text);
1354 }
1355 return ERROR_USAGE;
1356 }
1357 }
1358
1359 // Parse positional arguments
1360 int arg_index = 0;
1361 for (size_t i = 0; i < config->num_positional_args && arg_index < remaining_argc; i++) {
1362 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1363
1364 const char *arg = remaining_argv[arg_index];
1365 char **remaining = (arg_index + 1 < remaining_argc) ? &remaining_argv[arg_index + 1] : NULL;
1366 int num_remaining = remaining_argc - arg_index - 1;
1367 char *error_msg = NULL;
1368
1369 // Call custom parser
1370 int consumed = pos_arg->parse_fn(arg, options_struct, remaining, num_remaining, &error_msg);
1371
1372 if (consumed < 0) {
1373 // Parse error
1374 if (error_msg) {
1375 log_error("Error parsing positional argument '%s': %s", pos_arg->name, error_msg);
1376 free(error_msg);
1377 } else {
1378 log_error("Error parsing positional argument '%s': %s", pos_arg->name, arg);
1379 }
1380 return ERROR_USAGE;
1381 }
1382
1383 // Advance by consumed args (usually 1, but can be more for multi-arg parsers)
1384 arg_index += consumed;
1385 }
1386
1387 // Check for extra unconsumed positional arguments
1388 if (arg_index < remaining_argc) {
1389 log_error("Error: Unexpected positional argument '%s'", remaining_argv[arg_index]);
1390 return ERROR_USAGE;
1391 }
1392
1393 return ASCIICHAT_OK;
1394}
1395
1396// ============================================================================
1397// Parsing and Validation
1398// ============================================================================
1399
1401 if (!config || !options_struct) {
1402 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1403 }
1404
1405 char *base = (char *)options_struct;
1406
1407 for (size_t i = 0; i < config->num_descriptors; i++) {
1408 const option_descriptor_t *desc = &config->descriptors[i];
1409 void *field = base + desc->offset;
1410
1411 // Check environment variable first (primary)
1412 const char *env_value = NULL;
1413 if (desc->env_var_name) {
1414 env_value = SAFE_GETENV(desc->env_var_name);
1415 }
1416 // Check shorter alias names as fallback if primary not set
1417 if (!env_value) {
1418 if (desc->env_var_name && strcmp(desc->env_var_name, "ASCII_CHAT_PORT") == 0) {
1419 env_value = SAFE_GETENV("PORT");
1420 } else if (desc->env_var_name && strcmp(desc->env_var_name, "ASCII_CHAT_WEBSOCKET_PORT") == 0) {
1421 env_value = SAFE_GETENV("WS_PORT");
1422 }
1423 }
1424
1425 // Use handler registry to apply environment variables and defaults
1426 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
1427 if (g_builder_handlers[desc->type].apply_env) {
1428 g_builder_handlers[desc->type].apply_env(field, env_value, desc);
1429 }
1430 } else if (desc->type == OPTION_TYPE_CALLBACK && desc->parse_fn && desc->default_value) {
1431 // Special handling for callbacks with parse_fn
1432 char *error_msg = NULL;
1433 desc->parse_fn(NULL, field, &error_msg);
1434 if (error_msg) {
1435 free(error_msg);
1436 }
1437 }
1438 }
1439
1440 return ASCIICHAT_OK;
1441}
1442
1443// ============================================================================
1444// Unified Argument Parser (Supports Mixed Positional and Flag Arguments)
1445// ============================================================================
1446
1452static bool is_mode_keyword(const char *arg) {
1453 if (!arg || arg[0] == '\0')
1454 return false;
1455
1456 // Known mode keywords that should not be consumed as values
1457 static const char *mode_keywords[] = {"server", "client", "mirror", "acds", "discovery", "discovery-service", NULL};
1458
1459 for (int i = 0; mode_keywords[i] != NULL; i++) {
1460 if (strcmp(arg, mode_keywords[i]) == 0) {
1461 return true;
1462 }
1463 }
1464 return false;
1465}
1466
1476static bool is_flag_argument(const char *arg) {
1477 if (!arg || arg[0] == '\0')
1478 return false;
1479 // Allow "-" as a value (Unix convention for stdin/stdout), but treat other "-" args as flags
1480 if (arg[0] == '-' && arg[1] == '\0')
1481 return false; // Just "-" is a value, not a flag
1482 if (arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9')
1483 return false; // Negative numeric values are values, not flags
1484 return arg[0] == '-' || is_mode_keyword(arg);
1485}
1486
1490const option_descriptor_t *find_option_descriptor(const options_config_t *config, const char *opt_name) {
1491 if (!opt_name || opt_name[0] == '\0')
1492 return NULL;
1493
1494 // Handle short options: -x (single dash, single char)
1495 if (opt_name[0] == '-' && opt_name[1] != '-' && opt_name[1] != '\0') {
1496 // This is a short option like -x
1497 char short_char = opt_name[1];
1498 for (size_t i = 0; i < config->num_descriptors; i++) {
1499 if (config->descriptors[i].short_name == short_char) {
1500 return &config->descriptors[i];
1501 }
1502 }
1503 // Short option not found
1504 return NULL;
1505 }
1506
1507 // Handle long options: --name (double dash)
1508 if (opt_name[0] == '-' && opt_name[1] == '-' && opt_name[2] != '\0') {
1509 const char *long_name = opt_name + 2; // Skip the '--'
1510 for (size_t i = 0; i < config->num_descriptors; i++) {
1511 if (strcmp(config->descriptors[i].long_name, long_name) == 0) {
1512 return &config->descriptors[i];
1513 }
1514 }
1515 // Long option not found
1516 return NULL;
1517 }
1518
1519 // Not a recognized option format
1520 return NULL;
1521}
1522
1538static asciichat_error_t parse_single_flag_with_mode(const options_config_t *config, char **argv, int argv_index,
1539 int argc, void *options_struct, option_mode_bitmask_t mode_bitmask,
1540 int *consumed_count) {
1541 *consumed_count = 1;
1542 // Validate argv_index is within bounds to prevent SIGBUS
1543 if (argv_index < 0 || argv_index >= argc) {
1544 return SET_ERRNO(ERROR_INVALID_PARAM, "argv_index %d out of bounds [0, %d)", argv_index, argc);
1545 }
1546
1547 const char *arg = argv[argv_index];
1548
1549 // Skip --grep (already handled in main.c before mode-specific parsing)
1550 if (strcmp(arg, "--grep") == 0) {
1551 // --grep requires a value, skip both the flag and its argument
1552 if (argv_index + 1 < argc) {
1553 *consumed_count = 2;
1554 }
1555 return ASCIICHAT_OK;
1556 }
1557 char *long_opt_value = NULL;
1558 char *equals = NULL;
1559 char *arg_copy = NULL; // Copy of arg for safe parsing without modifying original
1560 const char *arg_for_lookup = arg; // What to use for option lookup
1561
1562 // Handle long options with `=` (e.g., --port=8080)
1563 if (strncmp(arg, "--", 2) == 0) {
1564 equals = strchr(arg, '=');
1565 if (equals) {
1566 long_opt_value = equals + 1;
1567 // Make a copy of the arg string and null-terminate it to get the option name
1568 // This avoids modifying the original argv string, which is important for
1569 // thread safety and to prevent issues with forked child processes
1570 size_t arg_len = equals - arg;
1571 arg_copy = SAFE_MALLOC(arg_len + 1, char *);
1572 if (arg_copy) {
1573 memcpy(arg_copy, arg, arg_len);
1574 arg_copy[arg_len] = '\0';
1575 arg_for_lookup = arg_copy;
1576 } else {
1577 return ERROR_MEMORY;
1578 }
1579 }
1580 }
1581
1582 // Find matching descriptor
1583 const option_descriptor_t *desc = find_option_descriptor(config, arg_for_lookup);
1584 if (!desc) {
1585 // Try to suggest a similar option
1586 const char *suggestion = find_similar_option_with_mode(arg_for_lookup, config, mode_bitmask);
1587 SAFE_FREE(arg_copy);
1588 if (suggestion) {
1589 return SET_ERRNO(ERROR_USAGE, "Unknown option: %s. %s", arg_for_lookup, suggestion);
1590 } else {
1591 return SET_ERRNO(ERROR_USAGE, "Unknown option: %s", arg_for_lookup);
1592 }
1593 }
1594
1595 // Check if option applies to current mode based on the passed mode_bitmask
1596 // Binary-level options (OPTION_MODE_BINARY) are always allowed regardless of detected mode
1597 // Only check mode restrictions for mode-specific options
1598 if (desc->mode_bitmask != 0 && desc->mode_bitmask != OPTION_MODE_BINARY &&
1599 !(desc->mode_bitmask & OPTION_MODE_BINARY)) {
1600 // Option has specific mode restrictions - check if it applies to current mode
1601 if (!(desc->mode_bitmask & mode_bitmask)) {
1602 // Option not available in current mode - show where it IS available
1603 const char *available_modes = format_available_modes(desc->mode_bitmask);
1604 SAFE_FREE(arg_copy);
1605 return SET_ERRNO(ERROR_USAGE, "Option %s is not available in this mode. Available in modes: %s", arg,
1606 available_modes);
1607 }
1608 }
1609
1610 void *field = (char *)options_struct + desc->offset;
1611 const char *opt_value = long_opt_value;
1612
1613 // Get option value if needed
1614 if (desc->type != OPTION_TYPE_BOOL && desc->type != OPTION_TYPE_ACTION) {
1615 if (long_opt_value) {
1616 // Value came from --name=value
1617 opt_value = long_opt_value;
1618 } else if (argv_index + 1 < argc && !is_flag_argument(argv[argv_index + 1])) {
1619 // Value from next argument
1620 opt_value = argv[argv_index + 1];
1621 *consumed_count = 2;
1622 } else {
1623 // No value provided
1624 // Check if this option allows optional arguments
1625 if (!desc->optional_arg) {
1626 if (equals)
1627 *equals = '='; // Restore
1628 return SET_ERRNO(ERROR_USAGE, "Option %s requires a value", arg);
1629 }
1630 // For optional arguments, pass NULL to the parser
1631 opt_value = NULL;
1632 }
1633 } else if (desc->type == OPTION_TYPE_BOOL && (desc->optional_arg || strcmp(desc->long_name, "splash-screen") == 0 ||
1634 strcmp(desc->long_name, "status-screen") == 0)) {
1635 // Boolean options with optional_arg support can accept value from --flag=value or --flag value
1636 if (long_opt_value) {
1637 // Value came from --name=value
1638 opt_value = long_opt_value;
1639 } else if (argv_index + 1 < argc && !is_flag_argument(argv[argv_index + 1])) {
1640 // Check if next argument is a valid boolean value
1641 const char *next_arg = argv[argv_index + 1];
1642 if (strcasecmp(next_arg, "true") == 0 || strcasecmp(next_arg, "yes") == 0 || strcasecmp(next_arg, "1") == 0 ||
1643 strcasecmp(next_arg, "on") == 0 || strcasecmp(next_arg, "false") == 0 || strcasecmp(next_arg, "no") == 0 ||
1644 strcasecmp(next_arg, "0") == 0 || strcasecmp(next_arg, "off") == 0) {
1645 // Next argument is a boolean value, consume it
1646 opt_value = next_arg;
1647 *consumed_count = 2;
1648 } else {
1649 // Next argument is not a boolean value, treat it as positional
1650 opt_value = NULL;
1651 }
1652 } else {
1653 // No value provided, pass NULL to toggle
1654 opt_value = NULL;
1655 }
1656 }
1657
1658 // Handle option based on type using the handler registry
1659 if (desc->type < NUM_OPTION_TYPES) {
1660 const option_builder_handler_t *handler = &g_builder_handlers[desc->type];
1661
1662 if (desc->type == OPTION_TYPE_BOOL) {
1663 // Boolean flags: pass value if provided (--flag=true/false), otherwise NULL to toggle
1664 asciichat_error_t result = handler->apply_cli(field, opt_value, desc);
1665 if (result != ASCIICHAT_OK) {
1666 SAFE_FREE(arg_copy);
1667 return result;
1668 }
1669 } else if (desc->type == OPTION_TYPE_ACTION) {
1670 // Action flags don't take values
1671 asciichat_error_t result = handler->apply_cli(field, NULL, desc);
1672 if (result != ASCIICHAT_OK) {
1673 SAFE_FREE(arg_copy);
1674 return result;
1675 }
1676 } else {
1677 // Other types need a value (INT, STRING, DOUBLE, CALLBACK)
1678 asciichat_error_t result = handler->apply_cli(field, opt_value, desc);
1679 if (result != ASCIICHAT_OK) {
1680 SAFE_FREE(arg_copy);
1681 return result;
1682 }
1683 }
1684 } else {
1685 SAFE_FREE(arg_copy);
1686 return SET_ERRNO(ERROR_USAGE, "Invalid option type for %s", arg);
1687 }
1688
1689 SAFE_FREE(arg_copy);
1690 return ASCIICHAT_OK;
1691}
1692
1707static asciichat_error_t options_config_parse_unified(const options_config_t *config, int argc, char **argv,
1708 void *options_struct, option_mode_bitmask_t detected_mode) {
1709 if (!config || !options_struct) {
1710 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1711 }
1712
1713 // Separate positional and flag arguments while respecting order
1714 char **positional_args = SAFE_MALLOC(argc * sizeof(char *), char **);
1715 if (!positional_args) {
1716 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate positional args buffer");
1717 }
1718 int positional_count = 0;
1719 bool end_of_options = false;
1720
1721 // Parse arguments in order
1722 for (int i = 1; i < argc; i++) { // Skip argv[0] (program name)
1723 const char *arg = argv[i];
1724
1725 // Handle `--` as end-of-options marker
1726 if (!end_of_options && strcmp(arg, "--") == 0) {
1727 end_of_options = true;
1728 continue;
1729 }
1730
1731 // If we've seen `--` or arg doesn't look like a flag, treat as positional
1732 // EXCEPT: skip mode keywords since mode has already been detected
1733 if (end_of_options || (!is_flag_argument(arg) && !is_mode_keyword(arg))) {
1734 positional_args[positional_count++] = argv[i];
1735 continue;
1736 }
1737
1738 // Skip mode keywords - they're not options to parse
1739 if (is_mode_keyword(arg)) {
1740 continue;
1741 }
1742
1743 // Parse this flag with mode validation using the detected_mode bitmask
1744 int consumed = 0;
1745 asciichat_error_t err =
1746 parse_single_flag_with_mode(config, argv, i, argc, options_struct, detected_mode, &consumed);
1747 if (err != ASCIICHAT_OK) {
1748 SAFE_FREE(positional_args);
1749 return err;
1750 }
1751
1752 // Skip consumed arguments
1753 i += (consumed - 1);
1754 }
1755
1756 // Now parse positional arguments
1757 if (config->num_positional_args > 0) {
1758 // Parse positional arguments in order, filtering by mode_bitmask
1759 int arg_index = 0;
1760 for (size_t pos_idx = 0; pos_idx < config->num_positional_args && arg_index < positional_count; pos_idx++) {
1761 const positional_arg_descriptor_t *pos_arg = &config->positional_args[pos_idx];
1762
1763 // Skip positional args that don't apply to the detected mode
1764 if (pos_arg->mode_bitmask != 0 && !(pos_arg->mode_bitmask & detected_mode)) {
1765 continue;
1766 }
1767
1768 const char *arg = positional_args[arg_index];
1769 char **remaining = (arg_index + 1 < positional_count) ? &positional_args[arg_index + 1] : NULL;
1770 int num_remaining = positional_count - arg_index - 1;
1771 char *error_msg = NULL;
1772
1773 int consumed = pos_arg->parse_fn(arg, options_struct, remaining, num_remaining, &error_msg);
1774
1775 if (consumed < 0) {
1776 log_error("Error parsing positional argument '%s': %s", pos_arg->name, error_msg ? error_msg : arg);
1777 free(error_msg);
1778 SAFE_FREE(positional_args);
1779 return ERROR_USAGE;
1780 }
1781
1782 arg_index += consumed;
1783 }
1784
1785 // Check for extra unconsumed positional arguments
1786 if (arg_index < positional_count) {
1787 log_error("Error: Unexpected positional argument '%s'", positional_args[arg_index]);
1788 SAFE_FREE(positional_args);
1789 return ERROR_USAGE;
1790 }
1791
1792 // Check if required positional args are missing
1793 for (size_t i = 0; i < config->num_positional_args; i++) {
1794 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1795 if (pos_arg->required && i >= (size_t)arg_index) {
1796 log_error("Error: Missing required positional argument '%s'", pos_arg->name);
1797 if (pos_arg->help_text) {
1798 log_error(" %s", pos_arg->help_text);
1799 }
1800 SAFE_FREE(positional_args);
1801 return ERROR_USAGE;
1802 }
1803 }
1804 } else if (positional_count > 0) {
1805 // No positional args expected, but we got some
1806 log_error("Error: Unexpected positional argument '%s'", positional_args[0]);
1807 SAFE_FREE(positional_args);
1808 return ERROR_USAGE;
1809 }
1810
1811 SAFE_FREE(positional_args);
1812 return ASCIICHAT_OK;
1813}
1814
1815asciichat_error_t options_config_parse(const options_config_t *config, int argc, char **argv, void *options_struct,
1816 option_mode_bitmask_t detected_mode, int *remaining_argc,
1817 char ***remaining_argv) {
1818 if (!config || !options_struct) {
1819 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1820 }
1821
1822 // Use the new unified parser that handles mixed positional and flag arguments
1823 asciichat_error_t result = options_config_parse_unified(config, argc, argv, options_struct, detected_mode);
1824 if (result != ASCIICHAT_OK) {
1825 return result;
1826 }
1827
1828 // Since unified parser handles all arguments, there are no remaining args
1829 // (backward compatibility: set remaining_argc to 0)
1830 if (remaining_argc) {
1831 *remaining_argc = 0;
1832 }
1833 if (remaining_argv) {
1834 *remaining_argv = NULL;
1835 }
1836
1837 return ASCIICHAT_OK;
1838}
1839
1840asciichat_error_t options_config_validate(const options_config_t *config, const void *options_struct,
1841 char **error_message) {
1842 if (!config || !options_struct) {
1843 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1844 }
1845
1846 const char *base = (const char *)options_struct;
1847
1848 // Check required fields
1849 for (size_t i = 0; i < config->num_descriptors; i++) {
1850 const option_descriptor_t *desc = &config->descriptors[i];
1851 if (!desc->required)
1852 continue;
1853
1854 const void *field = base + desc->offset;
1855 // Use handler registry to check if option is set (for required field validation)
1856 bool is_set = true;
1857 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
1858 if (g_builder_handlers[desc->type].is_set) {
1859 is_set = g_builder_handlers[desc->type].is_set(field, desc);
1860 }
1861 }
1862
1863 if (!is_set) {
1864 if (error_message) {
1865 int asprintf_result;
1866 if (desc->env_var_name) {
1867 asprintf_result = asprintf(error_message, "Required option --%s is not set (set %s env var or use --%s)",
1868 desc->long_name, desc->env_var_name, desc->long_name);
1869 } else {
1870 asprintf_result = asprintf(error_message, "Required option --%s is not set", desc->long_name);
1871 }
1872 if (asprintf_result < 0) {
1873 log_error("Failed to format error message for missing required option --%s", desc->long_name);
1874 *error_message = NULL;
1875 }
1876 }
1877 return ERROR_USAGE;
1878 }
1879 }
1880
1881 // Check dependencies
1882 for (size_t i = 0; i < config->num_dependencies; i++) {
1883 const option_dependency_t *dep = &config->dependencies[i];
1884
1885 bool option_is_set = is_option_set(config, options_struct, dep->option_name);
1886 bool depends_is_set = is_option_set(config, options_struct, dep->depends_on);
1887
1888 switch (dep->type) {
1890 if (option_is_set && !depends_is_set) {
1891 if (error_message) {
1892 if (dep->error_message) {
1893 *error_message = platform_strdup(dep->error_message);
1894 } else {
1895 int asprintf_result =
1896 asprintf(error_message, "Option --%s requires --%s to be set", dep->option_name, dep->depends_on);
1897 if (asprintf_result < 0) {
1898 log_error("Failed to format error message for option dependency: --%s requires --%s", dep->option_name,
1899 dep->depends_on);
1900 *error_message = NULL;
1901 }
1902 }
1903 }
1904 return ERROR_USAGE;
1905 }
1906 break;
1907
1909 if (option_is_set && depends_is_set) {
1910 if (error_message) {
1911 if (dep->error_message) {
1912 *error_message = platform_strdup(dep->error_message);
1913 } else {
1914 int asprintf_result =
1915 asprintf(error_message, "Option --%s conflicts with --%s", dep->option_name, dep->depends_on);
1916 if (asprintf_result < 0) {
1917 log_error("Failed to format error message for option conflict: --%s conflicts with --%s",
1918 dep->option_name, dep->depends_on);
1919 *error_message = NULL;
1920 }
1921 }
1922 }
1923 return ERROR_USAGE;
1924 }
1925 break;
1926
1927 case DEPENDENCY_IMPLIES:
1928 // Implies is handled during parsing, not validation
1929 break;
1930 }
1931 }
1932
1933 // Run custom validators
1934 for (size_t i = 0; i < config->num_descriptors; i++) {
1935 const option_descriptor_t *desc = &config->descriptors[i];
1936 if (!desc->validate)
1937 continue;
1938
1939 char *custom_error = NULL;
1940 if (!desc->validate(options_struct, &custom_error)) {
1941 if (error_message) {
1942 *error_message = custom_error;
1943 } else {
1944 free(custom_error);
1945 }
1946 return ERROR_USAGE;
1947 }
1948 }
1949
1950 // Cross-field validation: Check for conflicting color options
1951 // Cannot use --color with --color-mode none
1952 const options_t *opts = (const options_t *)options_struct;
1953 if (opts->color && opts->color_mode == TERM_COLOR_NONE) {
1954 if (error_message) {
1955 int asprintf_result =
1956 asprintf(error_message, "Option --color cannot be used with --color-mode=none (conflicting color settings)");
1957 if (asprintf_result < 0) {
1958 log_error("Failed to format error message for color option conflict");
1959 *error_message = NULL;
1960 }
1961 }
1962 return ERROR_USAGE;
1963 }
1964
1965 return ASCIICHAT_OK;
1966}
1967
1968// Help generation functions are in builder/help.c
1969// Type handlers are in builder/handlers.c
Platform abstraction layer umbrella header providing unified cross-platform API.
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
const char * get_option_help_placeholder_str(const option_descriptor_t *desc)
Get help placeholder string for an option type.
Definition builder.c:43
void options_builder_add_dependency(options_builder_t *builder, const option_dependency_t *dependency)
Add full dependency (advanced)
Definition builder.c:1164
void options_builder_set_numeric_range(options_builder_t *builder, const char *option_name, int min, int max, int step)
Set numeric range for an option.
Definition builder.c:1040
void options_builder_mark_binary_only(options_builder_t *builder, const char *option_name)
Mark an option as binary-level only (hide from mode-specific help)
Definition builder.c:1172
void options_builder_add_usage(options_builder_t *builder, const char *mode, const char *positional, bool show_options, const char *description)
Add usage line descriptor.
Definition builder.c:1242
bool is_option_set(const options_config_t *config, const void *options_struct, const char *option_name)
Check if an option is set (has non-default value)
Definition builder.c:356
asciichat_error_t options_config_parse(const options_config_t *config, int argc, char **argv, void *options_struct, option_mode_bitmask_t detected_mode, int *remaining_argc, char ***remaining_argv)
Parse command-line arguments.
Definition builder.c:1815
void options_builder_set_arg_placeholder(options_builder_t *builder, const char *arg_placeholder)
Set custom argument placeholder on the last added option descriptor.
Definition builder.c:992
void ensure_positional_arg_capacity(options_builder_t *builder)
Grow positional arg array if needed.
Definition builder.c:247
void options_builder_add_example(options_builder_t *builder, uint32_t mode_bitmask, const char *args, const char *description, bool owns_args)
Add example descriptor.
Definition builder.c:1255
void options_builder_add_action(options_builder_t *builder, const char *long_name, char short_name, void(*action_fn)(void), const char *help_text, const char *group)
Add action option (executes action and may exit)
Definition builder.c:943
void options_builder_set_mode_bitmask(options_builder_t *builder, option_mode_bitmask_t mode_bitmask)
Set mode bitmask on the last added option descriptor.
Definition builder.c:984
void options_builder_add_callback_optional(options_builder_t *builder, const char *long_name, char short_name, size_t offset, const void *default_value, size_t value_size, bool(*parse_fn)(const char *, void *, char **), const char *help_text, const char *group, bool required, const char *env_var_name, bool optional_arg)
Add callback option with optional argument support.
Definition builder.c:890
void options_builder_add_bool(options_builder_t *builder, const char *long_name, char short_name, size_t offset, bool default_value, const char *help_text, const char *group, bool required, const char *env_var_name)
Add boolean flag option.
Definition builder.c:657
options_builder_t * options_builder_from_preset(const options_config_t *preset)
Create builder from preset config.
Definition builder.c:432
const option_descriptor_t * find_option(const options_config_t *config, const char *long_name)
Find option descriptor by long name.
Definition builder.c:344
void options_builder_add_dependency_requires(options_builder_t *builder, const char *option_name, const char *depends_on, const char *error_message)
Add dependency: if option_name is set, depends_on must be set.
Definition builder.c:1130
void options_builder_add_positional(options_builder_t *builder, const char *name, const char *help_text, bool required, const char *section_heading, const char **examples, size_t num_examples, option_mode_bitmask_t mode_bitmask, int(*parse_fn)(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg))
Add positional argument descriptor.
Definition builder.c:1192
void options_builder_add_dependency_conflicts(options_builder_t *builder, const char *option_name, const char *conflicts_with, const char *error_message)
Add anti-dependency: if option_name is set, conflicts_with must NOT be set.
Definition builder.c:1142
asciichat_error_t options_config_validate(const options_config_t *config, const void *options_struct, char **error_message)
Validate options struct.
Definition builder.c:1840
asciichat_error_t options_config_parse_positional(const options_config_t *config, int remaining_argc, char **remaining_argv, void *options_struct)
Parse positional arguments.
Definition builder.c:1331
void options_builder_add_mode(options_builder_t *builder, const char *name, const char *description)
Add mode descriptor.
Definition builder.c:1308
void options_builder_set_default_value_display(options_builder_t *builder, const char *option_name, const char *default_value)
Set default value string in metadata.
Definition builder.c:1109
void options_builder_add_custom_section(options_builder_t *builder, const char *heading, const char *content, option_mode_bitmask_t mode_bitmask)
Add a custom help section.
Definition builder.c:1319
asciichat_error_t options_config_set_defaults(const options_config_t *config, void *options_struct)
Set default values in options struct.
Definition builder.c:1400
#define asprintf
Definition builder.c:24
int format_option_default_value_str(const option_descriptor_t *desc, char *buf, size_t bufsize)
Format option default value as a string.
Definition builder.c:83
void options_builder_set_enum_values(options_builder_t *builder, const char *option_name, const char **values, const char **descriptions)
Set enum values with descriptions for an option.
Definition builder.c:1022
void options_builder_add_double_with_metadata(options_builder_t *builder, const char *long_name, char short_name, size_t offset, double default_value, const char *help_text, const char *group, bool required, const char *env_var_name, bool(*validate)(const void *, char **), const option_metadata_t *metadata, bool optional_arg)
Definition builder.c:813
void options_builder_add_double(options_builder_t *builder, const char *long_name, char short_name, size_t offset, double default_value, const char *help_text, const char *group, bool required, const char *env_var_name, bool(*validate)(const void *, char **), bool optional_arg)
Definition builder.c:779
void options_builder_add_callback(options_builder_t *builder, const char *long_name, char short_name, size_t offset, const void *default_value, size_t value_size, bool(*parse_fn)(const char *, void *, char **), const char *help_text, const char *group, bool required, const char *env_var_name)
Definition builder.c:849
void options_builder_add_dependency_implies(options_builder_t *builder, const char *option_name, const char *implies, const char *error_message)
Add implication: if option_name is set, implies defaults to true.
Definition builder.c:1154
void options_builder_add_descriptor(options_builder_t *builder, const option_descriptor_t *descriptor)
Add full option descriptor (advanced)
Definition builder.c:973
void options_builder_add_int_with_metadata(options_builder_t *builder, const char *long_name, char short_name, size_t offset, int default_value, const char *help_text, const char *group, bool required, const char *env_var_name, bool(*validate)(const void *options_struct, char **error_msg), const option_metadata_t *metadata)
Add integer option with metadata (for numeric ranges and examples)
Definition builder.c:710
void ensure_dependency_capacity(options_builder_t *builder)
Grow dependency array if needed.
Definition builder.c:230
void options_builder_add_int(options_builder_t *builder, const char *long_name, char short_name, size_t offset, int default_value, const char *help_text, const char *group, bool required, const char *env_var_name, bool(*validate)(const void *, char **))
Definition builder.c:681
const option_descriptor_t * find_option_descriptor(const options_config_t *config, const char *opt_name)
Find option descriptor by short or long name.
Definition builder.c:1490
void options_builder_mark_as_list(options_builder_t *builder, const char *option_name)
Mark option as accepting multiple values.
Definition builder.c:1093
void options_builder_set_examples(options_builder_t *builder, const char *option_name, const char **examples)
Set example values for an option.
Definition builder.c:1059
void options_builder_destroy(options_builder_t *builder)
Free options builder.
Definition builder.c:467
void options_builder_add_callback_with_metadata(options_builder_t *builder, const char *long_name, char short_name, size_t offset, const void *default_value, size_t value_size, bool(*parse_fn)(const char *, void *, char **), const char *help_text, const char *group, bool required, const char *env_var_name, bool optional_arg, const option_metadata_t *metadata)
Definition builder.c:916
void options_config_destroy(options_config_t *config)
Free options config.
Definition builder.c:631
void options_builder_add_string(options_builder_t *builder, const char *long_name, char short_name, size_t offset, const char *default_value, const char *help_text, const char *group, bool required, const char *env_var_name, bool(*validate)(const void *, char **))
Definition builder.c:742
bool option_applies_to_mode(const option_descriptor_t *desc, asciichat_mode_t mode, bool for_binary_help)
Check if an option applies to a specific mode using bitmask.
Definition builder.c:177
void ensure_descriptor_capacity(options_builder_t *builder)
Grow descriptor array if needed.
Definition builder.c:213
options_config_t * options_builder_build(options_builder_t *builder)
Build immutable options config.
Definition builder.c:482
void options_builder_set_input_type(options_builder_t *builder, const char *option_name, option_input_type_t input_type)
Set input type for an option.
Definition builder.c:1076
void options_builder_add_example_utility(options_builder_t *builder, uint32_t mode_bitmask, const char *args, const char *description, bool is_utility_command)
Add an example with utility command support.
Definition builder.c:1289
options_builder_t * options_builder_create(size_t struct_size)
Create empty options builder.
Definition builder.c:378
Options builder API for flexible command-line option configuration.
option_input_type_t
Completion input type for smart shell completions.
Definition builder.h:176
@ 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
@ DEPENDENCY_IMPLIES
If A is set, B defaults to true/enabled.
Definition builder.h:231
@ DEPENDENCY_CONFLICTS
If A is set, B must NOT be set.
Definition builder.h:230
@ DEPENDENCY_REQUIRES
If A is set, B must be set.
Definition builder.h:229
⚙️ Common definitions, error codes, macros, and types shared throughout the application
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_GETENV(name)
Definition common.h:434
#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
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_USAGE
Definition error_codes.h:53
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
asciichat_mode_t
Mode type for options parsing.
option_mode_bitmask_t
Option mode bitmask.
void usage(FILE *desc, asciichat_mode_t mode)
Print usage information for a specific mode.
@ MODE_DISCOVERY
Discovery mode - participant that can dynamically become host.
@ OPTION_MODE_BINARY
Binary-level options (parsed before mode detection)
@ OPTION_MODE_DISCOVERY
Discovery mode (bit 4)
@ OPTION_MODE_NONE
No modes (invalid)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
Two-column layout helpers for help text and option formatting.
Application limits and constraints.
📝 Logging API with multiple log levels and terminal output control
const option_builder_handler_t g_builder_handlers[]
Internal declarations for builder implementation.
#define INITIAL_DEPENDENCY_CAPACITY
#define INITIAL_DESCRIPTOR_CAPACITY
#define INITIAL_OWNED_STRINGS_CAPACITY
#define INITIAL_POSITIONAL_ARG_CAPACITY
#define NUM_OPTION_TYPES
int options_format_default_value(option_type_t type, const void *default_value, char *buf, size_t bufsize)
Format option default value to string.
const char * format_available_modes(option_mode_bitmask_t mode_bitmask)
Format all available modes for an option as comma-separated list.
const char * find_similar_option_with_mode(const char *unknown_opt, const options_config_t *config, option_mode_bitmask_t current_mode_bitmask)
Find similar option across all modes and suggest with mode information.
const char * options_get_type_placeholder(option_type_t type)
Get placeholder string for option type.
Common utilities and helpers for option parsing across all modes.
action_args_t args
bool is_set
Custom help section descriptor.
Definition builder.h:389
const char * heading
Section heading (e.g., "INTERACTIVE CONTROLS")
Definition builder.h:390
Example descriptor for programmatic EXAMPLES generation.
Definition builder.h:310
uint32_t mode_bitmask
Bitmask of modes (OPTION_MODE_SERVER, OPTION_MODE_CLIENT, etc.)
Definition builder.h:311
const char * args
Command-line arguments part of the example.
Definition builder.h:312
Mode descriptor for programmatic MODES generation (for help output)
Definition builder.h:321
const char * name
Mode name (e.g., "server", "client")
Definition builder.h:322
Type handler for builder operations.
asciichat_error_t(* apply_cli)(void *field, const char *opt_value, const option_descriptor_t *desc)
bool(* is_set)(const void *field, const option_descriptor_t *desc)
void(* apply_env)(void *field, const char *env_value, const option_descriptor_t *desc)
Option dependency.
Definition builder.h:331
const char * depends_on
The option it depends on.
Definition builder.h:334
const char * error_message
Custom error message (optional)
Definition builder.h:335
const char * option_name
The option that has the dependency.
Definition builder.h:332
dependency_type_t type
Type of dependency.
Definition builder.h:333
Option descriptor.
Definition builder.h:241
bool(* validate)(const void *options_struct, char **error_msg)
Definition builder.h:263
option_mode_bitmask_t mode_bitmask
Which modes this option applies to.
Definition builder.h:278
bool hide_from_mode_help
If true, don't show in mode-specific help (binary-level only)
Definition builder.h:254
size_t offset
offsetof(struct, field) - where to store value
Definition builder.h:248
bool hide_from_binary_help
If true, don't show in binary-level help (e.g., in release builds)
Definition builder.h:255
const char * env_var_name
Environment variable fallback (or NULL)
Definition builder.h:260
const char * arg_placeholder
Custom argument placeholder (e.g., "SHELL [FILE]" instead of "STR")
Definition builder.h:253
const void * default_value
Pointer to default value (or NULL if required)
Definition builder.h:258
bool required
If true, option must be provided.
Definition builder.h:259
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
char short_name
Short option char (e.g., 'p', or '\0' if none)
Definition builder.h:244
option_metadata_t metadata
Metadata for shell completions (enums, ranges, examples, etc.)
Definition builder.h:281
option_type_t type
Value type.
Definition builder.h:247
bool optional_arg
If true, argument is optional (for callbacks like –verbose)
Definition builder.h:275
Metadata for shell completion generation.
Definition builder.h:192
int max
Maximum value (or 0 if no limit)
Definition builder.h:203
bool is_list
If true, option accepts multiple comma-separated or space-separated values.
Definition builder.h:217
const char ** enum_descriptions
Descriptions parallel to enum_values (null-terminated, e.g., "Auto-detect from terminal")
Definition builder.h:197
int min
Minimum value (or 0 if no limit)
Definition builder.h:202
option_input_type_t input_type
What kind of input this option expects.
Definition builder.h:214
const char ** examples
Example values or command invocations (null-terminated array)
Definition builder.h:208
const char * default_value
Default value as string for display.
Definition builder.h:211
const int * enum_integer_values
Actual enum integer values (e.g., {-1, 0, 1, 2, 3} for non-sequential enums)
Definition builder.h:198
int step
Step size (0 = no step, continuous)
Definition builder.h:204
const char ** enum_values
Enum value strings (null-terminated, e.g., {"auto", "none", "16", "256", "truecolor",...
Definition builder.h:195
struct option_metadata_t::@19 numeric_range
Options builder.
Definition builder.h:441
size_t owned_strings_builder_capacity
Definition builder.h:477
positional_arg_descriptor_t * positional_args
Dynamic array of positional args.
Definition builder.h:450
size_t num_dependencies
Current count.
Definition builder.h:447
size_t descriptor_capacity
Allocated capacity.
Definition builder.h:444
const char * program_name
Definition builder.h:471
example_descriptor_t * examples
Dynamic array of examples.
Definition builder.h:458
size_t mode_capacity
Allocated capacity.
Definition builder.h:464
help_mode_descriptor_t * modes
Dynamic array of modes.
Definition builder.h:462
size_t example_capacity
Allocated capacity.
Definition builder.h:460
const char * description
Definition builder.h:472
option_descriptor_t * descriptors
Dynamic array of descriptors.
Definition builder.h:442
size_t num_descriptors
Current count.
Definition builder.h:443
size_t num_custom_sections
Current count.
Definition builder.h:467
size_t num_positional_args
Current count.
Definition builder.h:451
char ** owned_strings_builder
Definition builder.h:475
size_t num_owned_strings_builder
Definition builder.h:476
size_t positional_arg_capacity
Allocated capacity.
Definition builder.h:452
size_t num_examples
Current count.
Definition builder.h:459
size_t num_modes
Current count.
Definition builder.h:463
size_t dependency_capacity
Allocated capacity.
Definition builder.h:448
size_t num_usage_lines
Current count.
Definition builder.h:455
size_t custom_section_capacity
Allocated capacity.
Definition builder.h:468
custom_section_descriptor_t * custom_sections
Dynamic array of custom sections.
Definition builder.h:466
size_t usage_line_capacity
Allocated capacity.
Definition builder.h:456
option_dependency_t * dependencies
Dynamic array of dependencies.
Definition builder.h:446
size_t struct_size
Definition builder.h:470
usage_descriptor_t * usage_lines
Dynamic array of usage lines.
Definition builder.h:454
Options configuration.
Definition builder.h:401
positional_arg_descriptor_t * positional_args
Array of positional argument descriptors.
Definition builder.h:408
size_t num_custom_sections
Number of custom sections.
Definition builder.h:426
size_t num_usage_lines
Number of usage lines.
Definition builder.h:417
option_descriptor_t * descriptors
Array of option descriptors.
Definition builder.h:402
option_dependency_t * dependencies
Array of dependencies.
Definition builder.h:405
custom_section_descriptor_t * custom_sections
Array of custom help sections.
Definition builder.h:425
size_t num_modes
Number of modes.
Definition builder.h:423
const char * program_name
For usage header.
Definition builder.h:412
size_t num_positional_args
Number of positional arguments.
Definition builder.h:409
size_t num_descriptors
Number of descriptors.
Definition builder.h:403
char ** owned_strings
Strdup'd strings to free on cleanup.
Definition builder.h:429
size_t owned_strings_capacity
Allocated capacity.
Definition builder.h:431
size_t num_examples
Number of examples.
Definition builder.h:420
size_t num_dependencies
Number of dependencies.
Definition builder.h:406
const char * description
For usage header.
Definition builder.h:413
help_mode_descriptor_t * modes
Array of mode descriptors.
Definition builder.h:422
usage_descriptor_t * usage_lines
Array of usage line descriptors.
Definition builder.h:416
example_descriptor_t * examples
Array of example descriptors.
Definition builder.h:419
size_t struct_size
sizeof(options_t) for bounds checking
Definition builder.h:411
size_t num_owned_strings
Number of owned strings.
Definition builder.h:430
Consolidated options structure.
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor)
int color
Color setting (COLOR_SETTING_AUTO/TRUE/FALSE)
Positional argument descriptor.
Definition builder.h:357
const char * section_heading
Section heading for examples (e.g., "ADDRESS FORMATS")
Definition builder.h:363
bool required
If true, this positional arg must be provided.
Definition builder.h:360
const char * help_text
Description for usage message.
Definition builder.h:359
int(* parse_fn)(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg)
Definition builder.h:377
const char * name
Name for help text (e.g., "address", "bind-addr")
Definition builder.h:358
option_mode_bitmask_t mode_bitmask
Which modes this positional arg applies to.
Definition builder.h:380
const char ** examples
Array of example strings with descriptions.
Definition builder.h:364
size_t num_examples
Number of examples.
Definition builder.h:365
Usage line descriptor for programmatic USAGE generation.
Definition builder.h:296
const char * mode
NULL or mode name (e.g., "server") or "<mode>" placeholder.
Definition builder.h:297
🖥️ Cross-platform terminal interface for ascii-chat
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:582
🔤 UTF-8 Encoding and Decoding Utilities
🔤 String Manipulation and Shell Escaping Utilities