Skip to content

Commit

Permalink
More redisx-cli improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Dec 12, 2024
1 parent 2508a60 commit 0f44972
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
path: xchange

- name: Install build dependencies
run: sudo apt-get install libpopt-dev libreadline-dev
run: sudo apt-get install libpopt-dev libreadline-dev libbsd-dev

- name: Build static library
run: make static
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
CC: gcc
steps:
- name: install build deps
run: sudo apt-get install doxygen libpopt-dev libreadline-dev
run: sudo apt-get install doxygen libpopt-dev libreadline-dev libsd-dev

- name: Check out RedisX
uses: actions/checkout@v4
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ Before then the API may undergo slight changes and tweaks. Use the repository as
The [Smithsonian/xchange](https://github.com/Smithsonian/xchange) library is both a build and a runtime dependency of
RedisX.

Additionally `redisx-cli` has the following dependencies:

- POPT library (`popt-devel` on RPM-based, or `libpopt-dev` on Debian based Linux).
- BSD libraries (`libbsd-devel` on RPM-based, or `libbsd-dev` on Debian based Linux).
- readline library (`readline-devel` on RPM based, or `libreadline-dev` on Debian based Linux).

-----------------------------------------------------------------------------

<a name="building-redisx"></a>
Expand Down
177 changes: 88 additions & 89 deletions src/redisx-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <math.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <bsd/readpassphrase.h>

#include "redisx-priv.h"

Expand All @@ -24,91 +25,26 @@ static char *host = "127.0.0.1";
static int port = 6379;
static int format = 0;

static void process(Redis *redis, const char **cmdargs, int nargs) {
int status = X_SUCCESS;
RESP *reply = redisxArrayRequest(redis, cmdargs, NULL, nargs, &status);

if(!status) {
if(format == FORMAT_JSON) redisxPrintJSON("REPLY", reply);
else redisxPrintRESP(reply);
}

redisxDestroyRESP(reply);
static void printRESP(const char *prefix, const RESP *resp) {
if(format == FORMAT_JSON) redisxPrintJSON(prefix, resp);
else redisxPrintRESP(resp);
}

static char *get_token(char *line, int *end) {
int from, to, quote = 0, escaped = 0;
char *token;

*end = 0;

while(isspace(*line)) {
line++;
(*end)++;
}

for(from = 0; line[from]; from++) {
char c = line[from];

if(!escaped) {
if(isspace(c)) break;
else if(c == '\'' || c == '"') quote = !quote;
else if(c == '\\') escaped = 1;
}
else escaped = 0;
}

if(from == 0) return NULL;

*end += from;
token = (char *) malloc(from + 1);

for(from = 0, to = 0; line[from]; from++) {
char c = line[from];

if(!escaped) {
if(isspace(c)) break;
else if(c == '\'' || c == '"') quote = !quote;
else if(c == '\\') escaped = 1;
else token[to++] = c;
}
else {
escaped = 0;
token[to++] = c;
}
}

token[to] = '\0';
return token;
static void process(Redis *redis, const char *prefix, const char **cmdargs, int nargs) {
int status = X_SUCCESS;
RESP *reply = redisxArrayRequest(redis, cmdargs, NULL, nargs, &status);
if(!status) printRESP(prefix, reply);
redisxDestroyRESP(reply);
}

static char ** get_args(char *line, int *n) {
char **array;
int capacity = 10;
char *tok;
int i, end;

array = (char **) malloc(capacity * sizeof(char *));

for(i = 0; (tok = get_token(line, &end)) != NULL; i++) {
if(i >= capacity) {
capacity *= 2;
array = (char **) realloc(array, capacity * sizeof(char *));
if(!array) {
fprintf(stderr, "ERROR! alloc error (%d char *): %s\n", capacity, strerror(errno));
exit(1);
}
}

array[i] = tok;
line += end;
}

*n = i;
return array;
// cppcheck-suppress constParameterCallback
static void PushProcessor(RedisClient *cl, RESP *resp, void *ptr) {
(void) cl;
(void) ptr;
printRESP("[PUSH]", resp);
}


static int interactive(Redis *redis) {
char *prompt = malloc(strlen(host) + 20);
sprintf(prompt, "%s:%d> ", host, port);
Expand All @@ -120,15 +56,18 @@ static int interactive(Redis *redis) {
char **args;
int nargs;

if(!line) continue;

if(strcmp("quit", line) == 0 || strcmp("exit", line) == 0) break;

args = get_args(line, &nargs);
poptParseArgvString(line, &nargs, (const char ***) &args);

if(args) {
if(nargs > 0) {
process(redis, (const char **) args, nargs);
process(redis, "[REPLY]", (const char **) args, nargs);
add_history(line);
}
redisxDestroyKeys(args, nargs);
free(args);
}

free(line);
Expand All @@ -140,40 +79,79 @@ static int interactive(Redis *redis) {
return X_SUCCESS;
}

static char *readScript(const char *eval) {
FILE *fp = fopen(eval, "r");
size_t filesize;
char *script;

if(!fp) {
fprintf(stderr, "ERROR! %s: %s", eval, strerror(errno));
exit(1);
}

fseek(fp, 0L, SEEK_END);
filesize = ftell(fp);

script = (char *) malloc(filesize);
x_check_alloc(script);

if(fread(script, filesize, 1, fp) < 1) {
fprintf(stderr, "ERROR! reading %s: %s", eval, strerror(errno));
exit(1);
}

fclose(fp);

return script;
}

int main(int argc, const char *argv[]) {
static const char *fn = "redis-cli";

double timeout = 0.0;
char *password = NULL;
char *user = NULL;
int askpass = 0;
int repeat = 1;
double interval = 1.0;
int dbIndex = 0;
int protocol = -1;
char *push = "y";
const char *eval = NULL;

struct poptOption options[] = { //
{NULL, 'h', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &host, 0, "Server hostname.", "<hostname>"}, //
{NULL, 'p', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &port, 0, "Server port.", "<port>"}, //
{NULL, 't', POPT_ARG_DOUBLE, &timeout, 0, "Server connection timeout in seconds (decimals allowed).", "<timeout>"}, //
{NULL, 'a', POPT_ARG_STRING, &password, 0, "Password to use when connecting to the server.", "<password>"}, //
{NULL, 'u', POPT_ARG_STRING, &user, 0, "Used to send ACL style 'AUTH username pass'. Needs -a.", "<user>"}, //
{"pass", 'a', POPT_ARG_STRING, &password, 0, "Password to use when connecting to the server.", "<password>"}, //
{"user", 0, POPT_ARG_STRING, &user, 0, "Used to send ACL style 'AUTH username pass'. Needs -a.", "<username>"}, //
{"askpass", 0, POPT_ARG_NONE, &askpass, 0, "Force user to input password with mask from STDIN." //
"If this argument is used, '-a' will be ignored.", NULL //
}, //
{NULL, 'r', POPT_ARG_INT, &repeat, 0, "Execute specified command N times.", "<repeat>"}, //
{NULL, 'i', POPT_ARG_DOUBLE | POPT_ARGFLAG_SHOW_DEFAULT, &interval, 0, "When -r is used, waits <interval> seconds per command. " //
"It is possible to specify sub-second times like -i 0.1.", "<interval>"}, //
{"db", 'n', POPT_ARG_INT, &dbIndex, 0, "Database number.", "<dbIdx>"}, //
{"resp2", '2', POPT_ARG_VAL, &protocol, 2, "Start session in RESP2 protocol mode.", NULL}, //
{"resp3", '3', POPT_ARG_VAL, &protocol, 3, "Start session in RESP3 protocol mode.", NULL}, //
"It is possible to specify sub-second times like -i 0.1.", "<interval>" //
}, //
{NULL, 'n', POPT_ARG_INT, &dbIndex, 0, "Database number.", "<db>"}, //
{NULL, '2', POPT_ARG_VAL, &protocol, 2, "Start session in RESP2 protocol mode.", NULL}, //
{NULL, '3', POPT_ARG_VAL, &protocol, 3, "Start session in RESP3 protocol mode.", NULL}, //
{"json", 0, POPT_ARG_NONE, NULL, 'j', "Output in JSON format", NULL}, //
{"show-pushes", 0, POPT_ARG_STRING, &push, 0, "Whether to print RESP3 PUSH messages. Enabled by default when STDOUT " //
"is a tty but can be overridden with --show-pushes no.", "<yes|no>" //
}, //
{"eval", 0, POPT_ARG_STRING, &push, 0, "Send an EVAL command using the Lua script at <file>.", "<file>" },

POPT_AUTOHELP POPT_TABLEEND //
};

int rc;
char **cmdargs;
char *script;
int i, nargs = 0;
Redis *redis;

poptContext optcon = poptGetContext(fn, argc, argv, options, 0);
poptSetOtherOptionHelp(optcon, "[OPTIONS] [command [args...]]");
poptSetOtherOptionHelp(optcon, "[OPTIONS] [cmd [args [arg ...]]]");

while((rc = poptGetNextOpt(optcon)) != -1) {
if(rc < -1) {
Expand All @@ -191,6 +169,25 @@ int main(int argc, const char *argv[]) {

cmdargs = (char **) poptGetArgs(optcon);

if(askpass) {
password = (char *) malloc(1024);
if(readpassphrase("Enter password: ", password, 1024, 0) == NULL) {
free(password);
password = NULL;
}
}

if(eval) {
// Insert 'EVAL <script>' in front of the command-line args.
script = readScript(eval);
cmdargs = realloc(cmdargs, nargs + 2 * sizeof(char *));
x_check_alloc(cmdargs);
memmove(cmdargs[2], cmdargs, nargs * sizeof(char *));
cmdargs[0] = "EVAL";
cmdargs[1] = script;
nargs += 2;
}

redis = redisxInit(host);

if(port <= 0) port = 6369;
Expand All @@ -202,6 +199,8 @@ int main(int argc, const char *argv[]) {
if(timeout > 0.0) redisxSetSocketTimeout(redis, (int) ceil(1000 * timeout));
if(protocol > 0) redisxSetProtocol(redis, protocol);

if(push) if(strcmp("y", push) == 0 || strcmp("yes", push) == 0) redisxSetPushProcessor(redis, PushProcessor, NULL);

xSetDebug(1);
prop_error(fn, redisxConnect(redis, 0));

Expand All @@ -217,7 +216,7 @@ int main(int argc, const char *argv[]) {
nanosleep(&sleeptime, NULL);
}

process(redis, (const char **) cmdargs, nargs);
if(nargs) process(redis, "[REPLY]", (const char **) cmdargs, nargs);
}

redisxDisconnect(redis);
Expand Down
4 changes: 0 additions & 4 deletions src/resp.c
Original file line number Diff line number Diff line change
Expand Up @@ -642,14 +642,10 @@ static XField *respMap2XField(const char *name, const RedisMapEntry *map, int n)
XStructure *s = xCreateStruct(), *nonstring = NULL;
int nNonString = 0;

printf("### map\n");

while(--n >= 0) {
const RedisMapEntry *e = &map[n];

if(redisxIsStringType(e->key)) {
printf("### + %s\n", (char *) e->key->value);

XField *fi = redisxRESP2XField((char *) e->key->value, e->value);
if(fi) {
fi->next = s->firstField;
Expand Down
2 changes: 1 addition & 1 deletion tools.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include config.mk

LDFLAGS += -L$(LIB) -lredisx -lpopt -lreadline
LDFLAGS += -L$(LIB) -lredisx -lpopt -lreadline -lbsd
LD_LIBRARY_PATH := $(LIB):$(LD_LIBRARY_PATH)

# Top level make targets...
Expand Down

0 comments on commit 0f44972

Please sign in to comment.