From 534f3cc158f0a4b582312350845841a84b62c1fa Mon Sep 17 00:00:00 2001 From: Attila Kovacs Date: Thu, 12 Dec 2024 10:41:05 +0100 Subject: [PATCH] Fixes, and add redisx-cli tool --- Makefile | 29 +++++++- README.md | 20 ++++++ build.mk | 7 +- include/redisx.h | 5 +- src/redisx-cli.c | 111 ++++++++++++++++++++++++++++ src/redisx-client.c | 4 +- src/redisx-net.c | 5 +- src/redisx-sub.c | 6 +- src/redisx-tab.c | 14 ++-- src/resp.c | 172 ++++++++++++++++++++++++++++++++++++-------- test/src/test-tab.c | 2 +- tools.mk | 9 +++ 12 files changed, 334 insertions(+), 50 deletions(-) create mode 100644 src/redisx-cli.c create mode 100644 tools.mk diff --git a/Makefile b/Makefile index 2e05788..f75bd91 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ LDFLAGS += -pthread # Build for distribution .PHONY: distro -distro: shared $(DOC_TARGETS) +distro: shared tools $(DOC_TARGETS) # Build everything... .PHONY: all @@ -48,6 +48,15 @@ shared: $(LIB)/libredisx.so .PHONY: static static: $(LIB)/libredisx.a +# Command-line tools +.PHONY: tools +ifdef STATICLINK +tools: static +else +tools: shared +endif + make -f tools.mk + # Run regression tests .PHONY: test test: @@ -92,6 +101,11 @@ $(LIB)/libredisx.so.$(SO_VERSION): $(SOURCES) # Static library $(LIB)/libredisx.a: $(OBJECTS) +# redisx-cli +$(BIN)/redisx-cli: LDFLAGS += -lredisx +$(BIN)/redisx-cli: $(OBJ)/redisx-cli.o + + README-redisx.md: README.md LINE=`sed -n '/\# /{=;q;}' $<` && tail -n +$$((LINE+2)) $< > $@ @@ -125,6 +139,7 @@ pdf: # See https://www.gnu.org/prep/standards/html_node/Directory-Variables.html prefix ?= /usr exec_prefix ?= $(prefix) +bindir ?= $(exec_prefix)/bin libdir ?= $(exec_prefix)/lib includedir ?= $(prefix)/include datarootdir ?= $(prefix)/share @@ -138,7 +153,7 @@ INSTALL_PROGRAM ?= install INSTALL_DATA ?= install -m 644 .PHONY: install -install: install-libs install-headers install-html +install: install-libs install-bin install-headers install-html .PHONY: install-libs install-libs: @@ -150,6 +165,16 @@ else @echo "WARNING! Skipping libs install: needs 'shared' and/or 'static'" endif +.PHONY: install-bin +install-bin: +ifneq ($(wildcard $(BIN)/*),) + @echo "installing executables to $(DESTDIR)$(bindir)" + install -d $(DESTDIR)$(bindir) + $(INSTALL_PROGRAM) -D $(BIN)/* $(DESTDIR)$(bindir)/ +else + @echo "WARNING! Skipping bins install: needs 'tools'" +endif + .PHONY: install-headers install-headers: @echo "installing headers to $(DESTDIR)$(includedir)" diff --git a/README.md b/README.md index 78c4be0..fa8c9a8 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Last Updated: 10 December 2024 - [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Building RedisX](#building-redisx) + - [Command-line interface (`redisx-cli`)](#redisx-cli) - [Linking your application against RedisX](#linking) - [Managing Redis server connections](#managing-redis-server-connections) - [Simple Redis queries](#simple-redis-queries) @@ -144,6 +145,25 @@ Or, to stage the installation (to `/usr`) under a 'build root': ----------------------------------------------------------------------------- + +## Command-line interface (`redisx-cli`) + +The __RedisX__ library provides its own command-line tool, called `redisx-cli`. It works very similar to `redis-cli`, +except that it supports a subset of the options (so far...). + +```bash + $ redisx-cli ping "Hello World" +``` +will print: + +```bash + "Hello world!" +``` + +provided it successfully connected to the Redis / Valkey server. (Otherwise it will print an error and a trace). + +----------------------------------------------------------------------------- + ## Linking your application against RedisX diff --git a/build.mk b/build.mk index 28a196d..01fc676 100644 --- a/build.mk +++ b/build.mk @@ -23,7 +23,12 @@ $(LIB)/lib%.so: $(LIB)/%.a: @make $(LIB) ar -rc $@ $^ - ranlib $@ + ranlib $@ + +# Simple binaries +$(BIN)/%: $(OBJ)/%.o + @make $(BIN) + $(CC) -o $@ $^ $(LDFLAGS) # Create sub-directories for build targets dep $(OBJ) $(LIB) $(BIN) apidoc: diff --git a/include/redisx.h b/include/redisx.h index ff8829a..d113ac8 100644 --- a/include/redisx.h +++ b/include/redisx.h @@ -169,7 +169,7 @@ enum redisx_protocol { */ typedef struct RESP { enum resp_type type; ///< RESP type; RESP_ARRAY, RESP_INT ... - int n; ///< Either the integer value of a RESP_INT or a RESP3_BOOLEAN response, or the + int n; ///< Either the integer value of a RESP_INT or a RESP3_BOOLEAN response, or the ///< dimension of the value field. void *value; ///< Pointer to text (char *) content or to an array of components ///< (RESP**) or (RedisMapEntry *), or else a pointer to a `double`, depending @@ -436,7 +436,8 @@ boolean redisxIsEqualRESP(const RESP *a, const RESP *b); int redisxSplitText(RESP *resp, char **text); XField *redisxRESP2XField(const char *name, const RESP *resp); char *redisxRESP2JSON(const char *name, const RESP *resp); -int redisxPrintRESP(const char *name, const RESP *resp); +int redisxPrintRESP(const RESP *resp); +int redisxPrintJSON(const char *name, const RESP *resp); RedisMapEntry *redisxGetMapEntry(const RESP *map, const RESP *key); RedisMapEntry *redisxGetKeywordEntry(const RESP *map, const char *key); diff --git a/src/redisx-cli.c b/src/redisx-cli.c new file mode 100644 index 0000000..f80a51e --- /dev/null +++ b/src/redisx-cli.c @@ -0,0 +1,111 @@ +/** + * @date Created on Dec 11, 2024 + * @author Attila Kovacs + */ + +#include +#include +#include +#include +#include + +#include "redisx.h" + +#define FORMAT_JSON 1 +#define FORMAT_RAW 2 + +int main(int argc, const char *argv[]) { + + char *host = "127.0.0.1"; + int port = 0; + double timeout = 0.0; + char *password = NULL; + char *user = NULL; + int repeat = 1; + double interval = 1.0; + int dbIndex = 0; + int protocol = -1; + int format = 0; + + struct poptOption options[] = { // + {"host", 'h', POPT_ARG_STRING, &host, 0, "Server hostname (default: 127.0.0.1).", NULL}, // + {"port", 'p', POPT_ARG_INT, &port, 0, "Server port (default: 6379).", NULL}, // + {"timeout", 't', POPT_ARG_DOUBLE, &timeout, 0, "Server connection timeout in seconds (decimals allowed).", NULL}, // + {"pass", 'a', POPT_ARG_STRING, &password, 0, "Password to use when connecting to the server.", NULL}, // + {"user", 'u', POPT_ARG_STRING, &user, 0, "Used to send ACL style 'AUTH username pass'. Needs -a.", NULL}, // + {"repeat", 'r', POPT_ARG_INT, &repeat, 0, "Execute specified command N times.", NULL}, // + {"interval", 'i', POPT_ARG_DOUBLE, &interval, 0, "When -r is used, waits seconds per command. " // + "It is possible to specify sub-second times like -i 0.1.", NULL}, // + {"db", 'n', POPT_ARG_INT, &dbIndex, 0, "Database number.", NULL}, // + {"resp2", '2', POPT_ARG_NONE, NULL, 2, "Start session in RESP2 protocol mode.", NULL}, // + {"resp3", '3', POPT_ARG_NONE, NULL, 3, "Start session in RESP3 protocol mode.", NULL}, // + {"json", 0, POPT_ARG_NONE, NULL, 'j', "Print raw strings", NULL}, // + POPT_AUTOHELP POPT_TABLEEND // + }; + + int rc; + char **cmdargs; + int i, nargs = 0; + Redis *redis; + + poptContext optcon = poptGetContext("redisx-cli", argc, argv, options, 0); + + while((rc = poptGetNextOpt(optcon)) != -1) { + if(rc < -1) { + fprintf(stderr, "ERROR! Bad syntax. Try running with --help to see command-line options.\n"); + exit(1); + } + + switch(rc) { + case '2': protocol = 2; break; + case '3': protocol = 3; break; + case 'j': format = FORMAT_JSON; break; + case 'r': format = FORMAT_RAW; break; + } + } + + cmdargs = (char **) poptGetArgs(optcon); + + if(!cmdargs) { + poptPrintHelp(optcon, stdout, 0); + return 1; + } + + while(cmdargs[nargs]) nargs++; + + redis = redisxInit(host); + if(port) redisxSetPort(redis, port); + if(user) redisxSetUser(redis, user); + if(password) redisxSetPassword(redis, password); + if(dbIndex > 0) redisxSelectDB(redis, dbIndex); + if(timeout > 0.0) redisxSetSocketTimeout(redis, (int) ceil(1000 * timeout)); + if(protocol > 0) redisxSetProtocol(redis, protocol); + + redisxConnect(redis, 0); + xSetDebug(1); + + for(i = 0; i < repeat; i++) { + int status = X_SUCCESS; + RESP *reply; + + if(i > 0 && interval > 0.0) { + struct timespec sleeptime; + sleeptime.tv_sec = (int) interval; + sleeptime.tv_nsec = 1000000000 * (interval - sleeptime.tv_sec); + nanosleep(&sleeptime, NULL); + } + + reply = redisxArrayRequest(redis, cmdargs, NULL, nargs, &status); + + if(!status) { + if(format == FORMAT_JSON) redisxPrintJSON("REPLY", reply); + else redisxPrintRESP(reply); + } + + redisxDestroyRESP(reply); + } + + poptFreeContext(optcon); + + return 0; +} diff --git a/src/redisx-client.c b/src/redisx-client.c index dca5bac..46ab105 100644 --- a/src/redisx-client.c +++ b/src/redisx-client.c @@ -942,7 +942,7 @@ RESP *redisxReadReplyAsync(RedisClient *cl) { } - for(i=0; in; i++) { + for(i = 0; i < resp->n; i++) { RESP *r = redisxReadReplyAsync(cl); // Always read RESP even if we don't have storage for it... if(component) component[i] = r; else redisxDestroyRESP(r); @@ -970,7 +970,7 @@ RESP *redisxReadReplyAsync(RedisClient *cl) { dict = (RedisMapEntry *) calloc(resp->n, sizeof(RedisMapEntry)); x_check_alloc(dict); - for(i=0; in; i++) { + for(i = 0; i < resp->n; i++) { RedisMapEntry *e = &dict[i]; e->key = redisxReadReplyAsync(cl); e->value = redisxReadReplyAsync(cl); diff --git a/src/redisx-net.c b/src/redisx-net.c index 5270c54..c3ea65f 100644 --- a/src/redisx-net.c +++ b/src/redisx-net.c @@ -557,7 +557,6 @@ static void rShutdownAsync() { while(l != NULL) { ServerLink *next = l->next; redisxDestroy(l->redis); - free(l); l = next; } @@ -1127,7 +1126,7 @@ boolean redisxIsConnected(Redis *redis) { * */ void *RedisPipelineListener(void *pRedis) { - static int counter, lastError; + static long counter, lastError; Redis *redis = (Redis *) pRedis; RedisPrivate *p; @@ -1187,7 +1186,7 @@ void *RedisPipelineListener(void *pRedis) { } // <-- End of listener loop... - xvprintf("Redis-X> Stopped processing pipeline responses (%d processed)...\n", counter); + xvprintf("Redis-X> Stopped processing pipeline responses (%ld processed)...\n", counter); rConfigLock(redis); // If we are the current listener thread, then mark the listener as disabled. diff --git a/src/redisx-sub.c b/src/redisx-sub.c index 98bdf98..72bfdcd 100644 --- a/src/redisx-sub.c +++ b/src/redisx-sub.c @@ -526,7 +526,7 @@ static void rNotifyConsumers(Redis *redis, char *pattern, char *channel, char *m * */ void *RedisSubscriptionListener(void *pRedis) { - static int counter, lastError; + static long counter, lastError; Redis *redis = (Redis *) pRedis; RedisPrivate *p; @@ -585,7 +585,7 @@ void *RedisSubscriptionListener(void *pRedis) { // Check for NULL component (all except last -- the payload -- which may be NULL) for(i=reply->n-1; --i >= 0; ) if(component[i] == NULL) { - fprintf(stderr, "WARNING! Redis-X : subscriber NULL in component %d.\n", (i+1)); + fprintf(stderr, "WARNING! Redis-X : subscriber NULL in component %d\n", (i+1)); continue; } @@ -625,7 +625,7 @@ void *RedisSubscriptionListener(void *pRedis) { rConfigUnlock(redis); } - xvprintf("Redis-X> Stopped processing subscriptions (%d processed)...\n", counter); + xvprintf("Redis-X> Stopped processing subscriptions (%ld processed)...\n", counter); redisxDestroyRESP(reply); diff --git a/src/redisx-tab.c b/src/redisx-tab.c index a7bd5bc..8669c77 100644 --- a/src/redisx-tab.c +++ b/src/redisx-tab.c @@ -87,7 +87,7 @@ RedisEntry *redisxGetTable(Redis *redis, const char *table, int *n) { else { int i; - for(i=0; in; i+=2) { + for(i = 0; i < reply->n; i += 2) { RedisEntry *e = &entries[i]; RedisMapEntry *component = &dict[i]; e->key = component->key->value; @@ -423,7 +423,7 @@ char **redisxGetKeys(Redis *redis, const char *table, int *n) { if(names == NULL) fprintf(stderr, "WARNING! Redis-X : alloc pointers for %d keys: %s\n", reply->n, strerror(errno)); else { int i; - for(i=0; in; i++) { + for(i = 0; i < reply->n; i++) { RESP **component = (RESP **) reply->value; names[i] = (char *) component[i]->value; @@ -527,7 +527,8 @@ char **redisxScanKeys(Redis *redis, const char *pattern, int *n, int *status) { char **pCursor; char **names = NULL; char countArg[20]; - int args = 0, i, j, capacity = SCAN_INITIAL_STORE_CAPACITY; + int capacity = SCAN_INITIAL_STORE_CAPACITY; + int args = 0, i, j; if(n == NULL) { x_error(X_NULL, EINVAL, fn, "parameter 'n' is NULL"); @@ -633,13 +634,13 @@ char **redisxScanKeys(Redis *redis, const char *pattern, int *n, int *status) { qsort(names, *n, sizeof(char *), compare_strings); // Remove duplicates - for(i=*n; --i > 0; ) if(!strcmp(names[i], names[i-1])) { + for(i = *n; --i > 0; ) if(!strcmp(names[i], names[i-1])) { free(names[i]); names[i] = NULL; } // Compact... - for(i=0, j=0; i < *n; i++) if(names[i]) { + for(i = 0, j = 0; i < *n; i++) if(names[i]) { if(i != j) names[j] = names[i]; j++; } @@ -692,7 +693,8 @@ RedisEntry *redisxScanTable(Redis *redis, const char *table, const char *pattern RedisEntry *entries = NULL; char *cmd[7] = {NULL}, countArg[20]; char **pCursor; - int args= 0, i, j, capacity = SCAN_INITIAL_STORE_CAPACITY; + int capacity = SCAN_INITIAL_STORE_CAPACITY; + int args= 0, i, j; if(n == NULL) { x_error(X_NULL, EINVAL, fn, "parameter 'n' is NULL"); diff --git a/src/resp.c b/src/resp.c index be0517c..133cc5c 100644 --- a/src/resp.c +++ b/src/resp.c @@ -427,7 +427,7 @@ int redisxAppendRESP(RESP *resp, RESP *part) { extend = (char *) realloc(resp->value, (resp->n + part->n) * eSize); if(!extend) { free(old); - return x_error(X_FAILURE, errno, fn, "alloc RESP array (%d components)", resp->n + part->n); + return x_error(X_FAILURE, errno, fn, "alloc RESP array (%ld components)", resp->n + part->n); } memcpy(extend + resp->n * eSize, part->value, part->n * eSize); @@ -472,8 +472,8 @@ boolean redisxIsEqualRESP(const RESP *a, const RESP *b) { * @sa redisxGetKeywordEntry() */ RedisMapEntry *redisxGetMapEntry(const RESP *map, const RESP *key) { - int i; RedisMapEntry *entries; + int i; if(!key) return NULL; if(!redisxIsMapType(map)) return NULL; @@ -510,8 +510,8 @@ RedisMapEntry *redisxGetMapEntry(const RESP *map, const RESP *key) { * @sa redisxGetMapEntry() */ RedisMapEntry *redisxGetKeywordEntry(const RESP *map, const char *key) { - int i; RedisMapEntry *entries; + int i; if(!key) return NULL; if(!redisxIsMapType(map)) return NULL; @@ -566,66 +566,74 @@ static void rSetRESPType(XField *f, char type) { } static XField *respArrayToXField(const char *name, const RESP **component, int n) { - static const char *fn = "respArrayToXField"; - XField *f; enum resp_type type = RESP3_NULL; + XType eType; + XField *f; + int i; - if(n < 0) return NULL; + if(n <= 0) return NULL; for(i = 0; i < n; i++) { if(i == 0) type = component[i]->type; else if(component[i]->type != type) break; } - if(i < n) { + eType = resp2xType(type); + + if(i < n || eType == X_FIELD) { // -------------------------------------------------------- // Heterogeneous array... XField *array = (XField *) calloc(n, sizeof(XField)); if(!array) { - x_error(0, errno, fn, "alloc error (%d XField)", n); + x_error(0, errno, "respArrayToXField", "alloc error (%d XField)", n); return NULL; } - f = xCreateMixed1DField(name, n, array); - for(i = 0; i < n; i++) { - XField *e = redisxRESP2XField(array[i].name, component[i]); + XField *e; + char idx[20]; + sprintf(idx, ".%d", (i + 1)); + e = redisxRESP2XField(idx, component[i]); if(e) { array[i] = *e; free(e); } } + + f = xCreateMixed1DField(name, n, array); } else { // -------------------------------------------------------- // Homogeneous array... - XType eType = resp2xType(type); char *array; size_t eSize; - if(eType == X_UNKNOWN) return xCreateMixed1DField(name, 0, NULL); + if(eType == X_UNKNOWN) eType = X_STRING; eSize = xElementSizeOf(eType); - array = (char *) calloc(1, n * eSize); - - f = xCreate1DField(name, eType, n, array); - rSetRESPType(f, type); - if(!array) return x_trace_null(fn, "field array"); + array = (char *) calloc(n, eSize); for(i = 0; i < n; i++) { - XField *e = redisxRESP2XField("", component[i]); + XField *e; + char idx[20]; + + sprintf(idx, ".%d", (i + 1)); + e = redisxRESP2XField(idx, component[i]); if(e) { - memcpy(&array[i * eSize], e, sizeof(XField)); + memcpy(&array[i * eSize], e->value, eSize); free(e); } } + + f = xCreate1DField(name, eType, n, array); } + rSetRESPType(f, type); return f; } @@ -634,10 +642,14 @@ 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; @@ -654,7 +666,7 @@ static XField *respMap2XField(const char *name, const RedisMapEntry *map, int n) sprintf(idx, ".%d", ++nNonString); if(!nonstring) nonstring = xCreateStruct(); - xSetSubstruct(nonstring, xStringCopyOf(idx), sub); + xSetSubstruct(nonstring, idx, sub); } } @@ -687,13 +699,11 @@ static XField *respMap2XField(const char *name, const RedisMapEntry *map, int n) * @sa redisxRESP2JSON() */ XField *redisxRESP2XField(const char *name, const RESP *resp) { - static const char *fn = "resp2XField"; - - errno = 0; + if(!resp) return NULL; switch(resp->type) { case RESP3_NULL: - return xCreateStringField(name, NULL); + return xCreateScalarField(name, X_UNKNOWN, NULL); case RESP3_BOOLEAN: return xCreateBooleanField(name, resp->n); @@ -719,7 +729,7 @@ XField *redisxRESP2XField(const char *name, const RESP *resp) { case RESP3_SET: case RESP3_PUSH: { XField *f = respArrayToXField(name, (const RESP **) resp->value, resp->n); - if(!f) return x_trace_null(fn, NULL); + if(!f) return NULL; rSetRESPType(f, resp->type); return f; } @@ -727,14 +737,14 @@ XField *redisxRESP2XField(const char *name, const RESP *resp) { case RESP3_MAP: case RESP3_ATTRIBUTE: { XField *f = respMap2XField(name, (const RedisMapEntry *) resp->value, resp->n); - if(!f) return x_trace_null(fn, NULL); + if(!f) return NULL; rSetRESPType(f, resp->type); return f; } } - return NULL; + return xCreateStringField(name, ""); } /** @@ -746,7 +756,7 @@ XField *redisxRESP2XField(const char *name, const RESP *resp) { * set to indicate the type of error). * * @sa redisxRESP2XField() - * @sa redisxPrintRESP() + * @sa redisxPrintJSON() */ char *redisxRESP2JSON(const char *name, const RESP *resp) { return xjsonFieldToString(redisxRESP2XField(name, resp)); @@ -759,9 +769,10 @@ char *redisxRESP2JSON(const char *name, const RESP *resp) { * @param resp The RESP data to print * @return 0 * + * @sa redisxPrintRESP() * @sa redisxRESP2JSON() */ -int redisxPrintRESP(const char *name, const RESP *resp) { +int redisxPrintJSON(const char *name, const RESP *resp) { char *json = redisxRESP2JSON(name, resp); if(json) { @@ -773,3 +784,104 @@ int redisxPrintRESP(const char *name, const RESP *resp) { return X_SUCCESS; } +static void rNewLine(int ident) { + putchar('\n'); + while(--ident >= 0) putchar(' '); +} + +static int rIndexWidth(int count) { + return 1 + (int) floor(log10(count)); +} + +static int rPrintRESP(int ident, const RESP *resp) { + + if(!resp) { + printf("(null)"); + return X_SUCCESS; + } + + if(resp->type == RESP3_NULL) { + printf("null"); + return X_SUCCESS; + } + + if(redisxIsStringType(resp)) { + int quote = !(resp->type == RESP_SIMPLE_STRING || resp->type == RESP_ERROR || resp->type == RESP3_BIG_NUMBER); + + if(!resp->value) printf("(nil)"); + else { + if(quote) putchar('"'); + printf("%s", ((char *) resp->value)); + if(quote) putchar('"'); + } + return X_SUCCESS; + } + + switch(resp->type) { + case RESP_INT: + printf("(integer) %d", resp->n); + return X_SUCCESS; + + case RESP3_DOUBLE: + printf("(double) %g", *(double *) resp->value); + return X_SUCCESS; + + case RESP_ARRAY: + case RESP3_SET: + case RESP3_PUSH: { + RESP **component = (RESP **) resp->value; + + if(!resp->value) printf("(empty array)"); + else { + int i; + for(i = 0; i < resp->n; i++) { + rNewLine(ident); + printf("%*d) ", rIndexWidth(resp->n), (i + 1)); + rPrintRESP(ident + 2, component[i]); + } + } + return X_SUCCESS; + } + + case RESP3_MAP: + case RESP3_ATTRIBUTE: { + const RedisMapEntry *component = (RedisMapEntry *) resp->value; + + if(!resp->value) printf("(empty map)"); + else { + int i; + for(i = 0; i < resp->n; i++) { + rNewLine(ident); + printf("%*d# ", rIndexWidth(resp->n), (i + 1)); + rPrintRESP(ident + 2, component[i].key); + printf(" => "); + rPrintRESP(ident + 2, component[i].value); + } + } + return X_SUCCESS; + } + + default: + if(!resp->value) printf("(nil)"); + else printf(" type '%c'", resp->type); + return X_FAILURE; + } + + +} + +/** + * Prints a RESP to the standard output, in a format that is similar to the one used by the standard + * redis-cli tool. + * + * @param resp Pointer to a RESP data structure. (It may be NULL). + * @return X_SUCCESS (0) if successful or else X_FAILURE if there was an error. + * + * @sa redisxPrintJSON() + */ +int redisxPrintRESP(const RESP *resp) { + int status = rPrintRESP(0, resp); + printf("\n"); + return status; +} + diff --git a/test/src/test-tab.c b/test/src/test-tab.c index 2f9702e..ace965e 100644 --- a/test/src/test-tab.c +++ b/test/src/test-tab.c @@ -41,7 +41,7 @@ int main() { return 1; } - redisxPrintRESP("get value", resp); + redisxPrintJSON("get value", resp); redisxCheckDestroyRESP(resp, RESP_BULK_STRING, 1); if(strcmp("2", (char *) resp->value) != 0) { diff --git a/tools.mk b/tools.mk new file mode 100644 index 0000000..4f5eb5b --- /dev/null +++ b/tools.mk @@ -0,0 +1,9 @@ +include config.mk + +LDFLAGS += -L$(LIB) -lredisx -lpopt +LD_LIBRARY_PATH := $(LIB):$(LD_LIBRARY_PATH) + +# Top level make targets... +all: $(BIN)/redisx-cli + +include build.mk \ No newline at end of file