ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
builder.c
Go to the documentation of this file.
1
7#include <ascii-chat/options/builder/internal.h>
8#include <ascii-chat/options/builder.h>
9#include <ascii-chat/options/common.h>
10#include <ascii-chat/common.h>
11#include <ascii-chat/log/logging.h>
12#include <ascii-chat/options/layout.h>
13#include <ascii-chat/platform/abstraction.h>
14#include <ascii-chat/platform/terminal.h>
15#include <ascii-chat/asciichat_errno.h>
16#include <ascii-chat/util/utf8.h>
17#include <ascii-chat/util/string.h>
18#include <stdlib.h>
19#include <string.h>
20#include <limits.h>
21
22// Use platform utilities
23#include <ascii-chat/platform/util.h>
24#include <ascii-chat/platform/terminal.h>
25#define asprintf platform_asprintf
26
27// ============================================================================
28// ============================================================================
29// Help Formatting Helper Functions
30// ============================================================================
31
38const char *get_option_help_placeholder_str(const option_descriptor_t *desc) {
39 if (!desc) {
40 return "";
41 }
42 // Check for custom placeholder first
43 if (desc->arg_placeholder != NULL) {
44 return desc->arg_placeholder;
45 }
46 return options_get_type_placeholder(desc->type);
47}
48
56int format_option_default_value_str(const option_descriptor_t *desc, char *buf, size_t bufsize) {
57 if (!desc || !buf || bufsize == 0) {
58 return 0;
59 }
60
61 // For callback options with enums, look up the enum string by matching the default value
62 if (desc->type == OPTION_TYPE_CALLBACK && desc->metadata.enum_values && desc->default_value) {
63 int default_int_val = 0;
64 memcpy(&default_int_val, desc->default_value, sizeof(int));
65
66 // Try to find matching enum value
67 if (desc->metadata.enum_integer_values) {
68 for (size_t i = 0; desc->metadata.enum_values[i] != NULL; i++) {
69 if (desc->metadata.enum_integer_values[i] == default_int_val) {
70 return safe_snprintf(buf, bufsize, "%s", desc->metadata.enum_values[i]);
71 }
72 }
73 } else {
74 // Fallback: assume sequential 0-based indices if integer values not provided
75 // Count enum values to check bounds
76 size_t enum_count = 0;
77 while (desc->metadata.enum_values[enum_count] != NULL) {
78 enum_count++;
79 }
80 if (default_int_val >= 0 && (size_t)default_int_val < enum_count) {
81 return safe_snprintf(buf, bufsize, "%s", desc->metadata.enum_values[default_int_val]);
82 }
83 }
84 }
85
86 // For callback options storing numeric types (int/double/float), format them as numbers
87 if (desc->type == OPTION_TYPE_CALLBACK && desc->default_value && desc->metadata.enum_values == NULL) {
88 // Check if this is a numeric callback by looking for numeric_range metadata
89 // Numeric callbacks have min/max range constraints set (max != 0 indicates a range was set)
90 if (desc->metadata.numeric_range.max != 0 || desc->metadata.numeric_range.min != 0) {
91 // This is a numeric callback option - extract and format the value
92 // Determine if it's int or double based on range (int max is 2147483647)
93 double default_double = 0.0;
94 if (desc->metadata.numeric_range.max <= 2147483647.0 && desc->metadata.numeric_range.min >= -2147483648.0) {
95 // Integer-ranged value (like port 1-65535) - stored as int
96 int default_int = 0;
97 memcpy(&default_int, desc->default_value, sizeof(int));
98 default_double = (double)default_int;
99 } else {
100 // Double-ranged value - stored as double
101 memcpy(&default_double, desc->default_value, sizeof(double));
102 }
103
104 // Format with appropriate precision (remove trailing zeros for integers)
105 char formatted[32];
106 safe_snprintf(formatted, sizeof(formatted), "%.1f", default_double);
107
108 // Remove trailing zeros after decimal point
109 char *dot = strchr(formatted, '.');
110 if (dot) {
111 char *end = formatted + strlen(formatted) - 1;
112 while (end > dot && *end == '0') {
113 end--;
114 }
115 if (*end == '.') {
116 *end = '\0';
117 } else {
118 *(end + 1) = '\0';
119 }
120 }
121
122 return safe_snprintf(buf, bufsize, "%s", formatted);
123 }
124 }
125
126 // For callback options with string defaults (no enum, no numeric range)
127 // Treat default_value as a const char* string pointer
128 if (desc->type == OPTION_TYPE_CALLBACK && desc->default_value && desc->metadata.enum_values == NULL &&
129 desc->metadata.numeric_range.max == 0 && desc->metadata.numeric_range.min == 0) {
130 const char *str_default = (const char *)desc->default_value;
131 if (str_default && str_default[0] != '\0') {
132 return safe_snprintf(buf, bufsize, "%s", str_default);
133 }
134 }
135
136 return options_format_default_value(desc->type, desc->default_value, buf, bufsize);
137}
138
139// ============================================================================
140// Internal Helper Functions
141// ============================================================================
142
150bool option_applies_to_mode(const option_descriptor_t *desc, asciichat_mode_t mode, bool for_binary_help) {
151 if (!desc) {
152 SET_ERRNO(ERROR_INVALID_PARAM, "Descriptor is NULL");
153 return false;
154 }
155
156 // When for_binary_help is true (i.e., for 'ascii-chat --help'),
157 // show options that apply to the default mode (DISCOVERY) or binary-level options only.
158 // Don't show mode-specific options for other modes (server, client, mirror, discovery-service).
159 if (for_binary_help) {
160 // Binary-level options or options that apply to discovery mode (the default)
161 option_mode_bitmask_t default_modes = OPTION_MODE_BINARY | OPTION_MODE_DISCOVERY;
162 return (desc->mode_bitmask & default_modes) != 0 && !desc->hide_from_binary_help;
163 }
164
165 // For mode-specific help (non-discovery modes), show only options for that mode.
166 // Do not show binary options here unless it also specifically applies to the mode.
167 // The 'mode' parameter is the actual mode (e.g., MODE_CLIENT, MODE_SERVER).
168 if (mode < 0 || mode > MODE_DISCOVERY) { // Use MODE_INVALID as upper bound for valid modes
169 return false;
170 }
171 option_mode_bitmask_t mode_bit = (1 << mode);
172
173 // Check if it's a binary option. If so, only show if it also explicitly applies to this mode.
174 if ((desc->mode_bitmask & OPTION_MODE_BINARY) && !(desc->mode_bitmask & mode_bit)) {
175 return false; // Binary options not shown in mode-specific help unless also mode-specific
176 }
177
178 bool applies = (desc->mode_bitmask & mode_bit) != 0;
179
180 return applies && !desc->hide_from_mode_help;
181}
182
186void ensure_descriptor_capacity(options_builder_t *builder) {
187 if (builder->num_descriptors >= builder->descriptor_capacity) {
188 size_t new_capacity = builder->descriptor_capacity * 2;
189 option_descriptor_t *new_descriptors =
190 SAFE_REALLOC(builder->descriptors, new_capacity * sizeof(option_descriptor_t), option_descriptor_t *);
191 if (!new_descriptors) {
192 log_fatal("Failed to reallocate descriptors array");
193 return;
194 }
195 builder->descriptors = new_descriptors;
196 builder->descriptor_capacity = new_capacity;
197 }
198}
199
203void ensure_dependency_capacity(options_builder_t *builder) {
204 if (builder->num_dependencies >= builder->dependency_capacity) {
205 size_t new_capacity = builder->dependency_capacity * 2;
206 option_dependency_t *new_dependencies =
207 SAFE_REALLOC(builder->dependencies, new_capacity * sizeof(option_dependency_t), option_dependency_t *);
208 if (!new_dependencies) {
209 log_fatal("Failed to reallocate dependencies array");
210 return;
211 }
212 builder->dependencies = new_dependencies;
213 builder->dependency_capacity = new_capacity;
214 }
215}
216
220void ensure_positional_arg_capacity(options_builder_t *builder) {
221 if (builder->num_positional_args >= builder->positional_arg_capacity) {
222 size_t new_capacity = builder->positional_arg_capacity * 2;
223 if (new_capacity == 0)
224 new_capacity = INITIAL_POSITIONAL_ARG_CAPACITY;
225
226 positional_arg_descriptor_t *new_positional = SAFE_REALLOC(
227 builder->positional_args, new_capacity * sizeof(positional_arg_descriptor_t), positional_arg_descriptor_t *);
228 if (!new_positional) {
229 log_fatal("Failed to reallocate positional_args array");
230 return;
231 }
232 builder->positional_args = new_positional;
233 builder->positional_arg_capacity = new_capacity;
234 }
235}
236
240static void ensure_usage_line_capacity(options_builder_t *builder) {
241 if (builder->num_usage_lines >= builder->usage_line_capacity) {
242 size_t new_capacity = builder->usage_line_capacity * 2;
243 if (new_capacity == 0)
244 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
245
246 usage_descriptor_t *new_usage =
247 SAFE_REALLOC(builder->usage_lines, new_capacity * sizeof(usage_descriptor_t), usage_descriptor_t *);
248 if (!new_usage) {
249 log_fatal("Failed to reallocate usage_lines array");
250 return;
251 }
252 builder->usage_lines = new_usage;
253 builder->usage_line_capacity = new_capacity;
254 }
255}
256
260static void ensure_example_capacity(options_builder_t *builder) {
261 if (builder->num_examples >= builder->example_capacity) {
262 size_t new_capacity = builder->example_capacity * 2;
263 if (new_capacity == 0)
264 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
265
266 example_descriptor_t *new_examples =
267 SAFE_REALLOC(builder->examples, new_capacity * sizeof(example_descriptor_t), example_descriptor_t *);
268 if (!new_examples) {
269 log_fatal("Failed to reallocate examples array");
270 return;
271 }
272 builder->examples = new_examples;
273 builder->example_capacity = new_capacity;
274 }
275}
276
280static void ensure_mode_capacity(options_builder_t *builder) {
281 if (builder->num_modes >= builder->mode_capacity) {
282 size_t new_capacity = builder->mode_capacity * 2;
283 if (new_capacity == 0)
284 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
285
286 help_mode_descriptor_t *new_modes =
287 SAFE_REALLOC(builder->modes, new_capacity * sizeof(help_mode_descriptor_t), help_mode_descriptor_t *);
288 if (!new_modes) {
289 log_fatal("Failed to reallocate modes array");
290 return;
291 }
292 builder->modes = new_modes;
293 builder->mode_capacity = new_capacity;
294 }
295}
296
297static void ensure_custom_section_capacity(options_builder_t *builder) {
298 if (builder->num_custom_sections >= builder->custom_section_capacity) {
299 size_t new_capacity = builder->custom_section_capacity * 2;
300 if (new_capacity == 0)
301 new_capacity = INITIAL_DESCRIPTOR_CAPACITY;
302
303 custom_section_descriptor_t *new_sections = SAFE_REALLOC(
304 builder->custom_sections, new_capacity * sizeof(custom_section_descriptor_t), custom_section_descriptor_t *);
305 if (!new_sections) {
306 log_fatal("Failed to reallocate custom sections array");
307 return;
308 }
309 builder->custom_sections = new_sections;
310 builder->custom_section_capacity = new_capacity;
311 }
312}
313
317const option_descriptor_t *find_option(const options_config_t *config, const char *long_name) {
318 for (size_t i = 0; i < config->num_descriptors; i++) {
319 if (strcmp(config->descriptors[i].long_name, long_name) == 0) {
320 return &config->descriptors[i];
321 }
322 }
323 return NULL;
324}
325
329bool is_option_set(const options_config_t *config, const void *options_struct, const char *option_name) {
330 const option_descriptor_t *desc = find_option(config, option_name);
331 if (!desc)
332 return false;
333
334 const char *base = (const char *)options_struct;
335 const void *field = base + desc->offset;
336
337 // Use handler registry to check if option is set
338 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
339 if (g_builder_handlers[desc->type].is_set) {
340 return g_builder_handlers[desc->type].is_set(field, desc);
341 }
342 }
343
344 return false;
345}
346
347// ============================================================================
348// Builder Lifecycle
349// ============================================================================
350
351options_builder_t *options_builder_create(size_t struct_size) {
352 options_builder_t *builder = SAFE_MALLOC(sizeof(options_builder_t), options_builder_t *);
353 if (!builder) {
354 SET_ERRNO(ERROR_MEMORY, "Failed to allocate options builder");
355 return NULL;
356 }
357
358 builder->descriptors = SAFE_MALLOC(INITIAL_DESCRIPTOR_CAPACITY * sizeof(option_descriptor_t), option_descriptor_t *);
359 if (!builder->descriptors) {
360 free(builder);
361 SET_ERRNO(ERROR_MEMORY, "Failed to allocate descriptors array");
362 return NULL;
363 }
364
365 builder->dependencies = SAFE_MALLOC(INITIAL_DEPENDENCY_CAPACITY * sizeof(option_dependency_t), option_dependency_t *);
366 if (!builder->dependencies) {
367 SAFE_FREE(builder->descriptors);
368 SAFE_FREE(builder);
369 SET_ERRNO(ERROR_MEMORY, "Failed to allocate dependencies array");
370 return NULL;
371 }
372
373 builder->num_descriptors = 0;
374 builder->descriptor_capacity = INITIAL_DESCRIPTOR_CAPACITY;
375 builder->num_dependencies = 0;
376 builder->dependency_capacity = INITIAL_DEPENDENCY_CAPACITY;
377 builder->positional_args = NULL;
378 builder->num_positional_args = 0;
379 builder->positional_arg_capacity = 0;
380 builder->usage_lines = NULL;
381 builder->num_usage_lines = 0;
382 builder->usage_line_capacity = 0;
383 builder->examples = NULL;
384 builder->num_examples = 0;
385 builder->example_capacity = 0;
386 builder->modes = NULL;
387 builder->num_modes = 0;
388 builder->mode_capacity = 0;
389 builder->custom_sections = NULL;
390 builder->num_custom_sections = 0;
391 builder->custom_section_capacity = 0;
392 builder->struct_size = struct_size;
393 builder->program_name = NULL;
394 builder->description = NULL;
395 builder->owned_strings_builder = NULL;
396 builder->num_owned_strings_builder = 0;
397 builder->owned_strings_builder_capacity = 0;
398
399 return builder;
400}
401
402options_builder_t *options_builder_from_preset(const options_config_t *preset) {
403 if (!preset) {
404 SET_ERRNO(ERROR_INVALID_PARAM, "Preset config is NULL");
405 return NULL;
406 }
407
408 options_builder_t *builder = options_builder_create(preset->struct_size);
409 if (!builder) {
410 return NULL;
411 }
412
413 builder->program_name = preset->program_name;
414 builder->description = preset->description;
415
416 // Copy all descriptors
417 for (size_t i = 0; i < preset->num_descriptors; i++) {
418 options_builder_add_descriptor(builder, &preset->descriptors[i]);
419 }
420
421 // Copy all dependencies
422 for (size_t i = 0; i < preset->num_dependencies; i++) {
423 options_builder_add_dependency(builder, &preset->dependencies[i]);
424 }
425
426 // Copy all positional arguments
427 for (size_t i = 0; i < preset->num_positional_args; i++) {
428 const positional_arg_descriptor_t *pos_arg = &preset->positional_args[i];
429 options_builder_add_positional(builder, pos_arg->name, pos_arg->help_text, pos_arg->required,
430 pos_arg->section_heading, pos_arg->examples, pos_arg->num_examples,
431 pos_arg->mode_bitmask, pos_arg->parse_fn);
432 }
433
434 return builder;
435}
436
437void options_builder_destroy(options_builder_t *builder) {
438 if (!builder)
439 return;
440
441 SAFE_FREE(builder->descriptors);
442 SAFE_FREE(builder->dependencies);
443 SAFE_FREE(builder->positional_args);
444 SAFE_FREE(builder->usage_lines);
445 SAFE_FREE(builder->examples);
446 SAFE_FREE(builder->modes);
447 SAFE_FREE(builder->custom_sections);
448 SAFE_FREE(builder->owned_strings_builder); // Free the builder's owned strings
449 SAFE_FREE(builder);
450}
451
452options_config_t *options_builder_build(options_builder_t *builder) {
453 if (!builder) {
454 SET_ERRNO(ERROR_INVALID_PARAM, "Builder is NULL");
455 return NULL;
456 }
457
458 options_config_t *config = SAFE_MALLOC(sizeof(options_config_t), options_config_t *);
459 if (!config) {
460 SET_ERRNO(ERROR_MEMORY, "Failed to allocate options config");
461 return NULL;
462 }
463
464 // Allocate and copy descriptors
465 config->descriptors = SAFE_MALLOC(builder->num_descriptors * sizeof(option_descriptor_t), option_descriptor_t *);
466 if (!config->descriptors && builder->num_descriptors > 0) {
467 SAFE_FREE(config);
468 SET_ERRNO(ERROR_MEMORY, "Failed to allocate descriptors");
469 return NULL;
470 }
471 memcpy(config->descriptors, builder->descriptors, builder->num_descriptors * sizeof(option_descriptor_t));
472 config->num_descriptors = builder->num_descriptors;
473
474 // Allocate and copy dependencies
475 config->dependencies = SAFE_MALLOC(builder->num_dependencies * sizeof(option_dependency_t), option_dependency_t *);
476 if (!config->dependencies && builder->num_dependencies > 0) {
477 SAFE_FREE(config->descriptors);
478 SAFE_FREE(config);
479 SET_ERRNO(ERROR_MEMORY, "Failed to allocate dependencies");
480 return NULL;
481 }
482 memcpy(config->dependencies, builder->dependencies, builder->num_dependencies * sizeof(option_dependency_t));
483 config->num_dependencies = builder->num_dependencies;
484
485 // Allocate and copy positional args
486 if (builder->num_positional_args > 0) {
487 config->positional_args =
488 SAFE_MALLOC(builder->num_positional_args * sizeof(positional_arg_descriptor_t), positional_arg_descriptor_t *);
489 if (!config->positional_args) {
490 SAFE_FREE(config->descriptors);
491 SAFE_FREE(config->dependencies);
492 SAFE_FREE(config);
493 SET_ERRNO(ERROR_MEMORY, "Failed to allocate positional args");
494 return NULL;
495 }
496 memcpy(config->positional_args, builder->positional_args,
497 builder->num_positional_args * sizeof(positional_arg_descriptor_t));
498 config->num_positional_args = builder->num_positional_args;
499 } else {
500 config->positional_args = NULL;
501 config->num_positional_args = 0;
502 }
503
504 // Allocate and copy usage lines
505 if (builder->num_usage_lines > 0) {
506 config->usage_lines = SAFE_MALLOC(builder->num_usage_lines * sizeof(usage_descriptor_t), usage_descriptor_t *);
507 if (!config->usage_lines) {
508 SAFE_FREE(config->descriptors);
509 SAFE_FREE(config->dependencies);
510 SAFE_FREE(config->positional_args);
511 SAFE_FREE(config);
512 SET_ERRNO(ERROR_MEMORY, "Failed to allocate usage lines");
513 return NULL;
514 }
515 memcpy(config->usage_lines, builder->usage_lines, builder->num_usage_lines * sizeof(usage_descriptor_t));
516 config->num_usage_lines = builder->num_usage_lines;
517 } else {
518 config->usage_lines = NULL;
519 config->num_usage_lines = 0;
520 }
521
522 // Allocate and copy examples
523 if (builder->num_examples > 0) {
524 config->examples = SAFE_MALLOC(builder->num_examples * sizeof(example_descriptor_t), example_descriptor_t *);
525 if (!config->examples) {
526 SAFE_FREE(config->descriptors);
527 SAFE_FREE(config->dependencies);
528 SAFE_FREE(config->positional_args);
529 SAFE_FREE(config->usage_lines);
530 SAFE_FREE(config);
531 SET_ERRNO(ERROR_MEMORY, "Failed to allocate examples");
532 return NULL;
533 }
534 memcpy(config->examples, builder->examples, builder->num_examples * sizeof(example_descriptor_t));
535 config->num_examples = builder->num_examples;
536 } else {
537 config->examples = NULL;
538 config->num_examples = 0;
539 }
540
541 // Allocate and copy modes
542 if (builder->num_modes > 0) {
543 config->modes = SAFE_MALLOC(builder->num_modes * sizeof(help_mode_descriptor_t), help_mode_descriptor_t *);
544 if (!config->modes) {
545 SAFE_FREE(config->descriptors);
546 SAFE_FREE(config->dependencies);
547 SAFE_FREE(config->positional_args);
548 SAFE_FREE(config->usage_lines);
549 SAFE_FREE(config->examples);
550 SAFE_FREE(config);
551 SET_ERRNO(ERROR_MEMORY, "Failed to allocate modes");
552 return NULL;
553 }
554 memcpy(config->modes, builder->modes, builder->num_modes * sizeof(help_mode_descriptor_t));
555 config->num_modes = builder->num_modes;
556 } else {
557 config->modes = NULL;
558 config->num_modes = 0;
559 }
560
561 // Allocate and copy custom sections
562 if (builder->num_custom_sections > 0) {
563 config->custom_sections =
564 SAFE_MALLOC(builder->num_custom_sections * sizeof(custom_section_descriptor_t), custom_section_descriptor_t *);
565 if (!config->custom_sections) {
566 SAFE_FREE(config->descriptors);
567 SAFE_FREE(config->dependencies);
568 SAFE_FREE(config->positional_args);
569 SAFE_FREE(config->usage_lines);
570 SAFE_FREE(config->examples);
571 SAFE_FREE(config->modes);
572 SAFE_FREE(config);
573 SET_ERRNO(ERROR_MEMORY, "Failed to allocate custom sections");
574 return NULL;
575 }
576 memcpy(config->custom_sections, builder->custom_sections,
577 builder->num_custom_sections * sizeof(custom_section_descriptor_t));
578 config->num_custom_sections = builder->num_custom_sections;
579 } else {
580 config->custom_sections = NULL;
581 config->num_custom_sections = 0;
582 }
583
584 config->struct_size = builder->struct_size;
585 config->program_name = builder->program_name;
586 config->description = builder->description;
587
588 // Initialize memory management
589 config->owned_strings = builder->owned_strings_builder;
590 config->num_owned_strings = builder->num_owned_strings_builder;
591 config->owned_strings_capacity = builder->owned_strings_builder_capacity;
592
593 // Clear builder's ownership so it doesn't free them
594 builder->owned_strings_builder = NULL;
595 builder->num_owned_strings_builder = 0;
596 builder->owned_strings_builder_capacity = 0;
597
598 return config;
599}
600
601void options_config_destroy(options_config_t *config) {
602 if (!config)
603 return;
604
605 SAFE_FREE(config->descriptors);
606 SAFE_FREE(config->dependencies);
607 SAFE_FREE(config->positional_args);
608 SAFE_FREE(config->usage_lines);
609 SAFE_FREE(config->examples);
610 SAFE_FREE(config->modes);
611 SAFE_FREE(config->custom_sections);
612
613 // Free all owned strings before freeing the array
614 // Use SAFE_FREE because platform_strdup uses SAFE_MALLOC (mimalloc on Windows)
615 for (size_t i = 0; i < config->num_owned_strings; i++) {
616 SAFE_FREE(config->owned_strings[i]);
617 }
618 SAFE_FREE(config->owned_strings);
619
620 SAFE_FREE(config);
621}
622
623// ============================================================================
624// Adding Options
625// ============================================================================
626
627void options_builder_add_bool(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
628 bool default_value, const char *help_text, const char *group, bool required,
629 const char *env_var_name) {
631
632 static bool default_vals[2] = {false, true};
633
634 option_descriptor_t desc = {.long_name = long_name,
635 .short_name = short_name,
636 .type = OPTION_TYPE_BOOL,
637 .offset = offset,
638 .help_text = help_text,
639 .group = group,
640 .default_value = &default_vals[default_value ? 1 : 0],
641 .required = required,
642 .env_var_name = env_var_name,
643 .mode_bitmask = OPTION_MODE_NONE,
644 .validate = NULL,
645 .parse_fn = NULL,
646 .owns_memory = false};
647
648 builder->descriptors[builder->num_descriptors++] = desc;
649}
650
651void options_builder_add_int(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
652 int default_value, const char *help_text, const char *group, bool required,
653 const char *env_var_name, bool (*validate)(const void *, char **)) {
655
656 // Store default value statically
657 static int defaults[256]; // Assume we won't have more than 256 int options
658 static size_t num_defaults = 0;
659
660 if (num_defaults >= 256) {
661 log_error("Too many int options (max 256)");
662 return;
663 }
664
665 defaults[num_defaults] = default_value;
666
667 option_descriptor_t desc = {.long_name = long_name,
668 .short_name = short_name,
669 .type = OPTION_TYPE_INT,
670 .offset = offset,
671 .help_text = help_text,
672 .group = group,
673 .default_value = &defaults[num_defaults++],
674 .required = required,
675 .env_var_name = env_var_name,
676 .validate = validate,
677 .parse_fn = NULL,
678 .owns_memory = false,
679 .mode_bitmask = OPTION_MODE_NONE};
680
681 builder->descriptors[builder->num_descriptors++] = desc;
682}
683
684void options_builder_add_int_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
685 size_t offset, int default_value, const char *help_text, const char *group,
686 bool required, const char *env_var_name,
687 bool (*validate)(const void *options_struct, char **error_msg),
688 const option_metadata_t *metadata) {
690
691 // Store default value statically
692 static int defaults[256]; // Assume we won't have more than 256 int options
693 static size_t num_defaults = 0;
694
695 if (num_defaults >= 256) {
696 log_error("Too many int options (max 256)");
697 return;
698 }
699
700 defaults[num_defaults] = default_value;
701
702 option_descriptor_t desc = {.long_name = long_name,
703 .short_name = short_name,
704 .type = OPTION_TYPE_INT,
705 .offset = offset,
706 .help_text = help_text,
707 .group = group,
708 .default_value = &defaults[num_defaults++],
709 .required = required,
710 .env_var_name = env_var_name,
711 .validate = validate,
712 .parse_fn = NULL,
713 .owns_memory = false,
714 .mode_bitmask = OPTION_MODE_NONE,
715 .metadata = metadata ? *metadata : (option_metadata_t){0}};
716
717 builder->descriptors[builder->num_descriptors++] = desc;
718}
719
720void options_builder_add_string(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
721 const char *default_value, const char *help_text, const char *group, bool required,
722 const char *env_var_name, bool (*validate)(const void *, char **)) {
724
725 // Store default string pointer in static storage to avoid stack-use-after-return
726 // Increased from 256 to 1024 to support multiple test builders without overflow
727 static const char *defaults[1024];
728 static size_t num_defaults = 0;
729
730 const void *default_ptr = NULL;
731 if (default_value) {
732 if (num_defaults >= 1024) {
733 log_error("Too many string options (max 1024)");
734 return;
735 }
736 defaults[num_defaults] = default_value;
737 default_ptr = &defaults[num_defaults++];
738 }
739
740 option_descriptor_t desc = {.long_name = long_name,
741 .short_name = short_name,
742 .type = OPTION_TYPE_STRING,
743 .offset = offset,
744 .help_text = help_text,
745 .group = group,
746 .default_value = default_ptr,
747 .required = required,
748 .env_var_name = env_var_name,
749 .validate = validate,
750 .parse_fn = NULL,
751 .owns_memory = true, // Strings are always owned
752 .mode_bitmask = OPTION_MODE_NONE};
753
754 builder->descriptors[builder->num_descriptors++] = desc;
755}
756
757void options_builder_add_double(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
758 double default_value, const char *help_text, const char *group, bool required,
759 const char *env_var_name, bool (*validate)(const void *, char **)) {
761
762 // Increased from 256 to 1024 to support multiple test builders without overflow
763 static double defaults[1024];
764 static size_t num_defaults = 0;
765
766 if (num_defaults >= 1024) {
767 log_error("Too many double options (max 1024)");
768 return;
769 }
770
771 defaults[num_defaults] = default_value;
772
773 option_descriptor_t desc = {.long_name = long_name,
774 .short_name = short_name,
775 .type = OPTION_TYPE_DOUBLE,
776 .offset = offset,
777 .help_text = help_text,
778 .group = group,
779 .default_value = &defaults[num_defaults++],
780 .required = required,
781 .env_var_name = env_var_name,
782 .validate = validate,
783 .parse_fn = NULL,
784 .owns_memory = false,
785 .mode_bitmask = OPTION_MODE_NONE};
786
787 builder->descriptors[builder->num_descriptors++] = desc;
788}
789
790void options_builder_add_double_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
791 size_t offset, double default_value, const char *help_text,
792 const char *group, bool required, const char *env_var_name,
793 bool (*validate)(const void *, char **),
794 const option_metadata_t *metadata) {
796
797 static double defaults[1024];
798 static size_t num_defaults = 0;
799
800 if (num_defaults >= 1024) {
801 log_error("Too many double options (max 1024)");
802 return;
803 }
804
805 defaults[num_defaults] = default_value;
806
807 option_descriptor_t desc = {.long_name = long_name,
808 .short_name = short_name,
809 .type = OPTION_TYPE_DOUBLE,
810 .offset = offset,
811 .help_text = help_text,
812 .group = group,
813 .default_value = &defaults[num_defaults++],
814 .required = required,
815 .env_var_name = env_var_name,
816 .validate = validate,
817 .parse_fn = NULL,
818 .owns_memory = false,
819 .mode_bitmask = OPTION_MODE_NONE,
820 .metadata = metadata ? *metadata : (option_metadata_t){0}};
821
822 builder->descriptors[builder->num_descriptors++] = desc;
823}
824
825void options_builder_add_callback(options_builder_t *builder, const char *long_name, char short_name, size_t offset,
826 const void *default_value, size_t value_size,
827 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
828 const char *group, bool required, const char *env_var_name) {
829 (void)value_size; // Unused parameter
831
832 option_descriptor_t desc = {.long_name = long_name,
833 .short_name = short_name,
834 .type = OPTION_TYPE_CALLBACK,
835 .offset = offset,
836 .help_text = help_text,
837 .group = group,
838 .default_value = default_value,
839 .required = required,
840 .env_var_name = env_var_name,
841 .validate = NULL,
842 .parse_fn = parse_fn,
843 .owns_memory = false,
844 .optional_arg = false,
845 .mode_bitmask = OPTION_MODE_NONE};
846
847 builder->descriptors[builder->num_descriptors++] = desc;
848}
849
866void options_builder_add_callback_optional(options_builder_t *builder, const char *long_name, char short_name,
867 size_t offset, const void *default_value, size_t value_size,
868 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
869 const char *group, bool required, const char *env_var_name,
870 bool optional_arg) {
871 (void)value_size; // Unused parameter
873
874 option_descriptor_t desc = {.long_name = long_name,
875 .short_name = short_name,
876 .type = OPTION_TYPE_CALLBACK,
877 .offset = offset,
878 .help_text = help_text,
879 .group = group,
880 .default_value = default_value,
881 .required = required,
882 .env_var_name = env_var_name,
883 .validate = NULL,
884 .parse_fn = parse_fn,
885 .owns_memory = false,
886 .optional_arg = optional_arg,
887 .mode_bitmask = OPTION_MODE_NONE};
888
889 builder->descriptors[builder->num_descriptors++] = desc;
890}
891
892void options_builder_add_callback_with_metadata(options_builder_t *builder, const char *long_name, char short_name,
893 size_t offset, const void *default_value, size_t value_size,
894 bool (*parse_fn)(const char *, void *, char **), const char *help_text,
895 const char *group, bool required, const char *env_var_name,
896 bool optional_arg, const option_metadata_t *metadata) {
897 (void)value_size; // Unused parameter
899
900 option_descriptor_t desc = {.long_name = long_name,
901 .short_name = short_name,
902 .type = OPTION_TYPE_CALLBACK,
903 .offset = offset,
904 .help_text = help_text,
905 .group = group,
906 .default_value = default_value,
907 .required = required,
908 .env_var_name = env_var_name,
909 .validate = NULL,
910 .parse_fn = parse_fn,
911 .owns_memory = false,
912 .optional_arg = optional_arg,
913 .mode_bitmask = OPTION_MODE_NONE,
914 .metadata = metadata ? *metadata : (option_metadata_t){0}};
915
916 builder->descriptors[builder->num_descriptors++] = desc;
917}
918
919void options_builder_add_action(options_builder_t *builder, const char *long_name, char short_name,
920 void (*action_fn)(void), const char *help_text, const char *group) {
922
923 option_descriptor_t desc = {.long_name = long_name,
924 .short_name = short_name,
925 .type = OPTION_TYPE_ACTION,
926 .offset = 0, // Actions don't store values
927 .help_text = help_text,
928 .group = group,
929 .default_value = NULL,
930 .required = false, // Actions are never required
931 .env_var_name = NULL,
932 .validate = NULL,
933 .parse_fn = NULL,
934 .action_fn = action_fn,
935 .owns_memory = false,
936 .hide_from_mode_help = false,
937 .hide_from_binary_help = false,
938 .mode_bitmask = OPTION_MODE_NONE};
939
940 builder->descriptors[builder->num_descriptors++] = desc;
941
942 // Set hide_from_binary_help after adding (so we can check the option name)
943 if (strcmp(long_name, "create-man-page") == 0) {
944 // Always hide from help and man page (this is a development tool)
945 builder->descriptors[builder->num_descriptors - 1].hide_from_binary_help = true;
946 }
947}
948
949void options_builder_add_descriptor(options_builder_t *builder, const option_descriptor_t *descriptor) {
950 if (!builder || !descriptor) {
951 SET_ERRNO(ERROR_INVALID_PARAM, "Builder is NULL or descriptor is NULL");
952 return;
953 }
954 return;
955
957 builder->descriptors[builder->num_descriptors++] = *descriptor;
958}
959
960void options_builder_set_mode_bitmask(options_builder_t *builder, option_mode_bitmask_t mode_bitmask) {
961 if (!builder || builder->num_descriptors == 0) {
962 SET_ERRNO(ERROR_INVALID_STATE, "Builder is NULL or has no descriptors");
963 return;
964 }
965 builder->descriptors[builder->num_descriptors - 1].mode_bitmask = mode_bitmask;
966}
967
968void options_builder_set_arg_placeholder(options_builder_t *builder, const char *arg_placeholder) {
969 if (!builder || builder->num_descriptors == 0) {
970 SET_ERRNO(ERROR_INVALID_STATE, "Builder is NULL or has no descriptors");
971 return;
972 }
973 builder->descriptors[builder->num_descriptors - 1].arg_placeholder = arg_placeholder;
974}
975
976// ============================================================================
977// Completion Metadata (Phase 2 Implementation)
978// ============================================================================
979
986static int find_descriptor_in_builder(const options_builder_t *builder, const char *option_name) {
987 if (!builder || !option_name) {
988 return -1;
989 }
990 for (size_t i = 0; i < builder->num_descriptors; i++) {
991 if (builder->descriptors[i].long_name && strcmp(builder->descriptors[i].long_name, option_name) == 0) {
992 return (int)i;
993 }
994 }
995 return -1;
996}
997
998void options_builder_set_enum_values(options_builder_t *builder, const char *option_name, const char **values,
999 const char **descriptions) {
1000 if (!builder || !option_name || !values || !descriptions) {
1001 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or arguments are NULL");
1002 return;
1003 }
1004
1005 int idx = find_descriptor_in_builder(builder, option_name);
1006 if (idx < 0) {
1007 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1008 return;
1009 }
1010
1011 option_descriptor_t *desc = &builder->descriptors[idx];
1012 desc->metadata.enum_values = values;
1013 desc->metadata.enum_descriptions = descriptions;
1014}
1015
1016void options_builder_set_numeric_range(options_builder_t *builder, const char *option_name, int min, int max,
1017 int step) {
1018 if (!builder || !option_name) {
1019 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1020 return;
1021 }
1022
1023 int idx = find_descriptor_in_builder(builder, option_name);
1024 if (idx < 0) {
1025 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1026 return;
1027 }
1028
1029 option_descriptor_t *desc = &builder->descriptors[idx];
1030 desc->metadata.numeric_range.min = min;
1031 desc->metadata.numeric_range.max = max;
1032 desc->metadata.numeric_range.step = step;
1033}
1034
1035void options_builder_set_examples(options_builder_t *builder, const char *option_name, const char **examples) {
1036 if (!builder || !option_name || !examples) {
1037 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or arguments are NULL");
1038 return;
1039 }
1040
1041 int idx = find_descriptor_in_builder(builder, option_name);
1042 if (idx < 0) {
1043 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1044 return;
1045 }
1046
1047 option_descriptor_t *desc = &builder->descriptors[idx];
1048 desc->metadata.examples = examples;
1049 // Note: examples array must be null-terminated
1050}
1051
1052void options_builder_set_input_type(options_builder_t *builder, const char *option_name,
1053 option_input_type_t input_type) {
1054 if (!builder || !option_name) {
1055 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1056 return;
1057 }
1058
1059 int idx = find_descriptor_in_builder(builder, option_name);
1060 if (idx < 0) {
1061 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1062 return;
1063 }
1064
1065 option_descriptor_t *desc = &builder->descriptors[idx];
1066 desc->metadata.input_type = input_type;
1067}
1068
1069void options_builder_mark_as_list(options_builder_t *builder, const char *option_name) {
1070 if (!builder || !option_name) {
1071 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1072 return;
1073 }
1074
1075 int idx = find_descriptor_in_builder(builder, option_name);
1076 if (idx < 0) {
1077 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1078 return;
1079 }
1080
1081 option_descriptor_t *desc = &builder->descriptors[idx];
1082 desc->metadata.is_list = true;
1083}
1084
1085void options_builder_set_default_value_display(options_builder_t *builder, const char *option_name,
1086 const char *default_value) {
1087 if (!builder || !option_name) {
1088 SET_ERRNO(ERROR_INVALID_PARAM, "Builder or option_name is NULL");
1089 return;
1090 }
1091
1092 int idx = find_descriptor_in_builder(builder, option_name);
1093 if (idx < 0) {
1094 SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' not found in builder", option_name);
1095 return;
1096 }
1097
1098 option_descriptor_t *desc = &builder->descriptors[idx];
1099 desc->metadata.default_value = default_value;
1100}
1101
1102// ============================================================================
1103// Managing Dependencies
1104// ============================================================================
1105
1106void options_builder_add_dependency_requires(options_builder_t *builder, const char *option_name,
1107 const char *depends_on, const char *error_message) {
1109
1110 option_dependency_t dep = {.option_name = option_name,
1111 .type = DEPENDENCY_REQUIRES,
1112 .depends_on = depends_on,
1113 .error_message = error_message};
1114
1115 builder->dependencies[builder->num_dependencies++] = dep;
1116}
1117
1118void options_builder_add_dependency_conflicts(options_builder_t *builder, const char *option_name,
1119 const char *conflicts_with, const char *error_message) {
1121
1122 option_dependency_t dep = {.option_name = option_name,
1123 .type = DEPENDENCY_CONFLICTS,
1124 .depends_on = conflicts_with,
1125 .error_message = error_message};
1126
1127 builder->dependencies[builder->num_dependencies++] = dep;
1128}
1129
1130void options_builder_add_dependency_implies(options_builder_t *builder, const char *option_name, const char *implies,
1131 const char *error_message) {
1133
1134 option_dependency_t dep = {
1135 .option_name = option_name, .type = DEPENDENCY_IMPLIES, .depends_on = implies, .error_message = error_message};
1136
1137 builder->dependencies[builder->num_dependencies++] = dep;
1138}
1139
1140void options_builder_add_dependency(options_builder_t *builder, const option_dependency_t *dependency) {
1141 if (!builder || !dependency)
1142 return;
1143
1145 builder->dependencies[builder->num_dependencies++] = *dependency;
1146}
1147
1148void options_builder_mark_binary_only(options_builder_t *builder, const char *option_name) {
1149 if (!builder || !option_name)
1150 return;
1151
1152 // Find the option by name and mark it
1153 for (size_t i = 0; i < builder->num_descriptors; i++) {
1154 if (strcmp(builder->descriptors[i].long_name, option_name) == 0) {
1155 builder->descriptors[i].hide_from_mode_help = true;
1156 return;
1157 }
1158 }
1159
1160 // Option not found - this is a programming error
1161 log_warn("Attempted to mark non-existent option '%s' as binary-only", option_name);
1162}
1163
1164// ============================================================================
1165// Positional Arguments
1166// ============================================================================
1167
1168void options_builder_add_positional(options_builder_t *builder, const char *name, const char *help_text, bool required,
1169 const char *section_heading, const char **examples, size_t num_examples,
1170 option_mode_bitmask_t mode_bitmask,
1171 int (*parse_fn)(const char *arg, void *config, char **remaining, int num_remaining,
1172 char **error_msg)) {
1173 if (!builder || !name || !parse_fn)
1174 return;
1175
1177
1178 positional_arg_descriptor_t pos_arg = {.name = name,
1179 .help_text = help_text,
1180 .required = required,
1181 .section_heading = section_heading,
1182 .examples = examples,
1183 .num_examples = num_examples,
1184 .mode_bitmask = mode_bitmask,
1185 .parse_fn = parse_fn};
1186
1187 builder->positional_args[builder->num_positional_args++] = pos_arg;
1188}
1189
1190// ============================================================================
1191// Programmatic Help Generation
1192// ============================================================================
1193
1197static void track_owned_string_builder(options_builder_t *builder, char *str) {
1198 if (!str)
1199 return;
1200
1201 if (builder->num_owned_strings_builder >= builder->owned_strings_builder_capacity) {
1202 size_t new_capacity = builder->owned_strings_builder_capacity * 2;
1203 if (new_capacity == 0)
1204 new_capacity = INITIAL_OWNED_STRINGS_CAPACITY;
1205
1206 char **new_owned = SAFE_REALLOC(builder->owned_strings_builder, new_capacity * sizeof(char *), char **);
1207 if (!new_owned) {
1208 log_fatal("Failed to reallocate builder owned_strings array");
1209 return;
1210 }
1211 builder->owned_strings_builder = new_owned;
1212 builder->owned_strings_builder_capacity = new_capacity;
1213 }
1214
1215 builder->owned_strings_builder[builder->num_owned_strings_builder++] = str;
1216}
1217
1218void options_builder_add_usage(options_builder_t *builder, const char *mode, const char *positional, bool show_options,
1219 const char *description) {
1220 if (!builder || !description)
1221 return;
1222
1223 ensure_usage_line_capacity(builder);
1224
1225 usage_descriptor_t usage = {
1226 .mode = mode, .positional = positional, .show_options = show_options, .description = description};
1227
1228 builder->usage_lines[builder->num_usage_lines++] = usage;
1229}
1230
1231void options_builder_add_example(options_builder_t *builder, uint32_t mode_bitmask, const char *args,
1232 const char *description, bool owns_args) {
1233 if (!builder || !description)
1234 return;
1235
1236 ensure_example_capacity(builder);
1237
1238 example_descriptor_t example = {.mode_bitmask = mode_bitmask,
1239 .description = description,
1240 .owns_args_memory = owns_args,
1241 .is_utility_command = false};
1242
1243 if (owns_args) {
1244 // If owning, duplicate the string and track it
1245 char *owned_args = platform_strdup(args);
1246 if (!owned_args) {
1247 log_fatal("Failed to duplicate example args string");
1248 return;
1249 }
1250 example.args = owned_args;
1251 track_owned_string_builder(builder, owned_args);
1252 } else {
1253 example.args = args;
1254 }
1255
1256 builder->examples[builder->num_examples++] = example;
1257}
1258
1265void options_builder_add_example_utility(options_builder_t *builder, uint32_t mode_bitmask, const char *args,
1266 const char *description, bool is_utility_command) {
1267 if (!builder || !description)
1268 return;
1269
1270 ensure_example_capacity(builder);
1271
1272 example_descriptor_t example = {.mode_bitmask = mode_bitmask,
1273 .description = description,
1274 .owns_args_memory = false,
1275 .is_utility_command = is_utility_command};
1276
1277 if (args) {
1278 example.args = args;
1279 }
1280
1281 builder->examples[builder->num_examples++] = example;
1282}
1283
1284void options_builder_add_mode(options_builder_t *builder, const char *name, const char *description) {
1285 if (!builder || !name || !description)
1286 return;
1287
1288 ensure_mode_capacity(builder);
1289
1290 help_mode_descriptor_t mode = {.name = name, .description = description};
1291
1292 builder->modes[builder->num_modes++] = mode;
1293}
1294
1295void options_builder_add_custom_section(options_builder_t *builder, const char *heading, const char *content,
1296 option_mode_bitmask_t mode_bitmask) {
1297 if (!builder || !heading || !content)
1298 return;
1299
1300 ensure_custom_section_capacity(builder);
1301
1302 custom_section_descriptor_t section = {.heading = heading, .content = content, .mode_bitmask = mode_bitmask};
1303
1304 builder->custom_sections[builder->num_custom_sections++] = section;
1305}
1306
1307asciichat_error_t options_config_parse_positional(const options_config_t *config, int remaining_argc,
1308 char **remaining_argv, void *options_struct) {
1309 if (!config || !options_struct) {
1310 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1311 }
1312
1313 if (remaining_argc < 0 || (remaining_argc > 0 && !remaining_argv)) {
1314 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid remaining args");
1315 }
1316
1317 // No positional args defined - but we got some positional arguments
1318 if (config->num_positional_args == 0 && remaining_argc > 0) {
1319 log_error("Error: Unexpected positional argument '%s'", remaining_argv[0]);
1320 return ERROR_USAGE;
1321 }
1322
1323 // Check if required positional args are missing
1324 for (size_t i = 0; i < config->num_positional_args; i++) {
1325 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1326 if (pos_arg->required && remaining_argc == 0) {
1327 log_error("Error: Missing required positional argument '%s'", pos_arg->name);
1328 if (pos_arg->help_text) {
1329 log_error(" %s", pos_arg->help_text);
1330 }
1331 return ERROR_USAGE;
1332 }
1333 }
1334
1335 // Parse positional arguments
1336 int arg_index = 0;
1337 for (size_t i = 0; i < config->num_positional_args && arg_index < remaining_argc; i++) {
1338 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1339
1340 const char *arg = remaining_argv[arg_index];
1341 char **remaining = (arg_index + 1 < remaining_argc) ? &remaining_argv[arg_index + 1] : NULL;
1342 int num_remaining = remaining_argc - arg_index - 1;
1343 char *error_msg = NULL;
1344
1345 // Call custom parser
1346 int consumed = pos_arg->parse_fn(arg, options_struct, remaining, num_remaining, &error_msg);
1347
1348 if (consumed < 0) {
1349 // Parse error
1350 if (error_msg) {
1351 log_error("Error parsing positional argument '%s': %s", pos_arg->name, error_msg);
1352 free(error_msg);
1353 } else {
1354 log_error("Error parsing positional argument '%s': %s", pos_arg->name, arg);
1355 }
1356 return ERROR_USAGE;
1357 }
1358
1359 // Advance by consumed args (usually 1, but can be more for multi-arg parsers)
1360 arg_index += consumed;
1361 }
1362
1363 // Check for extra unconsumed positional arguments
1364 if (arg_index < remaining_argc) {
1365 log_error("Error: Unexpected positional argument '%s'", remaining_argv[arg_index]);
1366 return ERROR_USAGE;
1367 }
1368
1369 return ASCIICHAT_OK;
1370}
1371
1372// ============================================================================
1373// Parsing and Validation
1374// ============================================================================
1375
1376asciichat_error_t options_config_set_defaults(const options_config_t *config, void *options_struct) {
1377 if (!config || !options_struct) {
1378 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1379 }
1380
1381 char *base = (char *)options_struct;
1382
1383 for (size_t i = 0; i < config->num_descriptors; i++) {
1384 const option_descriptor_t *desc = &config->descriptors[i];
1385 void *field = base + desc->offset;
1386
1387 // Check environment variable first
1388 const char *env_value = NULL;
1389 if (desc->env_var_name) {
1390 env_value = SAFE_GETENV(desc->env_var_name);
1391 }
1392
1393 // Use handler registry to apply environment variables and defaults
1394 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
1395 if (g_builder_handlers[desc->type].apply_env) {
1396 g_builder_handlers[desc->type].apply_env(field, env_value, desc);
1397 }
1398 } else if (desc->type == OPTION_TYPE_CALLBACK && desc->parse_fn && desc->default_value) {
1399 // Special handling for callbacks with parse_fn
1400 char *error_msg = NULL;
1401 desc->parse_fn(NULL, field, &error_msg);
1402 if (error_msg) {
1403 free(error_msg);
1404 }
1405 }
1406 }
1407
1408 return ASCIICHAT_OK;
1409}
1410
1411// ============================================================================
1412// Unified Argument Parser (Supports Mixed Positional and Flag Arguments)
1413// ============================================================================
1414
1420static bool is_mode_keyword(const char *arg) {
1421 if (!arg || arg[0] == '\0')
1422 return false;
1423
1424 // Known mode keywords that should not be consumed as values
1425 static const char *mode_keywords[] = {"server", "client", "mirror", "acds", "discovery", "discovery-service", NULL};
1426
1427 for (int i = 0; mode_keywords[i] != NULL; i++) {
1428 if (strcmp(arg, mode_keywords[i]) == 0) {
1429 return true;
1430 }
1431 }
1432 return false;
1433}
1434
1444static bool is_flag_argument(const char *arg) {
1445 if (!arg || arg[0] == '\0')
1446 return false;
1447 return arg[0] == '-' || is_mode_keyword(arg);
1448}
1449
1453const option_descriptor_t *find_option_descriptor(const options_config_t *config, const char *opt_name) {
1454 if (!opt_name || opt_name[0] == '\0')
1455 return NULL;
1456
1457 // Handle short options: -x (single dash, single char)
1458 if (opt_name[0] == '-' && opt_name[1] != '-' && opt_name[1] != '\0') {
1459 // This is a short option like -x
1460 char short_char = opt_name[1];
1461 for (size_t i = 0; i < config->num_descriptors; i++) {
1462 if (config->descriptors[i].short_name == short_char) {
1463 return &config->descriptors[i];
1464 }
1465 }
1466 // Short option not found
1467 return NULL;
1468 }
1469
1470 // Handle long options: --name (double dash)
1471 if (opt_name[0] == '-' && opt_name[1] == '-' && opt_name[2] != '\0') {
1472 const char *long_name = opt_name + 2; // Skip the '--'
1473 for (size_t i = 0; i < config->num_descriptors; i++) {
1474 if (strcmp(config->descriptors[i].long_name, long_name) == 0) {
1475 return &config->descriptors[i];
1476 }
1477 }
1478 // Long option not found
1479 return NULL;
1480 }
1481
1482 // Not a recognized option format
1483 return NULL;
1484}
1485
1501static asciichat_error_t parse_single_flag_with_mode(const options_config_t *config, char **argv, int argv_index,
1502 int argc, void *options_struct, option_mode_bitmask_t mode_bitmask,
1503 int *consumed_count) {
1504 *consumed_count = 1;
1505 // Validate argv_index is within bounds to prevent SIGBUS
1506 if (argv_index < 0 || argv_index >= argc) {
1507 return SET_ERRNO(ERROR_INVALID_PARAM, "argv_index %d out of bounds [0, %d)", argv_index, argc);
1508 }
1509
1510 const char *arg = argv[argv_index];
1511
1512 // Skip --grep (already handled in main.c before mode-specific parsing)
1513 if (strcmp(arg, "--grep") == 0) {
1514 // --grep requires a value, skip both the flag and its argument
1515 if (argv_index + 1 < argc) {
1516 *consumed_count = 2;
1517 }
1518 return ASCIICHAT_OK;
1519 }
1520 char *long_opt_value = NULL;
1521 char *equals = NULL;
1522 char *arg_copy = NULL; // Copy of arg for safe parsing without modifying original
1523 const char *arg_for_lookup = arg; // What to use for option lookup
1524
1525 // Handle long options with `=` (e.g., --port=8080)
1526 if (strncmp(arg, "--", 2) == 0) {
1527 equals = strchr(arg, '=');
1528 if (equals) {
1529 long_opt_value = equals + 1;
1530 // Make a copy of the arg string and null-terminate it to get the option name
1531 // This avoids modifying the original argv string, which is important for
1532 // thread safety and to prevent issues with forked child processes
1533 size_t arg_len = equals - arg;
1534 arg_copy = SAFE_MALLOC(arg_len + 1, char *);
1535 if (arg_copy) {
1536 memcpy(arg_copy, arg, arg_len);
1537 arg_copy[arg_len] = '\0';
1538 arg_for_lookup = arg_copy;
1539 } else {
1540 return ERROR_MEMORY;
1541 }
1542 }
1543 }
1544
1545 // Find matching descriptor
1546 const option_descriptor_t *desc = find_option_descriptor(config, arg_for_lookup);
1547 if (!desc) {
1548 // Try to suggest a similar option
1549 const char *suggestion = find_similar_option_with_mode(arg_for_lookup, config, mode_bitmask);
1550 if (suggestion) {
1551 log_plain_stderr("Unknown option: %s. %s", arg_for_lookup, suggestion);
1552 } else {
1553 log_plain_stderr("Unknown option: %s", arg_for_lookup);
1554 }
1555 SAFE_FREE(arg_copy);
1556 return ERROR_USAGE;
1557 }
1558
1559 // Check if option applies to current mode based on the passed mode_bitmask
1560 // Binary-level options (OPTION_MODE_BINARY) are always allowed regardless of detected mode
1561 // Only check mode restrictions for mode-specific options
1562 if (desc->mode_bitmask != 0 && desc->mode_bitmask != OPTION_MODE_BINARY &&
1563 !(desc->mode_bitmask & OPTION_MODE_BINARY)) {
1564 // Option has specific mode restrictions - check if it applies to current mode
1565 if (!(desc->mode_bitmask & mode_bitmask)) {
1566 // Option not available in current mode - show where it IS available
1567 const char *available_modes = format_available_modes(desc->mode_bitmask);
1568 SAFE_FREE(arg_copy);
1569 return SET_ERRNO(ERROR_USAGE, "Option %s is not available in this mode. Available in modes: %s", arg,
1570 available_modes);
1571 }
1572 }
1573
1574 void *field = (char *)options_struct + desc->offset;
1575 const char *opt_value = NULL;
1576
1577 // Get option value if needed
1578 if (desc->type != OPTION_TYPE_BOOL && desc->type != OPTION_TYPE_ACTION) {
1579 if (long_opt_value) {
1580 // Value came from --name=value
1581 opt_value = long_opt_value;
1582 } else if (argv_index + 1 < argc && !is_flag_argument(argv[argv_index + 1])) {
1583 // Value from next argument
1584 opt_value = argv[argv_index + 1];
1585 *consumed_count = 2;
1586 } else {
1587 // No value provided
1588 // Check if this option allows optional arguments
1589 if (!desc->optional_arg) {
1590 if (equals)
1591 *equals = '='; // Restore
1592 return SET_ERRNO(ERROR_USAGE, "Option %s requires a value", arg);
1593 }
1594 // For optional arguments, pass NULL to the parser
1595 opt_value = NULL;
1596 }
1597 }
1598
1599 // Handle option based on type using the handler registry
1600 if (desc->type < NUM_OPTION_TYPES) {
1601 const option_builder_handler_t *handler = &g_builder_handlers[desc->type];
1602
1603 if (desc->type == OPTION_TYPE_BOOL || desc->type == OPTION_TYPE_ACTION) {
1604 // Boolean/action flags don't need a value
1605 asciichat_error_t result = handler->apply_cli(field, NULL, desc);
1606 if (result != ASCIICHAT_OK) {
1607 if (equals)
1608 *equals = '='; // Restore
1609 return result;
1610 }
1611 } else {
1612 // Other types need a value (INT, STRING, DOUBLE, CALLBACK)
1613 asciichat_error_t result = handler->apply_cli(field, opt_value, desc);
1614 if (result != ASCIICHAT_OK) {
1615 SAFE_FREE(arg_copy);
1616 return result;
1617 }
1618 }
1619 } else {
1620 SAFE_FREE(arg_copy);
1621 return SET_ERRNO(ERROR_USAGE, "Invalid option type for %s", arg);
1622 }
1623
1624 SAFE_FREE(arg_copy);
1625 return ASCIICHAT_OK;
1626}
1627
1642static asciichat_error_t options_config_parse_unified(const options_config_t *config, int argc, char **argv,
1643 void *options_struct, option_mode_bitmask_t detected_mode) {
1644 if (!config || !options_struct) {
1645 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1646 }
1647
1648 // Separate positional and flag arguments while respecting order
1649 char **positional_args = SAFE_MALLOC(argc * sizeof(char *), char **);
1650 if (!positional_args) {
1651 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate positional args buffer");
1652 }
1653 int positional_count = 0;
1654 bool end_of_options = false;
1655
1656 // Parse arguments in order
1657 for (int i = 1; i < argc; i++) { // Skip argv[0] (program name)
1658 const char *arg = argv[i];
1659
1660 // Handle `--` as end-of-options marker
1661 if (!end_of_options && strcmp(arg, "--") == 0) {
1662 end_of_options = true;
1663 continue;
1664 }
1665
1666 // If we've seen `--` or arg doesn't look like a flag, treat as positional
1667 // EXCEPT: skip mode keywords since mode has already been detected
1668 if (end_of_options || (!is_flag_argument(arg) && !is_mode_keyword(arg))) {
1669 positional_args[positional_count++] = argv[i];
1670 continue;
1671 }
1672
1673 // Skip mode keywords - they're not options to parse
1674 if (is_mode_keyword(arg)) {
1675 continue;
1676 }
1677
1678 // Parse this flag with mode validation using the detected_mode bitmask
1679 int consumed = 0;
1680 asciichat_error_t err =
1681 parse_single_flag_with_mode(config, argv, i, argc, options_struct, detected_mode, &consumed);
1682 if (err != ASCIICHAT_OK) {
1683 SAFE_FREE(positional_args);
1684 return err;
1685 }
1686
1687 // Skip consumed arguments
1688 i += (consumed - 1);
1689 }
1690
1691 // Now parse positional arguments
1692 if (config->num_positional_args > 0) {
1693 // Parse positional arguments in order, filtering by mode_bitmask
1694 int arg_index = 0;
1695 for (size_t pos_idx = 0; pos_idx < config->num_positional_args && arg_index < positional_count; pos_idx++) {
1696 const positional_arg_descriptor_t *pos_arg = &config->positional_args[pos_idx];
1697
1698 // Skip positional args that don't apply to the detected mode
1699 if (pos_arg->mode_bitmask != 0 && !(pos_arg->mode_bitmask & detected_mode)) {
1700 continue;
1701 }
1702
1703 const char *arg = positional_args[arg_index];
1704 char **remaining = (arg_index + 1 < positional_count) ? &positional_args[arg_index + 1] : NULL;
1705 int num_remaining = positional_count - arg_index - 1;
1706 char *error_msg = NULL;
1707
1708 int consumed = pos_arg->parse_fn(arg, options_struct, remaining, num_remaining, &error_msg);
1709
1710 if (consumed < 0) {
1711 log_error("Error parsing positional argument '%s': %s", pos_arg->name, error_msg ? error_msg : arg);
1712 free(error_msg);
1713 SAFE_FREE(positional_args);
1714 return ERROR_USAGE;
1715 }
1716
1717 arg_index += consumed;
1718 }
1719
1720 // Check for extra unconsumed positional arguments
1721 if (arg_index < positional_count) {
1722 log_error("Error: Unexpected positional argument '%s'", positional_args[arg_index]);
1723 SAFE_FREE(positional_args);
1724 return ERROR_USAGE;
1725 }
1726
1727 // Check if required positional args are missing
1728 for (size_t i = 0; i < config->num_positional_args; i++) {
1729 const positional_arg_descriptor_t *pos_arg = &config->positional_args[i];
1730 if (pos_arg->required && i >= (size_t)arg_index) {
1731 log_error("Error: Missing required positional argument '%s'", pos_arg->name);
1732 if (pos_arg->help_text) {
1733 log_error(" %s", pos_arg->help_text);
1734 }
1735 SAFE_FREE(positional_args);
1736 return ERROR_USAGE;
1737 }
1738 }
1739 } else if (positional_count > 0) {
1740 // No positional args expected, but we got some
1741 log_error("Error: Unexpected positional argument '%s'", positional_args[0]);
1742 SAFE_FREE(positional_args);
1743 return ERROR_USAGE;
1744 }
1745
1746 SAFE_FREE(positional_args);
1747 return ASCIICHAT_OK;
1748}
1749
1750asciichat_error_t options_config_parse(const options_config_t *config, int argc, char **argv, void *options_struct,
1751 option_mode_bitmask_t detected_mode, int *remaining_argc,
1752 char ***remaining_argv) {
1753 if (!config || !options_struct) {
1754 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1755 }
1756
1757 // Use the new unified parser that handles mixed positional and flag arguments
1758 asciichat_error_t result = options_config_parse_unified(config, argc, argv, options_struct, detected_mode);
1759 if (result != ASCIICHAT_OK) {
1760 return result;
1761 }
1762
1763 // Since unified parser handles all arguments, there are no remaining args
1764 // (backward compatibility: set remaining_argc to 0)
1765 if (remaining_argc) {
1766 *remaining_argc = 0;
1767 }
1768 if (remaining_argv) {
1769 *remaining_argv = NULL;
1770 }
1771
1772 return ASCIICHAT_OK;
1773}
1774
1775asciichat_error_t options_config_validate(const options_config_t *config, const void *options_struct,
1776 char **error_message) {
1777 if (!config || !options_struct) {
1778 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options struct is NULL");
1779 }
1780
1781 const char *base = (const char *)options_struct;
1782
1783 // Check required fields
1784 for (size_t i = 0; i < config->num_descriptors; i++) {
1785 const option_descriptor_t *desc = &config->descriptors[i];
1786 if (!desc->required)
1787 continue;
1788
1789 const void *field = base + desc->offset;
1790 // Use handler registry to check if option is set (for required field validation)
1791 bool is_set = true;
1792 if (desc->type >= 0 && desc->type < (int)(NUM_OPTION_TYPES)) {
1793 if (g_builder_handlers[desc->type].is_set) {
1794 is_set = g_builder_handlers[desc->type].is_set(field, desc);
1795 }
1796 }
1797
1798 if (!is_set) {
1799 if (error_message) {
1800 if (desc->env_var_name) {
1801 asprintf(error_message, "Required option --%s is not set (set %s env var or use --%s)", desc->long_name,
1802 desc->env_var_name, desc->long_name);
1803 } else {
1804 asprintf(error_message, "Required option --%s is not set", desc->long_name);
1805 }
1806 }
1807 return ERROR_USAGE;
1808 }
1809 }
1810
1811 // Check dependencies
1812 for (size_t i = 0; i < config->num_dependencies; i++) {
1813 const option_dependency_t *dep = &config->dependencies[i];
1814
1815 bool option_is_set = is_option_set(config, options_struct, dep->option_name);
1816 bool depends_is_set = is_option_set(config, options_struct, dep->depends_on);
1817
1818 switch (dep->type) {
1819 case DEPENDENCY_REQUIRES:
1820 if (option_is_set && !depends_is_set) {
1821 if (error_message) {
1822 if (dep->error_message) {
1823 *error_message = platform_strdup(dep->error_message);
1824 } else {
1825 asprintf(error_message, "Option --%s requires --%s to be set", dep->option_name, dep->depends_on);
1826 }
1827 }
1828 return ERROR_USAGE;
1829 }
1830 break;
1831
1832 case DEPENDENCY_CONFLICTS:
1833 if (option_is_set && depends_is_set) {
1834 if (error_message) {
1835 if (dep->error_message) {
1836 *error_message = platform_strdup(dep->error_message);
1837 } else {
1838 asprintf(error_message, "Option --%s conflicts with --%s", dep->option_name, dep->depends_on);
1839 }
1840 }
1841 return ERROR_USAGE;
1842 }
1843 break;
1844
1845 case DEPENDENCY_IMPLIES:
1846 // Implies is handled during parsing, not validation
1847 break;
1848 }
1849 }
1850
1851 // Run custom validators
1852 for (size_t i = 0; i < config->num_descriptors; i++) {
1853 const option_descriptor_t *desc = &config->descriptors[i];
1854 if (!desc->validate)
1855 continue;
1856
1857 char *custom_error = NULL;
1858 if (!desc->validate(options_struct, &custom_error)) {
1859 if (error_message) {
1860 *error_message = custom_error;
1861 } else {
1862 free(custom_error);
1863 }
1864 return ERROR_USAGE;
1865 }
1866 }
1867
1868 // Cross-field validation: Check for conflicting color options
1869 // Cannot use --color with --color-mode none
1870 const options_t *opts = (const options_t *)options_struct;
1871 if (opts->color && opts->color_mode == TERM_COLOR_NONE) {
1872 if (error_message) {
1873 asprintf(error_message, "Option --color cannot be used with --color-mode=none (conflicting color settings)");
1874 }
1875 return ERROR_USAGE;
1876 }
1877
1878 return ASCIICHAT_OK;
1879}
1880
1881// Help generation functions are in builder/help.c
1882// Type handlers are in builder/handlers.c
const char * get_option_help_placeholder_str(const option_descriptor_t *desc)
Get help placeholder string for an option type.
Definition builder.c:38
void options_builder_add_dependency(options_builder_t *builder, const option_dependency_t *dependency)
Definition builder.c:1140
void options_builder_set_numeric_range(options_builder_t *builder, const char *option_name, int min, int max, int step)
Definition builder.c:1016
void options_builder_mark_binary_only(options_builder_t *builder, const char *option_name)
Definition builder.c:1148
void options_builder_add_usage(options_builder_t *builder, const char *mode, const char *positional, bool show_options, const char *description)
Definition builder.c:1218
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 **))
Definition builder.c:757
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:329
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)
Definition builder.c:1750
void options_builder_set_arg_placeholder(options_builder_t *builder, const char *arg_placeholder)
Definition builder.c:968
void ensure_positional_arg_capacity(options_builder_t *builder)
Grow positional arg array if needed.
Definition builder.c:220
void options_builder_add_example(options_builder_t *builder, uint32_t mode_bitmask, const char *args, const char *description, bool owns_args)
Definition builder.c:1231
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)
Definition builder.c:919
void options_builder_set_mode_bitmask(options_builder_t *builder, option_mode_bitmask_t mode_bitmask)
Definition builder.c:960
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:866
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)
Definition builder.c:627
options_builder_t * options_builder_from_preset(const options_config_t *preset)
Definition builder.c:402
const option_descriptor_t * find_option(const options_config_t *config, const char *long_name)
Find option descriptor by long name.
Definition builder.c:317
void options_builder_add_dependency_requires(options_builder_t *builder, const char *option_name, const char *depends_on, const char *error_message)
Definition builder.c:1106
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))
Definition builder.c:1168
void options_builder_add_dependency_conflicts(options_builder_t *builder, const char *option_name, const char *conflicts_with, const char *error_message)
Definition builder.c:1118
asciichat_error_t options_config_validate(const options_config_t *config, const void *options_struct, char **error_message)
Definition builder.c:1775
asciichat_error_t options_config_parse_positional(const options_config_t *config, int remaining_argc, char **remaining_argv, void *options_struct)
Definition builder.c:1307
void options_builder_add_mode(options_builder_t *builder, const char *name, const char *description)
Definition builder.c:1284
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)
Definition builder.c:790
void options_builder_set_default_value_display(options_builder_t *builder, const char *option_name, const char *default_value)
Definition builder.c:1085
void options_builder_add_custom_section(options_builder_t *builder, const char *heading, const char *content, option_mode_bitmask_t mode_bitmask)
Definition builder.c:1295
asciichat_error_t options_config_set_defaults(const options_config_t *config, void *options_struct)
Definition builder.c:1376
#define asprintf
Definition builder.c:25
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:56
void options_builder_set_enum_values(options_builder_t *builder, const char *option_name, const char **values, const char **descriptions)
Definition builder.c:998
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:825
void options_builder_add_dependency_implies(options_builder_t *builder, const char *option_name, const char *implies, const char *error_message)
Definition builder.c:1130
void options_builder_add_descriptor(options_builder_t *builder, const option_descriptor_t *descriptor)
Definition builder.c:949
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)
Definition builder.c:684
void ensure_dependency_capacity(options_builder_t *builder)
Grow dependency array if needed.
Definition builder.c:203
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:651
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:1453
void options_builder_mark_as_list(options_builder_t *builder, const char *option_name)
Definition builder.c:1069
void options_builder_set_examples(options_builder_t *builder, const char *option_name, const char **examples)
Definition builder.c:1035
void options_builder_destroy(options_builder_t *builder)
Definition builder.c:437
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:892
void options_config_destroy(options_config_t *config)
Definition builder.c:601
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:720
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:150
void ensure_descriptor_capacity(options_builder_t *builder)
Grow descriptor array if needed.
Definition builder.c:186
options_config_t * options_builder_build(options_builder_t *builder)
Definition builder.c:452
void options_builder_set_input_type(options_builder_t *builder, const char *option_name, option_input_type_t input_type)
Definition builder.c:1052
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:1265
options_builder_t * options_builder_create(size_t struct_size)
Definition builder.c:351
const option_builder_handler_t g_builder_handlers[]
int options_format_default_value(option_type_t type, const void *default_value, char *buf, size_t bufsize)
const char * format_available_modes(option_mode_bitmask_t mode_bitmask)
void usage(FILE *desc, asciichat_mode_t mode)
const char * find_similar_option_with_mode(const char *unknown_opt, const options_config_t *config, option_mode_bitmask_t current_mode_bitmask)
const char * options_get_type_placeholder(option_type_t type)
action_args_t args
char * platform_strdup(const char *s)
bool is_set
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456