mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
libutil: Implement zsh and bash autocompletion
Add functionality for generating autocompletion scripts, allowing for tab completion of tool options for bash and zsh. This functionality relies on reading the available options from the util_opt struct at runtime. The script generation happens on the build system. Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Szabina Korbai <szkorbai@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
committed by
Steffen Eiden
parent
70157462d8
commit
638cbbe332
81
common_autocomp.mak
Normal file
81
common_autocomp.mak
Normal file
@@ -0,0 +1,81 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright IBM Corp. 2025
|
||||
|
||||
|
||||
# This file defines the build process for shell autocompletion binaries
|
||||
#
|
||||
# How to incorporate it into tool Makefiles:
|
||||
#
|
||||
# 0. The file with the autocompletion script generation code should be named
|
||||
# 'autocompletion_generator_host.c'
|
||||
#
|
||||
# 1. Define the 'bash-completions' and 'zsh-completions' variables: both must be a list
|
||||
# of the names of all the shell completion scripts that belong to the tools the Makefile
|
||||
# is responsible for building.
|
||||
#
|
||||
# (The name of a zsh completion script is the same as the name of the tool,
|
||||
# prefixed by an underscore)
|
||||
#
|
||||
# (The name of a bash completion script is the same as the name of the tool,
|
||||
# suffixed by '.bash')
|
||||
#
|
||||
# 2. include this file
|
||||
#
|
||||
# +1: Add the autocompletion scripts to the .gitignore file
|
||||
#
|
||||
# (See cpumf or dasdfmt as example)
|
||||
|
||||
|
||||
autocomplete-bin := autocompletion_generator_host
|
||||
|
||||
autocomp-object := $(rootdir)/libutil/util_autocomp_host.o
|
||||
|
||||
$(autocomp-object): $(rootdir)/libutil
|
||||
$(MAKE) -C $(rootdir)/libutil/ $(notdir $@)
|
||||
|
||||
$(autocomplete-bin).o: $(autocomplete-bin).c
|
||||
$(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) -I $(rootdir)/include -c $< -o $@
|
||||
|
||||
$(autocomplete-bin): $(autocomplete-bin).o $(autocomp-object)
|
||||
$(LINK_FOR_BUILD) $^ -o $@
|
||||
|
||||
$(bash-completions) $(zsh-completions) &: $(autocomplete-bin)
|
||||
./$(autocomplete-bin)
|
||||
|
||||
install-shell-completions: $(bash-completions) $(zsh-completions)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(ZSHCOMPLETIONDIR)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(BASHCOMPLETIONDIR)
|
||||
for completion in $(bash-completions); do \
|
||||
$(INSTALL) -m 644 $$completion $(DESTDIR)$(BASHCOMPLETIONDIR); \
|
||||
done
|
||||
for completion in $(zsh-completions); do \
|
||||
$(INSTALL) -m 644 $$completion $(DESTDIR)$(ZSHCOMPLETIONDIR); \
|
||||
done
|
||||
|
||||
all: $(zsh-completions) $(bash-completions)
|
||||
|
||||
install: install-shell-completions
|
||||
|
||||
.PHONY: install-shell-completions $(rootdir)/libutil
|
||||
|
||||
clean-autocomplete-bin:
|
||||
$(RM) -- $(autocomplete-bin)
|
||||
|
||||
clean: clean-autocomplete-bin
|
||||
|
||||
ifdef bash-completions
|
||||
clean-bash-completions:
|
||||
$(RM) -- $(bash-completions)
|
||||
|
||||
clean: clean-bash-completions
|
||||
endif
|
||||
|
||||
|
||||
ifdef zsh-completions
|
||||
clean-zsh-completions:
|
||||
$(RM) -- $(zsh-completions)
|
||||
|
||||
clean: clean-zsh-completions
|
||||
endif
|
||||
|
||||
.PHONY: clean-autocomplete-bin clean-bash-completions clean-zsh-completions
|
||||
22
include/lib/util_autocomp.h
Normal file
22
include/lib/util_autocomp.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LIB_UTIL_AUTOCOMP_H
|
||||
#define LIB_UTIL_AUTOCOMP_H
|
||||
|
||||
#include "lib/util_opt.h"
|
||||
|
||||
void generate_autocomp(struct util_opt *opt_vec, char *tool_name);
|
||||
|
||||
#endif
|
||||
@@ -1,13 +1,16 @@
|
||||
include ../common.mak
|
||||
include ../common_autocomp.mak
|
||||
|
||||
lib := libutil.a
|
||||
|
||||
sources := $(filter-out %_example.c,$(wildcard *.c))
|
||||
sources := $(filter-out %_example.c %_host.c, $(wildcard *.c))
|
||||
objects := $(patsubst %.c,%.o,$(sources))
|
||||
|
||||
autocomp-object := util_autocomp_host.o
|
||||
|
||||
examples := $(patsubst %.c,%,$(wildcard *_example.c))
|
||||
|
||||
all: $(lib)
|
||||
all: $(lib) $(autocomp-object)
|
||||
examples: $(examples)
|
||||
|
||||
$(examples): %: %.o $(lib)
|
||||
|
||||
299
libutil/util_autocomp_host.c
Normal file
299
libutil/util_autocomp_host.c
Normal file
@@ -0,0 +1,299 @@
|
||||
// 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\n\
|
||||
\tlocal current_word previous_word options_array\n\n\
|
||||
\tCOMPREPLY=()\n\n\
|
||||
\tcurrent_word=\"${COMP_WORDS[COMP_CWORD]}\"\n\n\
|
||||
\tprevious_word=\"${COMP_WORDS[COMP_CWORD-1]}\"\n\n\
|
||||
\toptions_array=\"";
|
||||
|
||||
static const char *bash_script_part2 = "\tif [[ ${current_word} == -* || ${COMP_CWORD} -eq 1 ]] ; then\n\n\
|
||||
\t\tCOMPREPLY=( $(compgen -W \"${options_array}\" -- ${current_word} ) )\n\n\
|
||||
\t\treturn 0\n\n\
|
||||
\tfi\n\n\
|
||||
}\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 \\\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 *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);
|
||||
}
|
||||
}
|
||||
if (write(fd, "\n}\n", 3) != 3)
|
||||
return -EIO;
|
||||
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'.
|
||||
*
|
||||
* The full script will be as follows, supposing the tool name is
|
||||
* 'example' and it only has the options '--help' and
|
||||
* '--version':
|
||||
*
|
||||
* _example() {
|
||||
*
|
||||
* local current_word previous_word options_array
|
||||
*
|
||||
* COMPREPLY=()
|
||||
*
|
||||
* current_word="${COMP_WORDS[COMP_CWORD]}"
|
||||
*
|
||||
* previous_word="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
*
|
||||
* options_array="--version --help"
|
||||
*
|
||||
* if [[ ${current_word} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
*
|
||||
* COMPREPLY=( $(compgen -W "${options_array}" -- ${current_word} ) )
|
||||
*
|
||||
* return 0
|
||||
*
|
||||
* fi
|
||||
*
|
||||
* }
|
||||
*
|
||||
* complete -F _example example
|
||||
*
|
||||
*/
|
||||
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'.
|
||||
*
|
||||
* The full script will be as follows, supposing the tool name is
|
||||
* 'example' and it only has the options '--help', -h and
|
||||
* '--version' (the descriptions, as well as the flags are
|
||||
* taken from a util_opt struct):
|
||||
*
|
||||
* #compdef example_completion
|
||||
*
|
||||
* function _example_completion {
|
||||
*
|
||||
* _arguments -C \
|
||||
* "-h[Show help information]" \
|
||||
* "--help[Show help but long format]" \
|
||||
* "--version[Show version]"
|
||||
* }
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user