diff --git a/cmd/ledis-cli/linenoise.c b/cmd/ledis-cli/linenoise.c new file mode 100644 index 0000000..aef5cdd --- /dev/null +++ b/cmd/ledis-cli/linenoise.c @@ -0,0 +1,1090 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2013, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * CHA (Cursor Horizontal Absolute) + * Sequence: ESC [ n G + * Effect: moves cursor to column n + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward of n chars + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * The following are used to clear the screen: ESC [ H ESC [ 2 J + * This is actually composed of two sequences: + * + * cursorhome + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED2 (Clear entire screen) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static linenoiseCompletionCallback *completionCallback = NULL; + +static struct termios orig_termios; /* In order to restore at exit.*/ +static int rawmode = 0; /* For atexit() function to check if restore is needed*/ +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int atexit_registered = 0; /* Register atexit just 1 time. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(int ifd, int ofd) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nread, nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + nread = read(ls->ifd,&c,1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\x1b[0G"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,buf,len); + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int old_rows = l->maxrows; + int fd = l->ofd, j; + struct abuf ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\x1b[0G\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\x1b[0G\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,l->buf,l->len); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\x1b[0G"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + lndebug("set col %d", 1+((plen+(int)l->pos) % (int)l->cols)); + snprintf(seq,64,"\x1b[%dG", 1+((plen+(int)l->pos) % (int)l->cols)); + abAppend(&ab,seq,strlen(seq)); + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { + /* Avoid a full update of the line in the + * trivial case. */ + if (write(l->ofd,&c,1) == -1) return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.ifd = stdin_fd; + l.ofd = stdout_fd; + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(stdin_fd, stdout_fd); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + if (write(l.ofd,prompt,l.plen) == -1) return -1; + while(1) { + char c; + int nread; + char seq[3]; + + nread = read(l.ifd,&c,1); + if (nread <= 0) return l.len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + c = completeLine(&l); + /* Return on errors */ + if (c < 0) return l.len; + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or of the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history_len--; + free(history[history_len]); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd,seq,1) == -1) break; + if (read(l.ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + } + return l.len; +} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\x1b[0G"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function calls the line editing function linenoiseEdit() using + * the STDIN file descriptor set in raw mode. */ +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { + int count; + + if (buflen == 0) { + errno = EINVAL; + return -1; + } + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. */ + if (fgets(buf, buflen, stdin) == NULL) return -1; + count = strlen(buf); + if (count && buf[count-1] == '\n') { + count--; + buf[count] = '\0'; + } + } else { + /* Interactive editing. */ + if (enableRawMode(STDIN_FILENO) == -1) return -1; + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + disableRawMode(STDIN_FILENO); + printf("\n"); + } + return count; +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + int count; + + if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); + } else { + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + if (count == -1) return NULL; + return strdup(buf); + } +} + +/* ================================ History ================================= */ + +/* Free the history, but does not reset it. Only used when we have to + * exit() to avoid memory leaks are reported by valgrind & co. */ +static void freeHistory(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + } +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + FILE *fp = fopen(filename,"w"); + int j; + + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} diff --git a/cmd/ledis-cli/linenoise.go b/cmd/ledis-cli/linenoise.go new file mode 100644 index 0000000..bd591d7 --- /dev/null +++ b/cmd/ledis-cli/linenoise.go @@ -0,0 +1,43 @@ +package main + +//#include +//#include "linenoise.h" +import "C" + +import ( + "errors" + "unsafe" +) + +func line(prompt string) (string, error) { + promptCString := C.CString(prompt) + resultCString := C.linenoise(promptCString) + C.free(unsafe.Pointer(promptCString)) + defer C.free(unsafe.Pointer(resultCString)) + + if resultCString == nil { + return "", errors.New("quited by a signal") + } + + result := C.GoString(resultCString) + + return result, nil +} + +func addHistory(line string) error { + lineCString := C.CString(line) + res := C.linenoiseHistoryAdd(lineCString) + C.free(unsafe.Pointer(lineCString)) + if res != 1 { + return errors.New("Could not add line to history.") + } + return nil +} + +func setHistoryCapacity(capacity int) error { + res := C.linenoiseHistorySetMaxLen(C.int(capacity)) + if res != 1 { + return errors.New("Could not set history max len.") + } + return nil +} diff --git a/cmd/ledis-cli/linenoise.h b/cmd/ledis-cli/linenoise.h new file mode 100644 index 0000000..e22ebd3 --- /dev/null +++ b/cmd/ledis-cli/linenoise.h @@ -0,0 +1,66 @@ +/* linenoise.h -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ diff --git a/cmd/ledis-cli/main.go b/cmd/ledis-cli/main.go index 586c1ab..4149055 100644 --- a/cmd/ledis-cli/main.go +++ b/cmd/ledis-cli/main.go @@ -1,11 +1,10 @@ package main import ( - "bufio" "flag" "fmt" "github.com/siddontang/ledisdb/client/go/ledis" - "os" + "regexp" "strings" ) @@ -27,20 +26,26 @@ func main() { c := ledis.NewClient(cfg) - reader := bufio.NewReader(os.Stdin) + setHistoryCapacity(100) + + reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`) for { - fmt.Printf("ledis %s > ", cfg.Addr) + cmd, err := line(fmt.Sprintf("%s> ", cfg.Addr)) + if err != nil { + fmt.Printf("%s\n", err.Error()) + return + } - cmd, _ := reader.ReadString('\n') - - cmds := strings.Fields(cmd) + cmds := reg.FindAllString(cmd, -1) if len(cmds) == 0 { continue } else { + addHistory(cmd) + args := make([]interface{}, len(cmds[1:])) for i := range args { - args[i] = strings.Trim(string(cmds[1+i]), "\"") + args[i] = strings.Trim(string(cmds[1+i]), "\"'") } r, err := c.Do(cmds[0], args...) if err != nil { diff --git a/cmd/ledis-repair/main.go b/cmd/ledis-repair/main.go index 44ff7a5..3633ab2 100644 --- a/cmd/ledis-repair/main.go +++ b/cmd/ledis-repair/main.go @@ -9,7 +9,7 @@ import ( "path" ) -var fileName = flag.String("config", "/etc/ledis.config", "ledisdb config file") +var fileName = flag.String("config", "/etc/ledis.json", "ledisdb config file") func main() { flag.Parse() diff --git a/cmd/ledis-server/main.go b/cmd/ledis-server/main.go index 8513559..84c4442 100644 --- a/cmd/ledis-server/main.go +++ b/cmd/ledis-server/main.go @@ -17,18 +17,21 @@ func main() { flag.Parse() if len(*configFile) == 0 { - panic("must use a config file") + println("must use a config file") + return } cfg, err := server.NewConfigWithFile(*configFile) if err != nil { - panic(err) + println(err.Error()) + return } var app *server.App app, err = server.NewApp(cfg) if err != nil { - panic(err) + println(err.Error()) + return } sc := make(chan os.Signal, 1) diff --git a/doc/commands.md b/doc/commands.md index 793ec23..794e2e7 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -2,13 +2,13 @@ ledisdb use redis protocol called RESP(REdis Serialization Protocol), [here](http://redis.io/topics/protocol). -ledisdb all commands return RESP fomrat and it will use int64 instead of RESP integer, string instead of RESP simple string, bulk string instead of RESP bulk string, and array instead of RESP arrays below. +ledisdb all commands return RESP fomrat and it will use `int64` instead of `RESP integer`, `string` instead of `RESP simple string`, `bulk string` instead of `RESP bulk string`, and `array` instead of `RESP arrays` below. ## KV -### decr key +### DECR key Decrements the number stored at key by one. If the key does not exist, it is set to 0 before decrementing. -An error returns if the value for the key is a wrong type that can not be represented as a signed 64 bit integer. +An error returns if the value for the key is a wrong type that can not be represented as a `signed 64 bit integer`. **Return value** @@ -17,19 +17,20 @@ int64: the value of key after the decrement **Examples** ``` -ledis> decr mykey +ledis> DECR mykey (integer) -1 -ledis> decr mykey +ledis> DECR mykey (integer) -2 ledis> SET mykey "234293482390480948029348230948" OK -ledis> decr mykey +ledis> DECR mykey ERR strconv.ParseInt: parsing "234293482390480948029348230948“: invalid syntax ``` -### decrby key decrement -Decrements the number stored at key by decrement. like decr. +### DECRBY key decrement + +Decrements the number stored at key by decrement. like `DECR`. **Return value** @@ -38,13 +39,13 @@ int64: the value of key after the decrement **Examples** ``` -ledis> set mykey “10“ +ledis> SET mykey “10“ OK -ledis> decrby mykey “5“ +ledis> DECRBY mykey “5“ (integer) 5 ``` -### del key [key ...] +### DEL key [key ...] Removes the specified keys. @@ -55,38 +56,39 @@ int64: The number of input keys **Examples** ``` -ledis> set key1 "hello" +ledis> SET key1 "hello" OK -ledis> set key2 "world" +ledis> SET key2 "world" OK -ledis> del key1 key2 +ledis> DEL key1 key2 (integer) 2 ``` -### exists key +### EXISTS key Returns if key exists **Return value** int64, specifically: + - 1 if the key exists. - 0 if the key does not exists. **Examples** ``` -ledis> set key1 "hello" +ledis> SET key1 "hello" OK -ledis> exists key1 +ledis> EXISTS key1 (integer) 1 -ledis> exists key2 +ledis> EXISTS key2 (integer) 0 ``` -### get key +### GET key -Get the value of key. If the key does not exists, it returns nil value. +Get the value of key. If the key does not exists, it returns `nil` value. **Return value** @@ -96,15 +98,15 @@ bulk: the value of key, or nil when key does not exist. **Examples** ``` -ledis> get nonexisting +ledis> GET nonexisting (nil) -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> get mykey +ledis> GET mykey "hello" ``` -### getset key value +### GETSET key value Atomically sets key to value and returns the old value stored at key. @@ -115,17 +117,17 @@ bulk: the old value stored at key, or nil when key did not exists. **Examples** ``` -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> getset mykey "world" +ledis> GETSET mykey "world" "hello" -ledis> get mykey +ledis> GET mykey "world" ``` -### incr key +### INCR key -Increments the number stored at key by one. If the key does not exists, it is set to 0 before incrementing. +Increments the number stored at key by one. If the key does not exists, it is SET to `0` before incrementing. **Return value** @@ -134,17 +136,17 @@ int64: the value of key after the increment **Examples** ``` -ledis> set mykey "10" +ledis> SET mykey "10" OK -ledis> incr mykey +ledis> INCR mykey (integer) 11 -ledis> get mykey +ledis> GET mykey "11" ``` -### incrby key increment +### INCRBY key increment -Increments the number stored at key by increment. If the key does not exists, it is set to 0 before incrementing. +Increments the number stored at key by increment. If the key does not exists, it is SET to `0` before incrementing. **Return value** @@ -153,15 +155,15 @@ int64: the value of key after the increment **Examples** ``` -ledis> set mykey "10" +ledis> SET mykey "10" OK -ledis> incrby mykey 5 +ledis> INCRBY mykey 5 (integer) 15 ``` -### mget key [key ...] +### MGET key [key ...] -Returns the values of all specified keys. If the key does not exists, a nil will return. +Returns the values of all specified keys. If the key does not exists, a `nil` will return. **Return value** @@ -170,17 +172,17 @@ array: list of values at the specified keys **Examples** ``` -ledis> set key1 "hello" +ledis> SET key1 "hello" OK -ledis> set key2 "world" +ledis> SET key2 "world" OK -ledis> mget key1 key2 nonexisting +ledis> MGET key1 key2 nonexisting 1) "hello" 2) "world" 3) (nil) ``` -### mset key value [key value ...] +### MSET key value [key value ...] Sets the given keys to their respective values. @@ -191,15 +193,15 @@ string: always OK **Examples** ``` -ledis> mset key1 "hello" key2 "world" +ledis> MSET key1 "hello" key2 "world" OK -ledis> get key1 +ledis> GET key1 "hello" -ledis> get key2 +ledis> GET key2 "world" ``` -### set key value +### SET key value Set key to the value. @@ -210,13 +212,13 @@ string: OK **Examples** ``` -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> get mykey +ledis> GET mykey "hello" ``` -### setnx key value +### SETNX key value Set key to the value if key does not exist. If key already holds a value, no operation is performed. @@ -224,21 +226,21 @@ Set key to the value if key does not exist. If key already holds a value, no ope int64: -- 1 if the key was set -- 0 if the key was not set +- 1 if the key was SET +- 0 if the key was not SET **Examples** ``` -ledis> setnx mykey "hello" +ledis> SETNX mykey "hello" (integer) 1 -ledis> setnx mykey "world" +ledis> SETNX mykey "world" (integer) 0 -ledis> get mykey +ledis> GET mykey "hello" ``` -### expire key seconds +### EXPIRE key seconds Set a timeout on key. After the timeout has expired, the key will be deleted. @@ -252,19 +254,19 @@ int64: **Examples** ``` -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> expire mykey 60 +ledis> EXPIRE mykey 60 (integer) 1 -ledis> expire mykey 60 +ledis> EXPIRE mykey 60 (integer) 1 -ledis> ttl mykey +ledis> TTL mykey (integer) 58 -ledis> persist mykey +ledis> PERSIST mykey (integer) 1 ``` -### expireat key timestamp +### EXPIREAT key timestamp Set an expired unix timestamp on key. @@ -278,15 +280,15 @@ int64: **Examples** ``` -ledis> set mykey "Hello" +ledis> SET mykey "Hello" OK -ledis> expireat mykey 1293840000 +ledis> EXPIREAT mykey 1293840000 (integer) 1 -ledis> exists mykey +ledis> EXISTS mykey (integer) 0 ``` -### ttl key +### TTL key Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, -1 returns. @@ -297,15 +299,15 @@ int64: TTL in seconds **Examples** ``` -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> expire mykey 10 +ledis> EXPIRE mykey 10 (integer) 1 -ledis> ttl mykey +ledis> TTL mykey (integer) 8 ``` -### persist key +### PERSIST key Remove the existing timeout on key @@ -319,21 +321,22 @@ int64: **Examples** ``` -ledis> set mykey "hello" +ledis> SET mykey "hello" OK -ledis> expire mykey 60 +ledis> EXPIRE mykey 60 (integer) 1 -ledis> ttl mykey +ledis> TTL mykey (integer) 57 -ledis> persist mykey +ledis> PERSIST mykey (integer) 1 -ledis> ttl mykey +ledis> TTL mykey (integer) -1 ``` + ## Hash -### hdel key field [field ...] +### HDEL key field [field ...] Removes the specified fiedls from the hash stored at key. @@ -344,13 +347,13 @@ int64: the number of fields that were removed from the hash. **Examples** ``` -ledis> hset myhash field1 "foo" +ledis> HSET myhash field1 "foo" (integer) 1 -ledis> hdel myhash field1 field2 +ledis> HDEL myhash field1 field2 (integer) 1 ``` -### hexists key field +### HEXISTS key field Returns if field is an existing field in the hash stored at key. @@ -364,34 +367,34 @@ int64: **Examples** ``` -ledis> hset myhash field1 "foo" +ledis> HSET myhash field1 "foo" (integer) 1 -ledis> hexists myhash field1 +ledis> HEXISTS myhash field1 (integer) 1 -ledis> hexists myhash field2 +ledis> HEXISTS myhash field2 (integer) 0 ``` -### hget key field +### HGET key field Returns the value associated with field in the hash stored at key. **Return value** -bulk: the value associated with field, or nil. +bulk: the value associated with field, or `nil`. **Examples** ``` -ledis> hset myhash field1 "foo" +ledis> HSET myhash field1 "foo" (integer) 1 -ledis> hget myhash field1 +ledis> HGET myhash field1 "foo" -ledis> hget myhash field2 +ledis> HGET myhash field2 (nil) ``` -### hgetall key +### HGETALL key Returns all fields and values of the hash stored at key. @@ -402,18 +405,18 @@ array: list of fields and their values stored in the hash, or an empty list (usi **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hset myhash field2 "world" +ledis> HSET myhash field2 "world" (integer) 1 -ledis> hgetall myhash +ledis> HGETALL myhash 1) "field1" 2) "hello" 3) "field2" 4) "world" ``` -### hincrby key field increment +### HINCRBY key field increment Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new hash key is created. If field does not exists the value is set to 0 before incrementing. @@ -425,17 +428,17 @@ int64: the value at field after the increment. **Examples** ``` -ledis> hincrby myhash field 1 +ledis> HINCRBY myhash field 1 (integer) 1 -ledis> hget myhash field +ledis> HGET myhash field "1" -ledis> hincrby myhash field 5 +ledis> HINCRBY myhash field 5 (integer) 6 -ledis> hincrby myhash field -10 +ledis> HINCRBY myhash field -10 (integer) -4 ``` -### hkeys key +### HKEYS key Return all fields in the hash stored at key. @@ -446,16 +449,16 @@ array: list of fields in the hash, or an empty list. **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hset myhash field2 "world" +ledis> HSET myhash field2 "world" (integer) 1 -ledis> hkeys myhash +ledis> HKEYS myhash 1) "field1" 2) "field2" ``` -### hlen key +### HLEN key Returns the number of fields contained in the hash stored at key @@ -466,17 +469,17 @@ int64: number of fields in the hash, or 0 when key does not exist. **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hset myhash field2 "world" +ledis> HSET myhash field2 "world" (integer) 1 -ledis> hlen myhash +ledis> HLEN myhash (integer) 2 ``` -### hmget key field [field ...] +### HMGET key field [field ...] -Returns the values associated with the specified fields in the hash stored at key. If field does not exist in the hash, a nil value is returned. +Returns the values associated with the specified fields in the hash stored at key. If field does not exist in the hash, a `nil` value is returned. **Return value** @@ -485,17 +488,17 @@ array: list of values associated with the given fields. **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hset myhash field2 "world" +ledis> HSET myhash field2 "world" (integer) 1 -ledis> hmget myhash field1 field2 nofield +ledis> HMGET myhash field1 field2 nofield 1) "hello" 2) "world" 3) (nil) ``` -### hmset key field value [field value ...] +### HMSET key field value [field value ...] Sets the specified fields to their respective values in the hash stored at key. @@ -506,14 +509,14 @@ string: OK **Examples** ``` -ledis> hmset myhash field1 "hello" field2 "world" +ledis> HMSET myhash field1 "hello" field2 "world" OK -ledis> hmget myhash field1 field2 +ledis> HMGET myhash field1 field2 1) "hello" 2) "world" ``` -### hset key field value +### HSET key field value Sets field in the hash stored at key to value. If key does not exists, a new hash key is created. @@ -527,17 +530,17 @@ int64: **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hget myhash field1 +ledis> HGET myhash field1 "hello" -ledis> hset myhash field1 "world" +ledis> HSET myhash field1 "world" (integer) 0 -ledis> hget myhash field1 +ledis> HGET myhash field1 "world" ``` -### hvals key +### HVALS key Returns all values in the hash stored at key. @@ -548,16 +551,16 @@ array: list of values in the hash, or an empty list. **Examples** ``` -ledis> hset myhash field1 "hello" +ledis> HSET myhash field1 "hello" (integer) 1 -ledis> hset myhash field2 "world" +ledis> HSET myhash field2 "world" (integer) 1 -ledis> hvals myhash +ledis> HVALS myhash 1) "hello" 2) "world" ``` -### hclear key +### HCLEAR key Deletes the specified hash keys @@ -568,15 +571,15 @@ int64: the number of fields in the hash stored at key **Examples** ``` -ledis> hmset myhash field1 "hello" field2 "world" +ledis> HMSET myhash field1 "hello" field2 "world" OK -ledis> hclear myhash +ledis> HCLEAR myhash (integer) 2 ``` -### hmclear key [key...] +### HMCLEAR key [key...] -Deletes the specified hash keys +Deletes the specified hash keys. **Return value** @@ -585,180 +588,985 @@ int64: the number of input keys **Examples** ``` -ledis> hmset myhash field1 "hello" field2 "world" +ledis> HMSET myhash field1 "hello" field2 "world" OK -ledis> hclear myhash +ledis> HMCLEAR myhash (integer) 1 ``` -### hexpire key seconds +### HEXPIRE key seconds Sets a hash key's time to live in seconds, like expire similarly. -### hexpireat key timestamp +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + + +**Examples** + +``` +ledis> HSET myhash a 100 +(integer) 1 +ledis> HGET myhash a +100 +ledis> HEXPIRE myhash 100 +(integer) 1 +ledis> HTTL myhash +(integer) 94 +ledis> HPERSIST myhash +(integer) 1 +ledis> HTTL myhash +(integer) -1 +ledis> HEXPIRE not_exists_key 100 +(integer) 0 +``` + +### HEXPIREAT key timestamp Sets the expiration for a hash key as a unix timestamp, like expireat similarly. -### httl key +**Return value** -Gets the tiem to live for a hash key in seconds, like ttl similarly. +int64: -### hpersist key +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> HSET myhash a 100 +(integer) 1 +ledis> HEXPIREAT myhash 1404999999 +(integer) 1 +ledis> HTTL myhash +(integer) 802475 +ledis> HEXPIREAT not_exists_key 1404999999 +(integer) 0 +``` + +### HTTL key + +Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, `-1` returns. + +**Return value** + +int64: TTL in seconds + +**Examples** + +``` +ledis> HSET myhash a 100 +(integer) 1 +ledis> HEXPIREAT myhash 1404999999 +(integer) 1 +ledis> HTTL myhash +(integer) 802475 +ledis> HTTL not_set_timeout +(integer) -1 +``` + +### HPERSIST key Remove the expiration from a hash key, like persist similarly. +Remove the existing timeout on key. + +**Return value** + +int64: + +- 1 if the timeout was removed +- 0 if key does not exist or does not have an timeout + +``` +ledis> HSET myhash a 100 +(integer) 1 +ledis> HEXPIREAT myhash 1404999999 +(integer) 1 +ledis> HTTL myhash +(integer) 802475 +ledis> HPERSIST myhash +(integer) 1 +ledis> HTTL myhash +(integer) -1 +ledis> HPERSIST not_exists_key +(integer) 0 +``` + ## List -### lindex +### LINDEX key index +Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, `-1` means the last element, `-2` means the penultimate and so forth. +When the value at key is not a list, an error is returned. + **Return value** -**Examples** -### llen -**Return value** +string: the requested element, or `nil` when index is out of range. **Examples** -### lpop + +``` +ledis> RPUSH a 1 2 3 +(integer) 3 +ledis> LINDEX a 0 +1 +ledis> LINDEX a 1 +2 +ledis> LINDEX a 2 +3 +ledis> LINDEX a 3 +(nil) +ledis> LINDEX a -1 +3 +``` + +### LLEN key +Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and `0`is returned. An error is returned when the value stored at key is not a list. + **Return value** -**Examples** -### lrange -**Return value** +int64: the length of the list at key. **Examples** -### lpush + +``` +ledis> RPUSH a 'foo' +(integer) 1 +ledis> RPUSH a 'bar' +(integer) 2 +ledis> LLEN a +(integer) 2 +``` + +### LPOP key +Removes and returns the first element of the list stored at key. + **Return value** -**Examples** -### rpop -**Return value** +bulk: the value of the first element, or `nil` when key does not exist. **Examples** -### rpush + +``` +ledis> RPUSH a 'one' +(integer) 1 +ledis> RPUSH a 'two' +(integer) 2 +ledis> RPUSH a 'three' +(integer) 3 +ledis> LPOP a +one +``` + +### LRANGE key start stop +Returns the specified elements of the list stored at key. The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), `1` being the next element and so on. + **Return value** -**Examples** -### lclear -**Return value** +array: list of elements in the specified range. **Examples** -### lexpire + +``` +ledis> RPUSH a 'one' 'two' 'three' +(integer) 3 +ledis> LRANGE a 0 0 +1) "one" +ledis> LRANGE a -100 100 +1) "one" +2) "two" +3) "three" +ledis> LRANGE a -3 2 +1) "one" +2) "two" +3) "three" +ledis> LRANGE a 0 -1 +(empty list or set) +``` + +### LPUSH key value [value ...] +Insert all the specified values at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. When key holds a value that is not a list, an error is returned. + **Return value** -**Examples** -### lexpireat -**Return value** +int64: the length of the list after the push operations. **Examples** -### lttl + +``` +ledis> LPUSH a 1 +(integer) 1 +ledis> LPUSH a 2 +(integer) 2 +ledis> LRANGE a 0 2 +1) "2" +2) "1" +``` + +### RPOP key +Removes and returns the last element of the list stored at key. + **Return value** -**Examples** -### lpersist -**Return value** +bulk: the value of the last element, or `nil` when key does not exist. **Examples** +``` +edis > RPUSH a 1 +(integer) 1 +ledis> RPUSH a 2 +(integer) 2 +ledis> RPUSH a 3 +(integer) 3 +ledis> RPOP a +3 +ledis> LRANGE a 0 3 +1) "1" +2) "2" +``` + +### RPUSH key value [value ...] +Insert all the specified values at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. When key holds a value that is not a list, an error is returned. + +**Return value** + +int64: the length of the list after the push operation. + +**Examples** + +``` +ledis> RPUSH a 'hello' +(integer) 1 +ledis> RPUSH a 'world' +(integer) 2 +ledis> LRANGE a 0 2 +1) "hello" +2) "world" +``` + +### LCLEAR key +Deletes the specified list key + +**Return value** + +int64: the number of values in the list stored at key + +**Examples** + +``` +ledis> RPUSH a 1 2 3 +(integer) 3 +ledis> LLEN a +(integer) 3 +ledis> LCLEAR a +(integer) 3 +ledis> LLEN a +(integer) 0 +``` + +### LEXPIRE key seconds +Set a timeout on key. After the timeout has expired, the key will be deleted. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> RPUSH a 1 +(integer) 1 +ledis> LEXPIRE a 100 +(integer) 1 +ledis> LTTL a +(integer) 96 +ledis> LPERSIST a +(integer) 1 +ledis> LTTL a +(integer) -1 +``` + +### LEXPIREAT key timestamp +Set an expired unix timestamp on key. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> RPUSH a 1 +(integer) 1 +ledis> LEXPIREAT a 1404140183 +(integer) 1 +ledis> LTTL a +(integer) 570 +ledis> LPERSIST a +(integer) 1 +ledis> LTTL a +(integer) -1 +ledis> +``` + +### LTTL key +Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, `-1` returns. + +**Return value** + +int64: TTL in seconds + +**Examples** + +``` +ledis> RPUSH a 1 +(integer) 1 +ledis> LEXPIREAT a 1404140183 +(integer) 1 +ledis> LTTL a +(integer) 570 +ledis> LPERSIST a +(integer) 1 +ledis> LTTL a +(integer) -1 +``` + +### LPERSIST key +Remove the existing timeout on key + +**Return value** + +int64: + +- 1 if the timeout was removed +- 0 if key does not exist or does not have an timeout + +**Examples** + +``` +ledis> RPUSH a 1 +(integer) 1 +ledis> LEXPIREAT a 1404140183 +(integer) 1 +ledis> LTTL a +(integer) 570 +ledis> LPERSIST a +(integer) 1 +ledis> LTTL a +(integer) -1 +ledis> LPERSIST b +(integer) 0 +``` + + ## ZSet -### zadd +### ZADD key score member [score member ...] +Adds all the specified members with the specified scores to the sorted set stored at key. It is possible to specify multiple `score / member` pairs. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. + +If key does not exist, a new sorted set with the specified members as sole members is created, like if the sorted set was empty. If the key exists but does not hold a sorted set, an error is returned. + +The score values should be the string representation of an `int64` number. `+inf` and `-inf` values are valid values as well. + +**Currently, we only support int64 type, not double type.** + **Return value** -**Examples** -### zcard -**Return value** +int64, specifically: + +The number of elements added to the sorted sets, **not** including elements already existing for which the score was updated. + **Examples** -### zcount + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 1 'uno' +(integer) 1 +ledis> ZADD myset 2 'two' 3 'three' +(integer) 2 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "one" +2) "1" +3) "uno" +4) "1" +5) "two" +6) "2" +7) "three" +8) "3" +``` + +### ZCARD key +Returns the sorted set cardinality (number of elements) of the sorted set stored at key. + **Return value** -**Examples** -### zincrby -**Return value** +int64: the cardinality (number of elements) of the sorted set, or `0` if key does not exist. **Examples** -### zrange + +``` +edis > ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 1 'uno' +(integer) 1 +ledis> ZADD myset 2 'two' 3 'three' +(integer) 2 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "one" +2) "1" +3) "uno" +4) "1" +5) "two" +6) "2" +7) "three" +8) "3" +ledis> zcard myset +(integer) 4 +``` + +### ZCOUNT key min max +Returns the number of elements in the sorted set at key with a score between `min` and `max`. +The `min` and `max` arguments have the same semantic as described for `ZRANGEBYSCORE`. + **Return value** -**Examples** -### zrangebyscore -**Return value** +int64: the number of elements in the specified score range. **Examples** -### zrank + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 1 'uno' +(integer) 1 +ledis> ZADD myset 2 'two' 3 'three' +(integer) 2 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "one" +2) "1" +3) "uno" +4) "1" +5) "two" +6) "2" +7) "three" +8) "3" +ledis> zcount myset -inf +inf +(integer) 4 +ledis> zcount myset (1 3 +(integer) 2 +``` + +### ZINCRBY key increment member + +Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0). If key does not exist, a new sorted set with the specified member as its sole member is created. +An error is returned when key exists but does not hold a sorted set. +The score value should be the string representation of a numeric value. It is possible to provide a negative value to decrement the score. + **Return value** -**Examples** -### zrem -**Return value** +bulk: the new score of member (an int64 number), represented as string. **Examples** -### zremrangebyrank + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 2 'two' +(integer) 1 +ledis> ZINCRBY myset 2 'one' +3 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "two" +2) "2" +3) "one" +4) "3" +``` + +### ZRANGE key start stop [WITHSCORES] +Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. + **Return value** -**Examples** -### zremrangebyscore -**Return value** +array: list of elements in the specified range (optionally with their scores). **Examples** -### zrevrange + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 2 'two' +(integer) 1 +ledis> ZADD myset 3 'three' +(integer) 1 +ledis> ZRANGE myset 0 -1 +1) "one" +2) "two" +3) "three" +ledis> ZRANGE myset 2 3 +1) "three" +ledis> ZRANGE myset -2 -1 +1) "two" +2) "three" +``` + +### ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] + +Returns all the elements in the sorted set at key with a score between `min` and `max` (including elements with score equal to `min` or `max`). The elements are considered to be ordered from low to high scores. + +**Exclusive intervals and infinity** + +`min` and `max` can be `-inf` and `+inf`, so that you are not required to know the highest or lowest score in the sorted set to get all elements from or up to a certain score. +By default, the interval specified by min and max is closed (inclusive). It is possible to specify an open interval (exclusive) by prefixing the score with the character (. For example: + +``` +ZRANGEBYSCORE zset (1 5 +``` + +Will return all elements with 1 < score <= 5 while: + +``` +ZRANGEBYSCORE zset (5 (10 +``` + +Will return all the elements with 5 < score < 10 (5 and 10 excluded). + + **Return value** -**Examples** -### zrevrangebyscore -**Return value** +array: list of elements in the specified score range (optionally with their scores). **Examples** -### zscore + +``` +ledis> ZADD myzset 1 'one' +(integer) 1 +ledis> ZADD myzset 2 'two' +(integer) 1 +ledis> ZADD myzset 3 'three' +(integer) 1 +ledis> ZRANGEBYSCORE myzset -inf +inf WITHSCORES +1) "one" +2) "1" +3) "two" +4) "2" +5) "three" +6) "3" +ledis> ZRANGEBYSCORE myzset -inf +inf WITHSCORES LIMIT 2 5 +1) "three" +2) "3" +ledis> ZRANGEBYSCORE myzset (1 2 WITHSCORES +1) "two" +2) "2" +ledis> ZRANGEBYSCORE myzset (1 (2 WITHSCORES +``` + +### ZRANK key member +Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high. The rank (or index) is `0-based`, which means that the member with the lowest score has rank 0. + **Return value** -**Examples** -### zclear -**Return value** +Return value + +- If member exists in the sorted set, Integer reply: the rank of member. +- If member does not exist in the sorted set or key does not exist, Bulk string reply: nil. **Examples** -### zexpire + +``` +ledis> ZADD myzset 1 'one' +(integer) 1 +ledis> ZADD myzset 2 'two' +(integer) 1 +ledis> ZADD myzset 3 'three' +(integer) 1 +ledis> ZRANGEBYSCORE myzset -inf +inf WITHSCORES +1) "one" +2) "1" +3) "two" +4) "2" +5) "three" +6) "3" +ledis> ZRANK myzset 'three' +(integer) 2 +``` + + +### ZREM key member [member ...] +Removes the specified members from the sorted set stored at key. Non existing members are ignored. +An error is returned when key exists and does not hold a sorted set. + **Return value** -**Examples** -### zexpireat -**Return value** +int64 reply, specifically: + +The number of members removed from the sorted set, not including non existing members. **Examples** -### zttl + +``` +ledis> ZADD myset 1 one 2 two 3 three 4 four +(integer) 3 +ledis> ZRANGE myset 0 -1 +1) "one" +2) "two" +3) "three" +4) "four" +ledis> ZREM myset three +(integer) 1 +ledis> ZREM myset one four three +(integer) 2 +``` + +### ZREMRANGEBYRANK key start stop +Removes all elements in the sorted set stored at key with rank between start and stop. Both start and stop are 0 -based indexes with 0 being the element with the lowest score. These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. + **Return value** -**Examples** -### zpersist -**Return value** +int64: the number of elements removed. **Examples** +``` +ledis> ZADD myset 1 one 2 two 3 three 4 four +(integer) 3 +ledis> ZREMRANGEBYRANK myset 0 2 +(integer) 3 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "four" +2) "4" +``` + + +### ZREMRANGEBYSCORE key min max +Removes all elements in the sorted set stored at key with a score between `min` and `max` (inclusive). `Min` and `max` can be exclusive, following the syntax of `ZRANGEBYSCORE`. + +**Return value** + +int64: the number of elements removed. + +**Examples** + +``` +ledis> ZADD myset 1 one 2 two 3 three 4 four +(integer) 4 +ledis> ZREMRANGEBYSCORE myset -inf (2 +(integer) 1 +ledis> ZRANGE myset 0 -1 WITHSCORES +1) "two" +2) "2" +3) "three" +4) "3" +5) "four" +6) "4" +``` + +### ZREVRANGE key start stop [WITHSCORES] +Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the highest to the lowest score. Descending lexicographical order is used for elements with equal score. +Apart from the reversed ordering, ZREVRANGE is similar to `ZRANGE`. + +**Return value** + +array: list of elements in the specified range (optionally with their scores). + +**Examples** + +``` +ledis> ZADD myset 1 one 2 two 3 three 4 four +(integer) 4 +ledis> ZREVRANGE myset 0 -1 +1) "four" +2) "three" +3) "two" +4) "one" +``` + +### ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] +Returns all the elements in the sorted set at key with a score between max and min (including elements with score equal to max or min). In contrary to the default ordering of sorted sets, for this command the elements are considered to be ordered from high to low scores. +The elements having the same score are returned in reverse lexicographical order. +Apart from the reversed ordering, ZREVRANGEBYSCORE is similar to ZRANGEBYSCORE. + +**Return value** + +array: list of elements in the specified score range (optionally with their scores). + +**Examples** + +``` +ledis> ZADD myset 1 one 2 two 3 three 4 four +(integer) 4 +ledis> ZREVRANGEBYSCORE myset +inf -inf +1) "four" +2) "three" +3) "two" +4) "one" +ledis> ZREVRANGEBYSCORE myset 2 1 +1) "two" +2) "one" +ledis> ZREVRANGEBYSCORE myset 2 (1 +1) "two" +ledis> ZREVRANGEBYSCORE myset (2 (1 +(empty list or set) +ledis> ZREVRANGEBYSCORE myset +inf -inf WITHSCORES LIMIT 1 2 +1) "three" +2) "3" +3) "two" +4) "2" +``` + +### ZSCORE key member +Returns the score of member in the sorted set at key. +If member does not exist in the sorted set, or key does not exist, `nil` is returned. + +**Return value** + +bulk: the score of member (an `int64` number), represented as string. + +**Examples** + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZSCORE myset 'one' +1 +``` + +### ZCLEAR key +Delete the specified key + +**Return value** + +int64: the number of members in the zset stored at key + +**Examples** + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZADD myset 2 'two' +(integer) 1 +ledis> ZADD myset 3 'three' +(integer) 1 +ledis> ZRANGE myset 0 -1 +1) "one" +2) "two" +3) "three" +ledis> ZCLEAR myset +(integer) 3 +``` + +### ZMCLEAR key [key ...] +Delte multiple keys one time. + +**Return value** + +int64: the number of input keys + +**Examples** + +``` +ledis> ZADD myset1 1 'one' +(integer) 1 +ledis> ZADD myset2 2 'two' +(integer) 1 +ledis> ZMCLEAR myset1 myset2 +(integer) 2 +``` + +### ZEXPIRE key seconds + +Set a timeout on key. After the timeout has expired, the key will be deleted. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + + +**Examples** + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZEXPIRE myset 100 +(integer) 1 +ledis> ZTTL myset +(integer) 97 +ledis> ZPERSIST myset +(integer) 1 +ledis> ZTTL mset +(integer) -1 +ledis> ZEXPIRE myset1 100 +(integer) 0 +``` + +### ZEXPIREAT key timestamp +Set an expired unix timestamp on key. Similar to ZEXPIRE. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZEXPIREAT myset 1404149999 +(integer) 1 +ledis> ZTTL myset +(integer) 7155 +ledis> ZPERSIST myset +(integer) 1 +ledis> ZTTL mset +(integer) -1 +ledis> ZEXPIREAT myset1 1404149999 +(integer) 0 +``` + + +### ZTTL key +Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, `-1` returns. + +**Return value** + +int64: TTL in seconds + +**Examples** + +``` +ledis> zadd myset 1 'one' +(integer) 1 +ledis> zexpire myset 100 +(integer) 1 +ledis> zttl myset +(integer) 97 +ledis> zttl myset2 +(integer) -1 +``` + +### ZPERSIST key +Remove the existing timeout on key. + +**Return value** + +int64: + +- 1 if the timeout was removed +- 0 if key does not exist or does not have an timeout + +**Examples** + +``` +ledis> ZADD myset 1 'one' +(integer) 1 +ledis> ZEXPIRE myset 100 +(integer) 1 +ledis> ZTTL myset +(integer) 97 +ledis> ZPERSIST myset +(integer) 1 +ledis> ZTTL mset +(integer) -1 +``` + + ## Replication -### slaveof +### SLAVEOF host port + +Changes the replication settings of a slave on the fly. If the server is already acting as slave, SLAVEOF NO ONE will turn off the replication. + +SLAVEOF host port will make the server a slave of another server listening at the specified host and port. + +If a server is already a slave of a master, SLAVEOF host port will stop the replication against the old and start the synchronization against the new one, discarding the old dataset. + + +### FULLSYNC + +Inner command, starts a fullsync from the master set by SLAVEOF. + +FULLSYNC will first try to sync all data from the master, save in local disk, then discard old dataset and load new one. + **Return value** **Examples** -### fullsync -**Return value** -**Examples** -### sync + +### SYNC index offset + +Inner command, syncs the new changed from master set by SLAVEOF at offset in binlog.index file. + **Return value** **Examples** ## Server -### ping +### PING +Returns PONG. This command is often used to test if a connection is still alive, or to measure latency. + **Return value** -**Examples** -### echo -**Return value** +String **Examples** -### select + +``` +ledis> PING +PONG +ledis> PING +dial tcp 127.0.0.1:6665: connection refused +ledis> +``` + +### ECHO message + +Returns message. + **Return value** +bulk string reply + **Examples** + +``` +ledis> ECHO "hello" +hello +``` + +### SELECT index +Select the DB with having the specified zero-based numeric index. New connections always use DB `0`. Currently, We support `16` DBs(`0-15`). + +**Return value** + +Simple string reply + +**Examples** + +``` +ledis> SELECT 2 +OK +ledis> SELECT 15 +OK +ledis> SELECT 16 +ERR invalid db index 16 +``` diff --git a/leveldb/db.go b/leveldb/db.go index 7efed06..3cb2192 100644 --- a/leveldb/db.go +++ b/leveldb/db.go @@ -88,8 +88,7 @@ func Repair(cfg *Config) error { db.cfg = cfg err := db.open() - - db.Close() + defer db.Close() //open ok, do not need repair if err == nil {