12#include <ascii-chat/options/completions/bash.h>
13#include <ascii-chat/options/completions/completions.h>
14#include <ascii-chat/options/registry.h>
15#include <ascii-chat/options/enums.h>
16#include <ascii-chat/common.h>
21static void bash_escape_help(FILE *output,
const char *text) {
26 for (
const char *p = text; *p; p++) {
29 fprintf(output,
"'\\''");
32 fprintf(output,
"\\n");
35 fprintf(output,
"\\t");
46static void bash_write_option(FILE *output,
const option_descriptor_t *opt) {
51 if (opt->short_name !=
'\0') {
52 fprintf(output,
" '-%c' '", opt->short_name);
53 bash_escape_help(output, opt->help_text);
54 fprintf(output,
"'\n");
57 fprintf(output,
" '--%s' '", opt->long_name);
58 bash_escape_help(output, opt->help_text);
59 fprintf(output,
"'\n");
65static void bash_write_header(FILE *output) {
66 fprintf(output,
"# Bash completion script for ascii-chat\n"
67 "# Generated from options registry - DO NOT EDIT MANUALLY\n"
68 "# Usage: eval \"$(ascii-chat --completions bash)\"\n"
71 " local cur prev words cword\n"
72 " _init_completion || return\n"
79static void bash_write_footer(FILE *output) {
82 "complete -F _ascii_chat ascii-chat\n");
91static asciichat_error_t bash_write_all_options(FILE *output) {
94 size_t binary_count = 0;
97 fprintf(output,
" # Binary-level options (same as 'ascii-chat --help')\n local -a binary_opts=(\n");
99 for (
size_t i = 0; i < binary_count; i++) {
100 bash_write_option(output, &binary_opts[i]);
102 SAFE_FREE(binary_opts);
104 fprintf(output,
" )\n\n");
108 size_t server_count = 0;
111 fprintf(output,
" # Server-mode options (same as 'ascii-chat server --help')\n local -a server_opts=(\n");
113 for (
size_t i = 0; i < server_count; i++) {
114 bash_write_option(output, &server_opts[i]);
116 SAFE_FREE(server_opts);
118 fprintf(output,
" )\n\n");
121 size_t client_count = 0;
124 fprintf(output,
" # Client-mode options (same as 'ascii-chat client --help')\n local -a client_opts=(\n");
126 for (
size_t i = 0; i < client_count; i++) {
127 bash_write_option(output, &client_opts[i]);
129 SAFE_FREE(client_opts);
131 fprintf(output,
" )\n\n");
134 size_t mirror_count = 0;
137 fprintf(output,
" # Mirror-mode options (same as 'ascii-chat mirror --help')\n local -a mirror_opts=(\n");
139 for (
size_t i = 0; i < mirror_count; i++) {
140 bash_write_option(output, &mirror_opts[i]);
142 SAFE_FREE(mirror_opts);
144 fprintf(output,
" )\n\n");
147 size_t discovery_svc_count = 0;
148 const option_descriptor_t *discovery_svc_opts =
151 fprintf(output,
" # Discovery-service options (same as 'ascii-chat discovery-service --help')\n local -a "
152 "discovery_svc_opts=(\n");
153 if (discovery_svc_opts) {
154 for (
size_t i = 0; i < discovery_svc_count; i++) {
155 bash_write_option(output, &discovery_svc_opts[i]);
157 SAFE_FREE(discovery_svc_opts);
159 fprintf(output,
" )\n\n");
167static void bash_write_enum_cases(FILE *output) {
168 size_t combined_count = 0;
172 size_t binary_count = 0;
176 for (
size_t i = 0; i < binary_count; i++) {
178 bool already_has =
false;
179 for (
size_t j = 0; j < combined_count; j++) {
180 if (strcmp(combined_opts[j].long_name, binary_opts[i].long_name) == 0) {
187 option_descriptor_t *temp = (option_descriptor_t *)SAFE_REALLOC(
188 combined_opts, combined_count *
sizeof(option_descriptor_t), option_descriptor_t *);
190 combined_opts = temp;
191 combined_opts[combined_count - 1] = binary_opts[i];
195 SAFE_FREE(binary_opts);
198 if (!combined_opts) {
202 for (
size_t i = 0; i < combined_count; i++) {
203 const option_descriptor_t *opt = &combined_opts[i];
204 const option_metadata_t *meta = &opt->metadata;
207 bool has_examples = meta->examples && meta->examples[0] != NULL;
208 if (meta->input_type != OPTION_INPUT_ENUM && !has_examples && meta->numeric_range.max == 0) {
213 if (opt->short_name !=
'\0') {
214 fprintf(output,
" -%c | ", opt->short_name);
216 fprintf(output,
"--%-25s)\n", opt->long_name);
217 fprintf(output,
" COMPREPLY=($(compgen -W \"");
220 if (meta->input_type == OPTION_INPUT_ENUM && meta->enum_values && meta->enum_values[0] != NULL) {
221 for (
size_t j = 0; meta->enum_values[j] != NULL; j++) {
222 fprintf(output,
"%s", meta->enum_values[j]);
223 if (meta->enum_values[j + 1] != NULL) {
224 fprintf(output,
" ");
229 else if (has_examples) {
230 for (
size_t j = 0; meta->examples[j] != NULL; j++) {
231 fprintf(output,
"%s", meta->examples[j]);
232 if (meta->examples[j + 1] != NULL) {
233 fprintf(output,
" ");
238 else if (meta->input_type == OPTION_INPUT_NUMERIC) {
239 if (meta->numeric_range.min == 1 && meta->numeric_range.max == 9) {
240 fprintf(output,
"1 2 3 4 5 6 7 8 9");
241 }
else if (meta->numeric_range.max > 0) {
242 fprintf(output,
"%d %d %d", meta->numeric_range.min, (meta->numeric_range.min + meta->numeric_range.max) / 2,
243 meta->numeric_range.max);
247 fprintf(output,
"\" -- \"$cur\"))\n");
248 fprintf(output,
" return\n");
249 fprintf(output,
" ;;\n");
252 SAFE_FREE(combined_opts);
258static void bash_write_completion_logic(FILE *output) {
259 fprintf(output,
" # Modes\n"
260 " local modes=\"server client mirror discovery-service\"\n"
262 " # Detect which mode we're in\n"
265 " for ((i = 1; i < cword; i++)); do\n"
266 " case \"${words[i]}\" in\n"
267 " server | client | mirror | discovery-service)\n"
268 " mode=\"${words[i]}\"\n"
274 " case \"$prev\" in\n"
275 " # Options that take file paths\n");
278 size_t combined_count = 0;
283 for (
size_t i = 0; i < combined_count; i++) {
284 const option_descriptor_t *opt = &combined_opts[i];
287 if (meta && meta->input_type == OPTION_INPUT_FILEPATH) {
289 fprintf(output,
" | ");
293 if (opt->short_name !=
'\0') {
294 fprintf(output,
"-%c | ", opt->short_name);
296 fprintf(output,
"--%s", opt->long_name);
299 SAFE_FREE(combined_opts);
302 fprintf(output,
")\n");
303 fprintf(output,
" _filedir\n");
304 fprintf(output,
" return\n");
305 fprintf(output,
" ;;\n");
310 bash_write_enum_cases(output);
312 fprintf(output,
" esac\n"
314 " # If current word starts with -, complete options\n"
315 " if [[ \"$cur\" == -* ]]; then\n"
316 " local -a opts_to_complete\n"
318 " case \"$mode\" in\n"
320 " opts_to_complete=(\"${binary_opts[@]}\" \"${client_opts[@]}\")\n"
323 " opts_to_complete=(\"${binary_opts[@]}\" \"${server_opts[@]}\")\n"
326 " opts_to_complete=(\"${binary_opts[@]}\" \"${mirror_opts[@]}\")\n"
329 " opts_to_complete=(\"${binary_opts[@]}\")\n"
333 " # Generate completions with help text\n"
334 " local -a completions\n"
335 " for ((i = 0; i < ${#opts_to_complete[@]}; i += 2)); do\n"
336 " if [[ \"${opts_to_complete[i]}\" == \"$cur\"* ]]; then\n"
337 " completions+=(\"${opts_to_complete[i]}\")\n"
341 " if [[ ${#completions[@]} -gt 0 ]]; then\n"
342 " if compopt &>/dev/null 2>&1; then\n"
343 " compopt -o nosort 2>/dev/null || true\n"
345 " for opt in \"${completions[@]}\"; do\n"
346 " for ((i = 0; i < ${#opts_to_complete[@]}; i += 2)); do\n"
347 " if [[ \"${opts_to_complete[i]}\" == \"$opt\" ]]; then\n"
348 " COMPREPLY+=(\"$opt ${opts_to_complete[i+1]}\")\n"
354 " COMPREPLY=($(compgen -W \"${completions[*]}\" -- \"$cur\"))\n"
360 " # If no mode specified yet, suggest modes\n"
361 " if [[ -z \"$mode\" ]]; then\n"
362 " COMPREPLY=($(compgen -W \"$modes\" -- \"$cur\"))\n"
369 return SET_ERRNO(ERROR_INVALID_PARAM,
"Output stream cannot be NULL");
372 bash_write_header(output);
373 asciichat_error_t err = bash_write_all_options(output);
374 if (err != ASCIICHAT_OK) {
377 bash_write_completion_logic(output);
378 bash_write_footer(output);
asciichat_error_t completions_generate_bash(FILE *output)
option_descriptor_t * completions_collect_all_modes_unique(size_t *count)
Collect options from all modes with deduplication.
const option_descriptor_t * options_registry_get_for_display(asciichat_mode_t mode, bool for_binary_help, size_t *num_options)
const option_metadata_t * options_registry_get_metadata(const char *long_name)