feat: add rl-loop utility program
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
CismonX 2024-04-10 22:52:14 +08:00
parent ade349d1e8
commit 0956b2cd92
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
5 changed files with 284 additions and 1 deletions

View File

@ -19,7 +19,8 @@ steps:
gcc g++ make pkgconf autoconf automake libtool
autoconf-archive dejagnu texinfo libreadline-dev librime-dev
- mkdir build && cd build && autoreconf -i ..
- ../configure --with-readline --with-rime --enable-arif-debug
- ../configure --with-readline --with-rime
--enable-arif-debug --enable-arify --enable-rl-loop
CFLAGS='-O0 -g -std=c99 -Wall -Wextra -Wpedantic -Wshadow'
CPPFLAGS='-D_POSIX_C_SOURCE=200112L'
- make

View File

@ -56,6 +56,16 @@ AM_CONDITIONAL([ENABLE_DEBUG], [test x${enable_debug} = xyes])
ARIF_ARG_ENABLE([arify], [yes], [the arify program])
AM_CONDITIONAL([BUILD_ARIFY], [test x${enable_arify} != xno])
ARIF_ARG_ENABLE([rl-loop], [no], [the rl-loop program], [], [
AS_VAR_IF([with_readline], [no], [
AC_MSG_ERROR(m4_normalize([
The rl-loop program could not be built,
since mandatory dependency GNU Readline is not configured.
]))
])
])
AM_CONDITIONAL([BUILD_RL_LOOP], [test x${enable_rl_loop} != xno])
# -- Checks for compiler builtins and attributes --
AX_GCC_BUILTIN([__builtin_unreachable])

View File

@ -6,9 +6,11 @@
# this notice are preserved. This file is offered as-is, without any warranty.
#
bin_PROGRAMS =
include_HEADERS =
noinst_HEADERS =
pkglib_LTLIBRARIES =
dist_man1_MANS =
dist_man3_MANS =
@ -25,3 +27,14 @@ if BUILD_ARIF_RIME
rime_la_SOURCES = arif_rime.c arif_rime_workaround.cc
endif # BUILD_ARIF_RIME
if BUILD_RL_LOOP
bin_PROGRAMS += rl-loop
dist_man1_MANS += rl-loop.1
rl_loop_CPPFLAGS = $(READLINE_CFLAGS)
rl_loop_LDADD = $(READLINE_LIBS)
rl_loop_SOURCES = rl_loop.c
endif # BUILD_RL_LOOP

88
examples/rl-loop.1 Normal file
View File

@ -0,0 +1,88 @@
.TH RL-LOOP 1 "Apr 10, 2024" 0.1.0 "ARIF User Manual"
.
.SH NAME
rl-loop - Readline loop
.
.SH SYNOPSIS
.B rl-loop
.RI [ options ]
.I pathname
.RI [ args ]
.
.SH DESCRIPTION
Interactively reads user input with GNU Readline.
.PP
For each line of user input, executes the program referred to by
.IR pathname ,
passing
.I args
as its command-line arguments.
.
.SH OPTIONS
.TP
.B -e
Executes
.I pathname
even if the input line is empty.
.TP
\fB\-i\fR \fIreplace-idx\fR
When executing
.IR pathname ,
replaces the value of the nth (starting from 1) argument referred to by
.I replace-idx
with the line of text.
.IP
If this option is not provided, or has a value of 0, the line will be written
to standard input instead.
.TP
\fB\-n\fR \fIname\fR
Readline application name.
Default value is "rl-loop".
.IP
Allows application-specific settings in a Readline init file.
See "Conditional Init Constructs" subsection of GNU Readline's user manual.
.TP
\fB\-p\fR \fIprompt\fR
The prompt text to be printed before reading user input.
Default value is "% ".
.
.SH EXIT STATUS
The program exits with status 0 if there are no errors, or 1 if otherwise.
.
.SH NOTES
The functionalities provided by this program can be achived with a simple
Bash script
.RB ( "read -e"
and friends).
.PP
However, Bash resets Readline state after each
.BR read (1),
necessitating the need to reinstall the completion functions if they are
not handled by Bash, which could be tricky to workaround.
.PP
You may find this program useful when dealing with such cases (e.g. when using
.BR arify (1)).
.
.SH EXAMPLES
Copy each line to the Wayland clipboard:
.PP
.in +4n
.EX
$ rl-loop -i4 -- wl-copy -t text/plain -- %
.EE
.in
.
.SH COPYRIGHT
Copyright (C) 2024 CismonX <admin@cismon.net>
.PP
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
.PP
You should have received a copy of the license along with this document.
If not, see <https://www.gnu.org/licenses/fdl-1.3.html>.
.
.SH SEE ALSO
.BR bash (1),
.BR readline (3)

171
examples/rl_loop.c Normal file
View File

@ -0,0 +1,171 @@
/**
* arif/examples/rl_loop.c
* ----
*
* Copyright (C) 2024 CismonX <admin@cismon.net>
*
* This file is part of ARIF, Another Readline Input Framework.
*
* ARIF is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARIF is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ARIF. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_READLINE
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <readline/history.h>
#include <readline/readline.h>
// Forward declaration start
static void print_usage (char const *);
static int send_line (char *, int, char *[]);
// Forward declaration end
static void
print_usage (
char const *program
) {
fprintf(stderr, "Usage: %s [options] pathname [args]\n\n", program);
fputs("See the rl-loop(1) man page for details.\n", stderr);
}
static int
send_line (
char *line,
int replace_idx,
char *argv[]
) {
int pfds[2];
if (0 != pipe(pfds)) {
fprintf(stderr, "pipe(): %s\n", strerror(errno));
return -1;
}
pid_t child = fork();
if (child < 0) {
fprintf(stderr, "fork(): %s\n", strerror(errno));
return -1;
}
if (child != 0) {
close(pfds[0]);
if (replace_idx == 0) {
for (size_t line_len = rl_end; line_len > 0; ) {
ssize_t nbytes = write(pfds[1], line, line_len);
if (nbytes < 0) {
fprintf(stderr, "write(): %s\n", strerror(errno));
break;
}
line_len -= nbytes;
}
}
close(pfds[1]);
if (-1 == waitpid(child, NULL, 0)) {
fprintf(stderr, "waitpid(): %s\n", strerror(errno));
return -1;
}
} else {
close(pfds[1]);
if (-1 == dup2(pfds[0], STDIN_FILENO)) {
fprintf(stderr, "dup2(): %s\n", strerror(errno));
return -1;
}
close(pfds[0]);
if (replace_idx != 0) {
argv[replace_idx] = line;
}
if (0 != execvp(argv[0], argv)) {
fprintf(stderr, "execvp(): %s: %s\n", argv[0], strerror(errno));
return -1;
}
}
return 0;
}
int
main (
int argc,
char *argv[]
) {
char const *name = "rl-loop";
char const *prompt = "% ";
int replace_idx = 0;
bool send_empty = false;
for (int opt; -1 != (opt = getopt(argc, argv, "ei:n:p:")); ) {
switch (opt) {
case 'e':
send_empty = true;
break;
case 'i':
replace_idx = atoi(optarg);
break;
case 'n':
name = optarg;
break;
case 'p':
prompt = optarg;
break;
case '?':
default:
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
}
argc -= optind;
if (argc == 0) {
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
if (replace_idx < 0 || replace_idx >= argc) {
fprintf(stderr, "%s: bad option -i, expected [0, %d), got %d\n",
argv[0], argc, replace_idx);
exit(EXIT_FAILURE);
}
argv += optind;
rl_readline_name = name;
for (char *line; ; free(line)) {
line = readline(prompt);
if (line == NULL) {
break;
}
if (rl_end > 0) {
add_history(line);
} else {
if (!send_empty) {
continue;
}
}
if (0 != send_line(line, replace_idx, argv)) {
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}
#endif // defined(HAVE_READLINE)