13#include <ascii-chat/options/builder/internal.h>
14#include <ascii-chat/options/builder.h>
15#include <ascii-chat/options/common.h>
16#include <ascii-chat/options/layout.h>
17#include <ascii-chat/platform/terminal.h>
18#include <ascii-chat/util/string.h>
19#include <ascii-chat/util/utf8.h>
20#include <ascii-chat/log/logging.h>
42static const char *get_mode_name_from_bitmask(uint32_t mode_bitmask) {
44 if (mode_bitmask & OPTION_MODE_BINARY && !(mode_bitmask & 0x1F)) {
49 if (mode_bitmask == OPTION_MODE_DISCOVERY) {
54 if (mode_bitmask & OPTION_MODE_SERVER) {
57 if (mode_bitmask & OPTION_MODE_CLIENT) {
60 if (mode_bitmask & OPTION_MODE_MIRROR) {
63 if (mode_bitmask & OPTION_MODE_DISCOVERY_SVC) {
64 return "discovery-service";
80 const char *binary_name = PLATFORM_BINARY_NAME;
82 int max_col_width = 0;
83 char temp_buf[BUFFER_SIZE_MEDIUM];
86 for (
size_t i = 0; i < config->num_usage_lines; i++) {
87 const usage_descriptor_t *
usage = &config->usage_lines[i];
90 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
"%s", binary_name);
94 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", colored_mode);
97 if (
usage->positional) {
99 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", colored_pos);
102 if (
usage->show_options) {
103 const char *options_text =
104 (
usage->mode && strcmp(
usage->mode,
"<mode>") == 0) ?
"[mode-options...]" :
"[options...]";
105 const char *colored_opts =
colored_string(LOG_COLOR_WARN, options_text);
106 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", colored_opts);
110 if (w > LAYOUT_COLUMN_WIDTH) {
111 w = LAYOUT_COLUMN_WIDTH;
113 if (w > max_col_width)
118 for (
size_t i = 0; i < config->num_examples; i++) {
119 const example_descriptor_t *example = &config->examples[i];
123 if (!example->is_utility_command) {
124 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
"%s", binary_name);
127 const char *mode_name = get_mode_name_from_bitmask(example->mode_bitmask);
129 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", mode_name);
134 const char *colored_args =
colored_string(LOG_COLOR_INFO, example->args);
135 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", colored_args);
139 if (w > LAYOUT_COLUMN_WIDTH) {
140 w = LAYOUT_COLUMN_WIDTH;
142 if (w > max_col_width)
147 for (
size_t i = 0; i < config->num_modes; i++) {
148 const char *colored_name =
colored_string(LOG_COLOR_FATAL, config->modes[i].name);
150 if (w > LAYOUT_COLUMN_WIDTH) {
151 w = LAYOUT_COLUMN_WIDTH;
153 if (w > max_col_width)
158 for (
size_t i = 0; i < config->num_descriptors; i++) {
159 const option_descriptor_t *desc = &config->descriptors[i];
160 if (desc->hide_from_mode_help || desc->hide_from_binary_help || !desc->group)
164 char opts_buf[BUFFER_SIZE_SMALL];
165 if (desc->short_name && desc->short_name !=
'\0') {
167 safe_snprintf(short_flag,
sizeof(short_flag),
"-%c", desc->short_name);
168 char long_flag[BUFFER_SIZE_SMALL];
169 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
174 char long_flag[BUFFER_SIZE_SMALL];
175 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
178 const char *colored_opts = opts_buf;
181 if (w > LAYOUT_COLUMN_WIDTH) {
182 w = LAYOUT_COLUMN_WIDTH;
184 if (w > max_col_width)
189 if (max_col_width > 45) {
193 return max_col_width;
213static int calculate_section_max_col_width(
const options_config_t *config,
const char *section_type,
214 asciichat_mode_t mode,
bool for_binary_help) {
215 if (!config || !section_type) {
220 const char *binary_name = PLATFORM_BINARY_NAME;
221 char temp_buf[BUFFER_SIZE_MEDIUM];
223 if (strcmp(section_type,
"usage") == 0) {
225 if (config->num_usage_lines == 0)
229 const char *mode_name = NULL;
230 if (!for_binary_help) {
233 mode_name =
"server";
236 mode_name =
"client";
239 mode_name =
"mirror";
241 case MODE_DISCOVERY_SERVICE:
242 mode_name =
"discovery-service";
253 for (
size_t i = 0; i < config->num_usage_lines; i++) {
254 const usage_descriptor_t *
usage = &config->usage_lines[i];
257 if (!for_binary_help) {
260 if (!
usage->mode || strcmp(
usage->mode, mode_name) != 0) {
268 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
"%s", binary_name);
274 if (
usage->positional) {
275 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s",
usage->positional);
278 if (
usage->show_options) {
279 const char *options_text =
280 (
usage->mode && strcmp(
usage->mode,
"<mode>") == 0) ?
"[mode-options...]" :
"[options...]";
281 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", options_text);
292 }
else if (strcmp(section_type,
"examples") == 0) {
294 if (config->num_examples == 0)
297 for (
size_t i = 0; i < config->num_examples; i++) {
298 const example_descriptor_t *example = &config->examples[i];
301 if (for_binary_help) {
302 if (!(example->mode_bitmask & OPTION_MODE_BINARY))
305 uint32_t mode_bitmask = (1 << mode);
306 if (!(example->mode_bitmask & mode_bitmask))
311 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
"%s", binary_name);
314 if (!example->is_utility_command) {
315 const char *mode_name = get_mode_name_from_bitmask(example->mode_bitmask);
317 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", mode_name);
322 len +=
safe_snprintf(temp_buf + len,
sizeof(temp_buf) - len,
" %s", example->args);
332 }
else if (strcmp(section_type,
"modes") == 0) {
334 if (config->num_modes == 0)
337 for (
size_t i = 0; i < config->num_modes; i++) {
347 }
else if (strcmp(section_type,
"options") == 0) {
349 if (config->num_descriptors == 0)
352 char option_str[BUFFER_SIZE_MEDIUM];
354 for (
size_t i = 0; i < config->num_descriptors; i++) {
355 const option_descriptor_t *desc = &config->descriptors[i];
359 desc->hide_from_binary_help) {
365 if (desc->short_name) {
367 safe_snprintf(short_flag,
sizeof(short_flag),
"-%c", desc->short_name);
368 char long_flag[BUFFER_SIZE_SMALL];
369 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
371 safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s, %s",
374 char long_flag[BUFFER_SIZE_SMALL];
375 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
376 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s",
380 if (desc->type != OPTION_TYPE_BOOL && desc->type != OPTION_TYPE_ACTION) {
381 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
" ");
383 if (placeholder[0] !=
'\0') {
384 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s",
393 }
else if (strcmp(section_type,
"positional") == 0) {
395 if (config->num_positional_args == 0)
398 option_mode_bitmask_t current_mode_bitmask = 1U << mode;
400 for (
size_t pa_idx = 0; pa_idx < config->num_positional_args; pa_idx++) {
401 const positional_arg_descriptor_t *pos_arg = &config->positional_args[pa_idx];
404 if (pos_arg->mode_bitmask != 0 && !(pos_arg->mode_bitmask & current_mode_bitmask)) {
408 if (pos_arg->examples) {
409 for (
size_t i = 0; i < pos_arg->num_examples; i++) {
410 const char *example = pos_arg->examples[i];
411 const char *p = example;
416 const char *first_part = p;
419 while (*p && !(*p ==
' ' && *(p + 1) ==
' '))
421 int first_len_bytes = (int)(p - first_part);
435 return max_width > 20 ? max_width : 20;
446static void print_usage_section(
const options_config_t *config, FILE *stream,
int term_width,
int max_col_width) {
447 if (!config || !stream || config->num_usage_lines == 0) {
451 const char *binary_name = PLATFORM_BINARY_NAME;
453 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG,
"USAGE"));
456 for (
size_t i = 0; i < config->num_usage_lines; i++) {
457 const usage_descriptor_t *
usage = &config->usage_lines[i];
458 char usage_buf[BUFFER_SIZE_MEDIUM];
462 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
"%s", binary_name);
471 if (
usage->positional) {
472 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
" %s",
477 if (
usage->show_options) {
478 const char *options_text =
479 (
usage->mode && strcmp(
usage->mode,
"<mode>") == 0) ?
"[mode-options...]" :
"[options...]";
487 fprintf(stream,
"\n");
497static void print_examples_section(
const options_config_t *config, FILE *stream,
int term_width,
int max_col_width,
498 asciichat_mode_t mode,
bool for_binary_help) {
499 if (!config || !stream || config->num_examples == 0) {
503 const char *binary_name = PLATFORM_BINARY_NAME;
505 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG,
"EXAMPLES"));
508 for (
size_t i = 0; i < config->num_examples; i++) {
509 const example_descriptor_t *example = &config->examples[i];
512 if (for_binary_help) {
514 if (!(example->mode_bitmask & OPTION_MODE_BINARY)) {
520 uint32_t mode_bitmask = (1 << mode);
521 if (!(example->mode_bitmask & mode_bitmask)) {
526 char cmd_buf[BUFFER_SIZE_MEDIUM];
530 if (!example->is_utility_command) {
531 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s", binary_name);
536 uint32_t current_mode_bitmask = for_binary_help ? OPTION_MODE_BINARY : (1 << mode);
537 const char *mode_name = get_mode_name_from_bitmask(current_mode_bitmask);
547 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
" ");
552 if (example->is_utility_command) {
554 const char *p = example->args;
555 char current_token[BUFFER_SIZE_SMALL];
559 if (*p ==
' ' || *p ==
'|' || *p ==
'>' || *p ==
'<') {
562 current_token[token_len] =
'\0';
564 if (current_token[0] ==
'-') {
565 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
568 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
575 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s ",
580 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
" ");
587 current_token[token_len++] = *p;
594 current_token[token_len] =
'\0';
595 if (current_token[0] ==
'-') {
596 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
599 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
605 const char *p = example->args;
606 char current_token[BUFFER_SIZE_SMALL];
613 current_token[token_len] =
'\0';
615 if (current_token[0] ==
'-') {
616 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s ",
619 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s ",
629 current_token[token_len++] = *p;
636 current_token[token_len] =
'\0';
637 if (current_token[0] ==
'-') {
638 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
641 len +=
safe_snprintf(cmd_buf + len,
sizeof(cmd_buf) - len,
"%s",
648 if (len > 0 && cmd_buf[len - 1] ==
' ') {
657 fprintf(stream,
"\n");
663static void print_modes_section(
const options_config_t *config, FILE *stream,
int term_width,
int max_col_width) {
664 if (!config || !stream || config->num_modes == 0) {
668 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG,
"MODES"));
671 for (
size_t i = 0; i < config->num_modes; i++) {
672 char mode_buf[BUFFER_SIZE_SMALL];
677 fprintf(stream,
"\n");
683static void print_mode_options_section(FILE *stream,
int term_width,
int max_col_width) {
684 const char *binary_name = PLATFORM_BINARY_NAME;
687 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG,
"MODE-OPTIONS"));
694 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
"%s ", binary_name);
704 fprintf(stream,
"\n");
708 if (!config || !stream)
713 const char *cols_env = SAFE_GETENV(
"COLUMNS");
715 int cols = atoi(cols_env);
721 asciichat_mode_t mode = MODE_DISCOVERY;
722 bool for_binary_help =
true;
725 int usage_max_col_width = calculate_section_max_col_width(config,
"usage", mode, for_binary_help);
726 int modes_max_col_width = calculate_section_max_col_width(config,
"modes", mode, for_binary_help);
727 int examples_max_col_width = calculate_section_max_col_width(config,
"examples", mode, for_binary_help);
728 int options_max_col_width = calculate_section_max_col_width(config,
"options", mode, for_binary_help);
731 print_usage_section(config, stream, term_width, usage_max_col_width);
732 print_modes_section(config, stream, term_width, modes_max_col_width);
733 print_mode_options_section(stream, term_width, 40);
734 print_examples_section(config, stream, term_width, examples_max_col_width, MODE_SERVER,
true);
738 const char **unique_groups = SAFE_MALLOC(config->num_descriptors *
sizeof(
const char *),
const char **);
739 size_t num_unique_groups = 0;
741 for (
size_t i = 0; i < config->num_descriptors; i++) {
742 const option_descriptor_t *desc = &config->descriptors[i];
749 bool group_exists =
false;
750 for (
size_t j = 0; j < num_unique_groups; j++) {
751 if (unique_groups[j] && strcmp(unique_groups[j], desc->group) == 0) {
758 if (!group_exists && num_unique_groups < config->num_descriptors) {
759 unique_groups[num_unique_groups++] = desc->group;
764 for (
size_t g = 0; g < num_unique_groups; g++) {
765 const char *current_group = unique_groups[g];
768 fprintf(stream,
"\n");
770 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG, current_group));
773 for (
size_t i = 0; i < config->num_descriptors; i++) {
774 const option_descriptor_t *desc = &config->descriptors[i];
778 strcmp(desc->group, current_group) != 0) {
783 char option_str[BUFFER_SIZE_MEDIUM] =
"";
787 if (desc->short_name) {
789 safe_snprintf(short_flag,
sizeof(short_flag),
"-%c", desc->short_name);
790 char long_flag[BUFFER_SIZE_SMALL];
791 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
794 safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s, %s",
797 char long_flag[BUFFER_SIZE_SMALL];
798 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
799 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s",
804 if (desc->type != OPTION_TYPE_BOOL && desc->type != OPTION_TYPE_ACTION) {
805 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
" ");
807 if (placeholder[0] !=
'\0') {
808 option_len +=
safe_snprintf(option_str + option_len,
sizeof(option_str) - option_len,
"%s",
814 char desc_str[BUFFER_SIZE_MEDIUM] =
"";
817 if (desc->help_text) {
818 desc_len +=
safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
"%s", desc->help_text);
822 bool description_has_default =
823 desc->help_text && (strstr(desc->help_text,
"(default:") || strstr(desc->help_text,
"=default)"));
825 if (desc->default_value && !description_has_default) {
826 char default_buf[32];
828 if (default_len > 0) {
830 safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
" (%s %s)",
835 if (desc->required) {
836 desc_len +=
safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
" [REQUIRED]");
846 SAFE_FREE(unique_groups);
848 fprintf(stream,
"\n");
858 if (!config || !stream)
863 const char *cols_env = SAFE_GETENV(
"COLUMNS");
865 int cols = atoi(cols_env);
874 print_usage_section(config, stream, term_width, max_col_width);
884 asciichat_mode_t mode) {
885 if (!config || !stream) {
886 SET_ERRNO(ERROR_INVALID_PARAM,
"Config or stream is NULL");
892 terminal_size_t term_size;
894 term_width = term_size.cols;
896 const char *cols_env = SAFE_GETENV(
"COLUMNS");
898 int cols = atoi(cols_env);
905 if (max_col_width <= 0) {
910 int max_col_cap = (term_width > 170) ? 86 : 45;
911 if (max_col_width > max_col_cap) {
912 max_col_width = max_col_cap;
918 bool for_binary_help = (mode == MODE_DISCOVERY);
920 const char **unique_groups = SAFE_MALLOC(config->num_descriptors *
sizeof(
const char *),
const char **);
921 size_t num_unique_groups = 0;
925 if (for_binary_help) {
927 bool general_added =
false;
928 bool logging_added =
false;
929 for (
size_t i = 0; i < config->num_descriptors; i++) {
930 const option_descriptor_t *desc = &config->descriptors[i];
932 if (!general_added && strcmp(desc->group,
"GENERAL") == 0) {
933 unique_groups[num_unique_groups++] =
"GENERAL";
934 general_added =
true;
936 if (!logging_added && strcmp(desc->group,
"LOGGING") == 0) {
937 unique_groups[num_unique_groups++] =
"LOGGING";
938 logging_added =
true;
940 if (general_added && logging_added) {
947 for (
size_t i = 0; i < config->num_descriptors; i++) {
948 const option_descriptor_t *desc = &config->descriptors[i];
952 if (strcmp(desc->group,
"GENERAL") == 0 || strcmp(desc->group,
"LOGGING") == 0) {
956 bool group_exists =
false;
957 for (
size_t j = 0; j < num_unique_groups; j++) {
958 if (strcmp(unique_groups[j], desc->group) == 0) {
964 unique_groups[num_unique_groups++] = desc->group;
966 }
else if (desc->group) {
971 for (
size_t i = 0; i < config->num_descriptors; i++) {
972 const option_descriptor_t *desc = &config->descriptors[i];
974 bool group_exists =
false;
975 for (
size_t j = 0; j < num_unique_groups; j++) {
976 if (strcmp(unique_groups[j], desc->group) == 0) {
982 unique_groups[num_unique_groups++] = desc->group;
989 for (
size_t gi = 0; gi < num_unique_groups; gi++) {
990 const char *current_group = unique_groups[gi];
993 fprintf(stream,
"\n");
995 fprintf(stream,
"%s\n",
colored_string(LOG_COLOR_DEBUG, current_group));
997 for (
size_t i = 0; i < config->num_descriptors; i++) {
998 const option_descriptor_t *desc = &config->descriptors[i];
1000 strcmp(desc->group, current_group) != 0) {
1005 char colored_option_str[BUFFER_SIZE_MEDIUM] =
"";
1006 int colored_len = 0;
1009 if (desc->short_name) {
1010 char short_flag[16];
1011 safe_snprintf(short_flag,
sizeof(short_flag),
"-%c", desc->short_name);
1012 char long_flag[BUFFER_SIZE_SMALL];
1013 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
1016 safe_snprintf(colored_option_str + colored_len,
sizeof(colored_option_str) - colored_len,
"%s, %s",
1019 char long_flag[BUFFER_SIZE_SMALL];
1020 safe_snprintf(long_flag,
sizeof(long_flag),
"--%s", desc->long_name);
1021 colored_len +=
safe_snprintf(colored_option_str + colored_len,
sizeof(colored_option_str) - colored_len,
"%s",
1026 if (desc->type != OPTION_TYPE_BOOL && desc->type != OPTION_TYPE_ACTION) {
1027 colored_len +=
safe_snprintf(colored_option_str + colored_len,
sizeof(colored_option_str) - colored_len,
" ");
1029 if (placeholder[0] !=
'\0') {
1030 colored_len +=
safe_snprintf(colored_option_str + colored_len,
sizeof(colored_option_str) - colored_len,
"%s",
1036 char desc_str[1024] =
"";
1039 if (desc->help_text) {
1040 desc_len +=
safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
"%s", desc->help_text);
1044 bool description_has_default =
1045 desc->help_text && (strstr(desc->help_text,
"(default:") || strstr(desc->help_text,
"=default)"));
1047 if (desc->default_value && !description_has_default) {
1048 char default_buf[32];
1050 if (default_len > 0) {
1052 safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
" (%s %s)",
1057 if (desc->required) {
1058 desc_len +=
safe_snprintf(desc_str + desc_len,
sizeof(desc_str) - desc_len,
" [REQUIRED]");
1066 SAFE_FREE(unique_groups);
1095 const char *description, FILE *desc) {
1096 if (!config || !desc) {
1098 fprintf(desc,
"Error: Failed to create options config\n");
1100 SET_ERRNO(ERROR_INVALID_PARAM,
"Config or desc is NULL");
1106 int term_width = 80;
1107 terminal_size_t term_size;
1109 term_width = term_size.cols;
1111 const char *cols_env = SAFE_GETENV(
"COLUMNS");
1113 int cols = atoi(cols_env);
1120 if (term_width >= 60) {
1121 (void)fprintf(desc,
" __ _ ___ ___(_|_) ___| |__ __ _| |_ \n");
1122 (void)fprintf(desc,
" / _` / __|/ __| | |_____ / __| '_ \\ / _` | __|\n");
1123 (void)fprintf(desc,
"| (_| \\__ \\ (__| | |_____| (__| | | | (_| | |_ \n");
1124 (void)fprintf(desc,
" \\__,_|___/\\___|_|_| \\___|_| |_|\\__,_|\\__|\n");
1125 (void)fprintf(desc,
"\n");
1130 const char *space = strchr(program_name,
' ');
1131 if (space && mode >= 0) {
1133 int binary_len = space - program_name;
1134 (void)fprintf(desc,
"%.*s %s - %s\n\n", binary_len, program_name,
colored_string(LOG_COLOR_FATAL, space + 1),
1138 (void)fprintf(desc,
"%s - %s\n\n",
colored_string(LOG_COLOR_FATAL, program_name), description);
1144 (void)fprintf(desc,
"\n");
1148 bool for_binary_help = (mode == MODE_DISCOVERY);
1152 if (config->num_usage_lines > 0) {
1154 const char *mode_name = NULL;
1157 mode_name =
"server";
1160 mode_name =
"client";
1163 mode_name =
"mirror";
1165 case MODE_DISCOVERY_SERVICE:
1166 mode_name =
"discovery-service";
1168 case MODE_DISCOVERY:
1176 int usage_max_col_width = calculate_section_max_col_width(config,
"usage", mode, for_binary_help);
1178 for (
size_t i = 0; i < config->num_usage_lines; i++) {
1179 const usage_descriptor_t *
usage = &config->usage_lines[i];
1182 if (!for_binary_help) {
1185 if (!
usage->mode || !mode_name || strcmp(
usage->mode, mode_name) != 0) {
1190 char usage_buf[BUFFER_SIZE_MEDIUM];
1193 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
"ascii-chat");
1196 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
" %s",
1200 if (
usage->positional) {
1201 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
" %s",
1205 if (
usage->show_options) {
1206 const char *options_text =
1207 (
usage->mode && strcmp(
usage->mode,
"<mode>") == 0) ?
"[mode-options...]" :
"[options...]";
1208 len +=
safe_snprintf(usage_buf + len,
sizeof(usage_buf) - len,
" %s",
1215 fprintf(desc,
"\n");
1218 if (config->num_positional_args > 0) {
1220 option_mode_bitmask_t current_mode_bitmask = 1U << mode;
1221 bool has_applicable_positional_args =
false;
1223 for (
size_t pa_idx = 0; pa_idx < config->num_positional_args; pa_idx++) {
1224 const positional_arg_descriptor_t *pos_arg = &config->positional_args[pa_idx];
1227 if (pos_arg->mode_bitmask != 0 && !(pos_arg->mode_bitmask & current_mode_bitmask)) {
1231 if (pos_arg->section_heading && pos_arg->examples && pos_arg->num_examples > 0) {
1232 has_applicable_positional_args =
true;
1238 if (has_applicable_positional_args) {
1239 int positional_max_col_width = calculate_section_max_col_width(config,
"positional", mode,
false);
1241 for (
size_t pa_idx = 0; pa_idx < config->num_positional_args; pa_idx++) {
1242 const positional_arg_descriptor_t *pos_arg = &config->positional_args[pa_idx];
1245 if (pos_arg->mode_bitmask != 0 && !(pos_arg->mode_bitmask & current_mode_bitmask)) {
1249 if (pos_arg->section_heading && pos_arg->examples && pos_arg->num_examples > 0) {
1250 (void)fprintf(desc,
"%s\n",
colored_string(LOG_COLOR_DEBUG, pos_arg->section_heading));
1252 for (
size_t i = 0; i < pos_arg->num_examples; i++) {
1253 const char *example = pos_arg->examples[i];
1254 const char *p = example;
1255 const char *desc_start = NULL;
1259 const char *first_part = p;
1261 while (*p && !(*p ==
' ' && *(p + 1) ==
' '))
1263 int first_len_bytes = (int)(p - first_part);
1271 char colored_first_part[256];
1272 safe_snprintf(colored_first_part,
sizeof(colored_first_part),
"%.*s", first_len_bytes, first_part);
1273 char colored_result[512];
1280 (void)fprintf(desc,
"\n");
1287 int examples_max_col_width = calculate_section_max_col_width(config,
"examples", mode, for_binary_help);
1288 print_examples_section(config, desc, term_width, examples_max_col_width, mode, for_binary_help);
1291 if (config->num_custom_sections > 0) {
1292 option_mode_bitmask_t current_mode_bitmask = 1U << mode;
1293 for (
size_t i = 0; i < config->num_custom_sections; i++) {
1294 const custom_section_descriptor_t *section = &config->custom_sections[i];
1297 if (section->mode_bitmask != 0 && !(section->mode_bitmask & current_mode_bitmask)) {
1301 if (section->heading) {
1302 fprintf(desc,
"%s\n",
colored_string(LOG_COLOR_DEBUG, section->heading));
1305 if (section->content) {
1307 if (section->heading && strcmp(section->heading,
"KEYBINDINGS") == 0) {
1309 char colored_output[2048] =
"";
1310 const char *src = section->content;
1311 char *dst = colored_output;
1312 size_t remaining =
sizeof(colored_output) - 1;
1314 while (*src && remaining > 0) {
1316 if (*src ==
'?' && *(src + 1) !=
'\n' && *(src + 1) !=
'\0') {
1318 size_t len = strlen(colored);
1319 if (len <= remaining) {
1320 memcpy(dst, colored, len);
1327 }
else if (strncmp(src,
"Space", 5) == 0 &&
1328 (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' '))) {
1330 size_t len = strlen(colored);
1331 if (len <= remaining) {
1332 memcpy(dst, colored, len);
1339 }
else if (strncmp(src,
"arrows", 6) == 0 &&
1340 (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' '))) {
1342 size_t len = strlen(colored);
1343 if (len <= remaining) {
1344 memcpy(dst, colored, len);
1351 }
else if (*src ==
'm' && (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' ')) &&
1352 (*(src + 1) ==
',' || *(src + 1) ==
')')) {
1354 size_t len = strlen(colored);
1355 if (len <= remaining) {
1356 memcpy(dst, colored, len);
1363 }
else if (*src ==
'c' && (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' ')) &&
1364 (*(src + 1) ==
',' || *(src + 1) ==
')')) {
1366 size_t len = strlen(colored);
1367 if (len <= remaining) {
1368 memcpy(dst, colored, len);
1375 }
else if (*src ==
'f' && (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' ')) &&
1376 (*(src + 1) ==
',' || *(src + 1) ==
')')) {
1378 size_t len = strlen(colored);
1379 if (len <= remaining) {
1380 memcpy(dst, colored, len);
1387 }
else if (*src ==
'r' && (src > section->content && (*(src - 1) ==
',' || *(src - 1) ==
' ')) &&
1388 (*(src + 1) ==
')')) {
1390 size_t len = strlen(colored);
1391 if (len <= remaining) {
1392 memcpy(dst, colored, len);
1407 int keybindings_wrap_width = term_width < 90 ? term_width : 90;
1409 fprintf(desc,
"\n");
1411 fprintf(desc,
"%s\n", section->content);
1415 fprintf(desc,
"\n");
1420 int options_max_col_width = calculate_section_max_col_width(config,
"options", mode, for_binary_help);
1425 if (!config || !options_struct)
1429 for (
size_t i = 0; i < config->num_owned_strings; i++) {
1430 free(config->owned_strings[i]);
1434 ((options_config_t *)config)->num_owned_strings = 0;
1437 char *base = (
char *)options_struct;
1438 for (
size_t i = 0; i < config->num_descriptors; i++) {
1439 const option_descriptor_t *desc = &config->descriptors[i];
1440 if (desc->type == OPTION_TYPE_STRING && desc->owns_memory) {
1441 char **field = (
char **)(base + desc->offset);
const char * get_option_help_placeholder_str(const option_descriptor_t *desc)
Get help placeholder string for an option type.
int format_option_default_value_str(const option_descriptor_t *desc, char *buf, size_t bufsize)
Format option default value as a string.
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.
void options_config_print_usage_section(const options_config_t *config, FILE *stream)
Print only the USAGE section.
void options_print_help_for_mode(const options_config_t *config, asciichat_mode_t mode, const char *program_name, const char *description, FILE *desc)
Print help for a specific mode or binary level.
int options_config_calculate_max_col_width(const options_config_t *config)
Calculate global max column width across all help sections.
void options_config_print_usage(const options_config_t *config, FILE *stream)
void options_config_print_options_sections_with_width(const options_config_t *config, FILE *stream, int max_col_width, asciichat_mode_t mode)
Print everything except the USAGE section.
void options_config_print_options_sections(const options_config_t *config, FILE *stream, asciichat_mode_t mode)
Print everything except the USAGE section (backward compatibility wrapper)
void options_struct_destroy(const options_config_t *config, void *options_struct)
void layout_print_wrapped_description(FILE *stream, const char *text, int indent_width, int term_width)
void layout_print_two_column_row(FILE *stream, const char *first_column, const char *second_column, int first_col_len, int term_width)
void print_project_links(FILE *desc)
void usage(FILE *desc, asciichat_mode_t mode)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int utf8_display_width_n(const char *str, size_t max_bytes)
int utf8_display_width(const char *str)
const char * colored_string(log_color_t color, const char *text)