Added --yes and --no options, and renamed --initial to --query for

compatibility with fzf and fzy
This commit is contained in:
Bruce Hill 2019-06-06 17:46:59 -07:00
parent 5f84d69881
commit 88ed75a56b
2 changed files with 129 additions and 77 deletions

40
ask.1
View File

@ -5,11 +5,10 @@
ask \- A tiny command line tool for getting user input ask \- A tiny command line tool for getting user input
.SH SYNOPSIS .SH SYNOPSIS
.B ask .B ask
[\fI-hpqv\fR] [\fI-hPqvyn\fR]
[\fI--initial=initial\fR] [\fI-q\fR |\fI--query=initial\fR]
[\fIprompt \fR [[\fI-p\fR |\fI--prompt=\fR]\fIprompt \fR
[\fIinitial\fR [\fIoptions...\fR]]
[\fIoptions...\fR]]]
.SH DESCRIPTION .SH DESCRIPTION
\fBask\fR is a tiny console application that displays a prompt, gets user input \fBask\fR is a tiny console application that displays a prompt, gets user input
(with line editing and fuzzy finding functionality), and prints the result to (with line editing and fuzzy finding functionality), and prints the result to
@ -20,7 +19,7 @@ standard output
When used with fuzzy finding, as soon as exactly one match is found, exit and When used with fuzzy finding, as soon as exactly one match is found, exit and
print it. print it.
.B \-p .B \-P
.B \--password .B \--password
Use password mode, which does not print user input as it's being typed. Use password mode, which does not print user input as it's being typed.
@ -30,13 +29,27 @@ Print \fBask\fR's version and exit.
.B \-h .B \-h
.B \--help .B \--help
Print ask's usage and exit. Print \fBask\fR's usage and exit.
.B \--initial=initial .B \-q
.B \--query=
If given, pre-populate the user input with this value. If given, pre-populate the user input with this value.
.B prompt .B \-p
If provided, display the given prompt in bold. (Default: "> ") .B \--prompt=
If provided, display the given prompt in bold. If the \fI-p\fR and
\fI--prompt=\fR flags are not used, the first positional argument is used as
the prompt, or \fB"> "\fR if there are no positional arguments.
.B \-y
.B \--yes
Quickpick between "y" and "n" with "[Y/n]" appended to the prompt, exiting with
success if "n" is not chosen.
.B \-n
.B \--no
Quickpick between "y" and "n" with "[y/N]" appended to the prompt, exiting with
failure if "y" is not chosen.
.B options... .B options...
If additional command line arguments are provided, or if any input is piped in, If additional command line arguments are provided, or if any input is piped in,
@ -56,13 +69,8 @@ Fuzzy find a file. (Equivalent of \fBrm "`ls | fzf --prompt='Delete file: '`"\fR
.TP .TP
.B .B
if test "`ask -q 'Do thing? [Y/n] ' '' Y N`" != N; then dothing; fi if ask -y 'Do thing? '; then dothing; fi
Ask user for confirmation (default: yes). Ask user for confirmation (default: yes).
.TP
.B
if test "`ask -q 'Do thing? [y/N] ' '' Y N`" = Y; then dothing; fi
Ask user for confirmation (default: no).
.SH AUTHOR .SH AUTHOR
Bruce Hill (bruce@bruce-hill.com) Bruce Hill (bruce@bruce-hill.com)

166
ask.c
View File

@ -1,12 +1,15 @@
/* ask - a simple command line asker /* ask - a simple command line asker
* Copyright 2019 Bruce Hill * Copyright 2019 Bruce Hill
* Released under the MIT License (see LICENSE for details) * Released under the MIT License (see LICENSE for details)
* Usage: ask [-q|--quickpick] [-p|--password] [-v|--version] [-h|--help] [--initial=initial] [prompt [options...]] * Usage: ask [-Q|--quickpick] [-y|--yes] [-n|--no] [-p|--password]
* [-v|--version] [-h|--help] [-q |--query=initial] [[--prompt=]prompt [options...]]
* --password: password mode * --password: password mode
* --quickpick: quickpick mode (exit when only one option remains) * --quickpick: quickpick mode (exit when only one option remains)
* --version: print version and exit * --version: print version and exit
* --help: print usage and exit * --help: print usage and exit
* --initial: initial value to pre-populate user input * --query: initial value to pre-populate user input
* --prompt: use the given prompt (displayed in bold)
* --yes: append "[Y/n]" to the prompt, use quickpick mode,
*/ */
#include <poll.h> #include <poll.h>
#include <stdio.h> #include <stdio.h>
@ -19,6 +22,7 @@
#include "bterm.h" #include "bterm.h"
#define ASK_VERSION "0.2"
#define LOWERCASE(c) ('A' <= (c) && (c) <= 'Z' ? ((c) + 'a' - 'A') : (c)) #define LOWERCASE(c) ('A' <= (c) && (c) <= 'Z' ? ((c) + 'a' - 'A') : (c))
#define EQ(a, b) (case_sensitive ? (a) == (b) : LOWERCASE(a) == LOWERCASE(b)) #define EQ(a, b) (case_sensitive ? (a) == (b) : LOWERCASE(a) == LOWERCASE(b))
#define PASSWORD "-\\|/" #define PASSWORD "-\\|/"
@ -125,7 +129,6 @@ static int draw_line(FILE *out, const char *line, const char *patt, int cursor)
*/ */
static char *get_input(FILE *in, FILE *out, const char *prompt, const char *initial, int nopts, char **opts) static char *get_input(FILE *in, FILE *out, const char *prompt, const char *initial, int nopts, char **opts)
{ {
if (!prompt) prompt = "> ";
fprintf(out, "\033[K\033[0;1m%s\033[0m", prompt); fprintf(out, "\033[K\033[0;1m%s\033[0m", prompt);
size_t cap = initial ? strlen(initial) + 100 : 100; size_t cap = initial ? strlen(initial) + 100 : 100;
char *buf = memcheck(calloc(cap, 1)); char *buf = memcheck(calloc(cap, 1));
@ -184,7 +187,6 @@ static char *get_input(FILE *in, FILE *out, const char *prompt, const char *init
switch (key) { switch (key) {
case -1: case -2: case -3: goto skip_redraw; case -1: case -2: case -3: goto skip_redraw;
case '\r': case '\r':
// TODO: support backslash-enter
goto finished; goto finished;
case KEY_CTRL_C: case KEY_ESC: case KEY_CTRL_C: case KEY_ESC:
free(buf); free(buf);
@ -295,6 +297,98 @@ static char *get_input(FILE *in, FILE *out, const char *prompt, const char *init
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int yes = 0, no = 0;
char *prompt = NULL, *query = NULL;
char **opts = NULL;
size_t linescap = 0, linecap = 0;
int nopts = 0;
char *line = NULL;
struct pollfd pfd = {STDIN_FILENO, POLLIN, 0};
if (poll(&pfd, 1, 50) > 0) {
while ((getline(&line, &linecap, stdin)) >= 0) {
if ((size_t)nopts >= linescap)
opts = memcheck(realloc(opts, (linescap += 100)*sizeof(char*)));
if (!line[0]) continue;
if (line[strlen(line)-1] == '\n')
line[strlen(line)-1] = '\0';
opts[nopts++] = memcheck(strdup(line));
}
}
int a = 1;
while (a < argc) {
if (argv[a][0] == '-' && argv[a][1] != '-') {
for (char *p = &argv[a][1]; *p; p++) {
switch (*p) {
case 'P': password = 1; break;
case 'q': quickpick = 1; break;
case 'h': goto help_flag;
case 'v': goto version_flag;
case 'y': yes = 1; quickpick = 1; break;
case 'n': no = 1; quickpick = 1; break;
}
}
} else if (strcmp(argv[a], "-p") == 0) {
prompt = argv[++a];
} else if (strcmp(argv[a], "-q") == 0) {
query = argv[++a];
} else if (strncmp(argv[a], "--prompt=", strlen("--prompt=")) == 0) {
prompt = &argv[a][strlen("--prompt=")];
} else if (strncmp(argv[a], "--query=", strlen("--query=")) == 0) {
query = &argv[a][strlen("--query=")];
} else if (strcmp(argv[a], "--password") == 0) {
password = 1;
} else if (strcmp(argv[a], "--quickpick") == 0) {
quickpick = 1;
} else if (strcmp(argv[a], "--yes") == 0) {
yes = 1;
quickpick = 1;
} else if (strcmp(argv[a], "--no") == 0) {
no = 1;
quickpick = 1;
} else if (strcmp(argv[a], "--help") == 0) {
help_flag:
printf("ask - A simple command line input tool.\n"
"Usage: ask [-Q|--quickpick] [-P|--password] [-v|--version] [-h|--help] "
"[-y|--yes] [-n|--no] [-q |--query=query] [[-p |--prompt=]prompt [options...]]\n");
return 0;
} else if (strcmp(argv[a], "--version") == 0) {
version_flag:
printf("ask %s\n", ASK_VERSION);
return 0;
} else break;
++a;
}
if (!prompt && a < argc) prompt = argv[a++];
while (a < argc) {
if ((size_t)nopts >= linescap)
opts = memcheck(realloc(opts, (linescap += 100)*sizeof(char*)));
opts[nopts++] = argv[a++];
}
if (yes || no) {
if ((size_t)nopts + 4 >= linescap)
opts = memcheck(realloc(opts, (linescap += 4)*sizeof(char*)));
opts[nopts++] = "y";
opts[nopts++] = "n";
opts[nopts++] = "Y";
opts[nopts++] = "N";
}
if (yes) {
char *p2 = memcheck(calloc((prompt ? strlen(prompt) : 0)+5+1, sizeof(char)));
if (prompt) strcpy(p2, prompt);
strcat(p2, "[Y/n]");
prompt = p2;
} else if (no) {
char *p2 = memcheck(calloc((prompt ? strlen(prompt) : 0)+5+1, sizeof(char)));
if (prompt) strcpy(p2, prompt);
strcat(p2, "[y/N]");
prompt = p2;
}
if (!prompt) prompt = "> ";
FILE *tty_in = fopen("/dev/tty", "r"); FILE *tty_in = fopen("/dev/tty", "r");
FILE *tty_out = fopen("/dev/tty", "w"); FILE *tty_out = fopen("/dev/tty", "w");
struct termios orig_termios, bb_termios; struct termios orig_termios, bb_termios;
@ -303,60 +397,7 @@ int main(int argc, char *argv[])
if (tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios) == -1) if (tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios) == -1)
return 1; return 1;
char *prompt = NULL, *initial = NULL; char *output = get_input(tty_in, tty_out, prompt, query, nopts, opts);
char **lines = NULL;
size_t linescap = 0, linecap = 0;
int nlines = 0;
char *line = NULL;
struct pollfd pfd = {STDIN_FILENO, POLLIN, 0};
if (poll(&pfd, 1, 50) > 0) {
while ((getline(&line, &linecap, stdin)) >= 0) {
if ((size_t)nlines >= linescap)
lines = memcheck(realloc(lines, (linescap += 100)*sizeof(char*)));
if (!line[0]) continue;
if (line[strlen(line)-1] == '\n')
line[strlen(line)-1] = '\0';
lines[nlines++] = memcheck(strdup(line));
}
}
int a = 1;
while (a < argc) {
if (argv[a][0] == '-' && argv[a][1] != '-') {
for (char *p = &argv[a][1]; *p; p++) {
switch (*p) {
case 'p': password = 1; break;
case 'q': quickpick = 1; break;
case 'h': goto help_flag;
case 'v': goto version_flag;
}
}
} else if (strncmp(argv[a], "--initial=", strlen("--initial=")) == 0) {
initial = &argv[a][strlen("--initial=")];
} else if (strcmp(argv[a], "--password") == 0) {
password = 1;
} else if (strcmp(argv[a], "--quickpick") == 0) {
quickpick = 1;
} else if (strcmp(argv[a], "--help") == 0) {
help_flag:
printf("ask - A simple command line input tool.\n"
"Usage: ask [-q|--quickpick] [-p|--password] [-v|--version] [-h|--help] [--initial=initial] [prompt [options...]]\n");
return 0;
} else if (strcmp(argv[a], "--version") == 0) {
version_flag:
printf("ask v0.1\n");
return 0;
} else break;
++a;
}
if (!prompt && a < argc) prompt = argv[a++];
while (a < argc) {
if ((size_t)nlines >= linescap)
lines = memcheck(realloc(lines, (linescap += 100)*sizeof(char*)));
lines[nlines++] = argv[a++];
}
char *output = get_input(tty_in, tty_out, prompt, initial, nlines, lines);
fflush(tty_out); fflush(tty_out);
tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios); tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios);
@ -365,13 +406,16 @@ int main(int argc, char *argv[])
fclose(tty_in); fclose(tty_in);
tty_in = NULL; tty_in = NULL;
// This doesn't free memory, but it doesn't need to because
// the program is exiting
if (!output) return 1; if (!output) return 1;
fputs(output, stdout); fputs(output, stdout);
free(output); if (yes)
return strcmp(output, "n") == 0 && strcmp(output, "N") == 0;
if (no)
return strcmp(output, "y") != 0 && strcmp(output, "Y") != 0;
// This doesn't free the memory in lines, but it doesn't need to because
// the program is exiting
return 0; return 0;
} }
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1