/* * SPDX-License-Identifier: MIT * * Copyright IBM Corp. */ #include #include #include #include #include #include #include "lib/util_autocomp.h" #include "lib/util_opt.h" static const char *bash_script_part1 = "() {\n\ \tlocal current_word options_array\n\ \tCOMPREPLY=()\n\n\ \tcurrent_word=\"${COMP_WORDS[COMP_CWORD]}\"\n\ \toptions_array=\""; static const char *bash_script_part2 = "\tif [[ ${current_word} == -* ]] ; then\n\ \t\tmapfile -t \"COMPREPLY\" < <(compgen -W \"${options_array}\" -- \"$current_word\")\n\ \telse\n\ \t\tcompopt -o bashdefault -o default\n\ \tfi\n\ \treturn 0\n\ }\n\ complete -F "; static char *format_name(const char *fmt, char *tool_name) { char *func_name; if (asprintf(&func_name, fmt, tool_name) == -1) return NULL; return func_name; } /* * The convention for a completion function name is to be the same * as the command's name, but prefixed by '_'. */ static char *generate_func_name(char *tool_name) { return format_name("_%s", tool_name); } static char *generate_bash_filename(char *tool_name) { return format_name("%s.bash", tool_name); } static int init_scriptfile(char *file_path) { int fd; fd = open(file_path, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) return -EIO; return fd; } static int start_bash_scriptfile(int fd, char *func_name) { int len, ret = 0; char *str; len = asprintf(&str, "%s%s", func_name, bash_script_part1); if (len == -1) return -EIO; if (write(fd, str, len) != len) ret = -EIO; free(str); return ret; } static int start_zsh_scriptfile(int fd, char *func_name, char *tool_name) { const char *part3 = " {\n\n\t_arguments -C -A \"*\" \\\n"; const char *part2 = "\n\nfunction "; const char *part1 = "#compdef "; int len, ret = 0; char *str; len = asprintf(&str, "%s%s%s%s%s", part1, tool_name, part2, func_name, part3); if (len == -1) return -EIO; if (write(fd, str, len) != len) ret = -EIO; free(str); return ret; } static int write_bash_command_options(struct util_opt *opt_vec, int fd) { const char *prefix = " --"; char *str; int len; for (int i = 0; opt_vec[i].desc; i++) { if (opt_vec[i].option.name) { len = asprintf(&str, "%s%s", prefix, opt_vec[i].option.name); if (len == -1) return -EIO; if (write(fd, str, len) != len) { free(str); return -EIO; } free(str); } } if (write(fd, "\"\n\n", 3) != 3) return -EIO; return 0; } static int write_zsh_command_options(struct util_opt *opt_vec, int fd) { const char *end = "\t\t\"*:files:_files\"\n}\n"; const char *name, *desc; char *str; int len; for (int i = 0; opt_vec[i].desc; i++) { if (opt_vec[i].option.name) { name = opt_vec[i].option.name; desc = opt_vec[i].desc; len = asprintf(&str, "\t\t\"--%s[%s]\" \\\n", name, desc); if (len == -1) return -EIO; if (write(fd, str, len) != len) { free(str); return -EIO; } free(str); } } len = asprintf(&str, "%s", end); if (len == -1) return -EIO; if (write(fd, str, len) != len) { free(str); return -EIO; } free(str); return 0; } static int finish_bash_scriptfile(char *tool_name, int fd, char *func_name) { int len, ret = 0; char *str; len = asprintf(&str, "%s%s %s\n", bash_script_part2, func_name, tool_name); if (len == -1) return -EIO; if (write(fd, str, len) != len) ret = -EIO; free(str); return ret; } /* * Adds tab completion in bash for a command. * Works by generating an autocompletion * script file at '/usr/share/bash-completion/completions'. */ static void generate_bash_autocomp(struct util_opt *opt_vec, char *tool_name) { char *func_name, *filename; int fd, ret = 0; func_name = generate_func_name(tool_name); if (!func_name) { ret = -ENOMEM; goto end; } filename = generate_bash_filename(tool_name); if (!filename) { ret = -ENOMEM; goto free_func; } fd = init_scriptfile(filename); if (fd < 0) { ret = fd; goto free_file; } ret = start_bash_scriptfile(fd, func_name); if (ret < 0) goto close; ret = write_bash_command_options(opt_vec, fd); if (ret < 0) goto close; ret = finish_bash_scriptfile(tool_name, fd, func_name); close: close(fd); if (ret) remove(filename); free_file: free(filename); free_func: free(func_name); end: printf(" AUTOCOMP\t%s/%s.bash\n", tool_name, tool_name); if (ret) printf("%s.bash: error - %s\n", tool_name, strerror(abs(ret))); } /* * Adds tab completion in zsh for a command. * Works by generating an autocompletion * script file at '/usr/share/zsh/site-functions'. */ static void generate_zsh_autocomp(struct util_opt *opt_vec, char *tool_name) { char *func_name; int fd, ret = 0; func_name = generate_func_name(tool_name); if (!func_name) { ret = -ENOMEM; goto end; } fd = init_scriptfile(func_name); if (fd < 0) { ret = fd; goto free_func; } ret = start_zsh_scriptfile(fd, func_name, tool_name); if (ret < 0) goto close; ret = write_zsh_command_options(opt_vec, fd); close: close(fd); if (ret) remove(func_name); free_func: free(func_name); end: printf(" AUTOCOMP\t%s/_%s\n", tool_name, tool_name); if (ret) printf("_%s: error - %s\n", tool_name, strerror(abs(ret))); } void generate_autocomp(struct util_opt *opt_vec, char *tool_name) { generate_bash_autocomp(opt_vec, tool_name); generate_zsh_autocomp(opt_vec, tool_name); }