mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Future updates might change the outcome of the resulting scripts and the comments describing the script output would need to be updated every time as well. It's not worth the effort. Remove the comments that list script examples from the functions generate_bash_autocomp() and generate_zsh_autocomp(). Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
263 lines
5.5 KiB
C
263 lines
5.5 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* autocomp - command line autocompletion
|
|
*
|
|
* Generating autocompletion scripts for bash and zsh
|
|
* based on util_opt struct
|
|
*
|
|
* Copyright IBM Corp. 2025
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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, 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);
|
|
}
|