]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
kresc: improve libedit/editline based history and tab-completion
authorŠtěpán Balážik <stepan.balazik@nic.cz>
Wed, 25 Jan 2017 15:47:49 +0000 (16:47 +0100)
committerŠtěpán Balážik <stepan.balazik@nic.cz>
Fri, 3 Feb 2017 18:29:13 +0000 (19:29 +0100)
daemon/daemon.mk
daemon/kresc.c
daemon/main.c

index 8fe117f1b4d7083ab305ee66fc8fa7ce6e9211d6..5653ffa35651a42ce7347fb9d62ab63409fa2002 100644 (file)
@@ -62,7 +62,7 @@ daemon/lua/kres-gen.lua: | $(libkres)
 ifeq ($(HAS_libedit), yes)
 kresc_SOURCES := daemon/kresc.c
 kresc_CFLAGS += -fPIE $(libedit_CFLAGS)
-kresc_LIBS += $(libedit_LIBS)
+kresc_LIBS += $(contrib_TARGET) $(libedit_LIBS)
 kresc_DEPEND := $(libkres) $(contrib)
 $(eval $(call make_sbin,kresc,daemon,yes))
 client: $(kresc)
index b56959e2e3c47390a507aa74d28ab45dcd947580..5aebeae60c1eebb6b8d8b47a2dc982bc11e589ee 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2016-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -14,7 +14,9 @@
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include <assert.h>
+#include <contrib/ccan/asprintf/asprintf.h>
 #include <editline/readline.h>
+#include <errno.h>
 #include <histedit.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
 
-#define HISTORY_FILE ".kresc_history"
+#define HISTORY_FILE "kresc_history"
 #define PROGRAM_NAME "kresc"
 
 FILE *g_tty = NULL;            //!< connection to the daemon
 
-static char *run_cmd(const char *cmd, uint32_t * msg_len);
+static char *run_cmd(const char *cmd, size_t * msg_len);
 
-char *prompt(EditLine * e)
+const char *prompt(EditLine * e)
 {
        return PROGRAM_NAME "> ";
 }
@@ -44,67 +47,216 @@ bool starts_with(const char *a, const char *b)
        return 0;
 }
 
+//! Returns Lua name of type of value, NULL on error. Puts length of type in name_len;
+const char *get_type_name(const char *value)
+{
+       if (value == NULL) {
+               return NULL;
+       }
+
+       for (int i = 0; value[i]; i++) {
+               if (value[i] == ')') {
+                       //Return NULL to prevent unexpected function call
+                       return NULL;
+               }
+       }
+
+       char *cmd = afmt("type(%s)", value);
+
+       if (!cmd) {
+               perror("While tab-completing.");
+               return NULL;
+       }
+
+       size_t name_len;
+       char *type = run_cmd(cmd, &name_len);
+       if (!type) {
+               return NULL;
+       } else {
+               free(cmd);
+       }
+
+       if (starts_with(type, "[")) {
+               //Return "nil" on non-valid name.
+               return "nil";
+       } else {
+               type[(strlen(type)) - 1] = '\0';
+               return type;
+       }
+}
+
 static unsigned char complete(EditLine * el, int ch)
 {
        int argc, pos;
        const char **argv;
        const LineInfo *li = el_line(el);
        Tokenizer *tok = tok_init(NULL);
-       // Parse the line.
-       int ret = tok_line(tok, li, &argc, &argv, NULL, &pos);
 
-       uint32_t msg_len;
+       //Tokenize current line.
+       int ret = tok_line(tok, li, &argc, &argv, NULL, &pos);
 
-       char *help = run_cmd("help()", &msg_len);
-       if (!help) {
-               perror("While communication with daemon");
+       if (ret != 0) {
+               perror("While tab-completing.");
+               goto complete_exit;
+       }
+       //Show help.
+       if (argc == 0) {
+               size_t help_len;
+               char *help = run_cmd("help()", &help_len);
+               if (help) {
+                       printf("\n%s", help);
+                       free(help);
+               } else {
+                       perror("While communication with daemon");
+               }
                goto complete_exit;
        }
 
-       if (ret != 0) {
+       if (argc > 1) {
                goto complete_exit;
        }
+       //Get name of type of current line.
+       const char *type = get_type_name(argv[0]);
 
-       if (argc == 0) {
-               printf("\n%s", help);
-       }
-
-       char *lines;
-       lines = strtok(help, "\n");
-       int matches = 0;
-       bool exactmatch = 0;
-       char *lastmatch;
-       int i = 0;
-       while (lines != NULL) {
-               if (!(i % 2))
-                       if (argv[0] && starts_with(lines, argv[0])) {
-                               printf("\n%s", lines);
-                               lastmatch = lines;
+       if (!type) {
+               goto complete_exit;
+       }
+       //Get position of last dot in current line (useful for parsing table).
+       char *dot = strrchr(argv[0], '.');
+
+       //Line is not a name of some table and there is no dot in it.
+       if (strncmp(type, "table", 5) && !dot) {
+               //Parse Lua globals.
+               size_t globals_len;
+               char *globals = run_cmd("_G.__orig_name_list", &globals_len);
+               if (!globals) {
+                       perror("While tab-completing");
+                       goto complete_exit;
+               }
+               //Show possible globals.
+               char *globals_tok = strdup(globals);
+               free(globals);
+               if (!globals_tok) {
+                       goto complete_exit;
+               }
+               char *token = strtok(globals_tok, "\n");
+               int matches = 0;
+               char *lastmatch;
+               while (token) {
+                       if (argv[0] && starts_with(token, argv[0])) {
+                               printf("\n%s (%s)", token,
+                                      get_type_name(token));
+                               fflush(stdout);
+                               lastmatch = token;
                                matches++;
-                               if (!strcmp(lines, argv[0]))
-                                       exactmatch = 1;
                        }
-               lines = strtok(NULL, "\n");
-               i++;
-       }
-       printf("\n");
-       if (matches == 1) {
-               char *brace = strchr(lastmatch, '(');
-               if (brace != NULL)
-                       *(brace + 1) = '\0';
-               el_deletestr(el, pos);
-               el_insertstr(el, lastmatch);
-               pos = strlen(lastmatch);
-               if (exactmatch && brace == NULL) {
-                       char *prettyprint = run_cmd(lastmatch, &msg_len);
-                       printf("%s", prettyprint);
+                       token = strtok(NULL, "\n");
+               }
+               if (matches > 1) {
+                       printf("\n");
+               }
+               //Complete matching global.
+               if (matches == 1) {
+                       el_deletestr(el, pos);
+                       el_insertstr(el, lastmatch);
+                       pos = strlen(lastmatch);
+               }
+               free(globals_tok);
+
+               //Current line (or part of it) is a name of some table.
+       } else if ((dot && !strncmp(type, "nil", 3))
+                  || !strncmp(type, "table", 5)) {
+               char *table = strdup(argv[0]);
+               if (!table) {
+                       perror("While tab-completing");
+                       goto complete_exit;
+               }
+               //Get only the table name (without partial member name).
+               if (dot) {
+                       *(table + (dot - argv[0])) = '\0';
+               }
+               //Insert a dot after the table name.
+               if (!strncmp(type, "table", 5)) {
                        el_insertstr(el, ".");
-                       free(prettyprint);
+                       pos++;
                }
+               //Check if the substring before dot is a valid table name.
+               const char *t_type = get_type_name(table);
+               if (t_type && !strncmp("table", t_type, 5)) {
+                       //Get string of members of the table.
+                       char *cmd =
+                           afmt
+                           ("do local s=\"\"; for i in pairs(%s) do s=s..i..\"\\n\" end return(s) end",
+                            table);
+                       if (!cmd) {
+                               perror("While tab-completing.");
+                               goto complete_exit;
+                       }
+                       size_t members_len;
+                       char *members = run_cmd(cmd, &members_len);
+                       free(cmd);
+                       if (!members) {
+                               perror("While communication with daemon");
+                       }
+                       //Split members by newline.
+                       char *members_tok = strdup(members);
+                       free(members);
+                       if (!members_tok) {
+                               goto complete_exit;
+                       }
+                       char *token = strtok(members_tok, "\n");
+                       int matches = 0;
+                       char *lastmatch;
+                       if (!dot || dot - argv[0] + 1 == strlen(argv[0])) {
+                               //Prints all members.
+                               while (token) {
+                                       char *member =
+                                           afmt("%s.%s", table, token);
+                                       printf("\n%s (%s)", member,
+                                              get_type_name(member));
+                                       token = strtok(NULL, "\n");
+                                       matches++;
+                               }
+                       } else {
+                               //Print members matching the current line.
+                               while (token) {
+                                       if (argv[0]
+                                           && starts_with(token, dot + 1)) {
+                                               printf("\n%s.%s (%s)", table,
+                                                      token,
+                                                      get_type_name(afmt
+                                                                    ("%s.%s",
+                                                                     table,
+                                                                     token)));
+                                               lastmatch = token;
+                                               matches++;
+                                       }
+                                       token = strtok(NULL, "\n");
+                               }
+
+                               //Complete matching member.
+                               if (matches == 1) {
+                                       el_deletestr(el, pos);
+                                       el_insertstr(el, table);
+                                       el_insertstr(el, ".");
+                                       el_insertstr(el, lastmatch);
+                                       pos =
+                                           strlen(lastmatch) + strlen(table) +
+                                           1;
+                               }
+                       }
+                       if (matches > 1) {
+                               printf("\n");
+                       }
+                       free(members_tok);
+               }
+       } else if (!strncmp(type, "function", 8)) {
+               //Add left parenthesis to function name. 
+               el_insertstr(el, "(");
+               pos++;
        }
 
 complete_exit:
-       free(help);
        tok_reset(tok);
        tok_end(tok);
        return CC_REDISPLAY;
@@ -133,7 +285,6 @@ static int init_tty(const char *path)
        g_tty = fdopen(fd, "r+");
        if (!g_tty) {
                perror("While opening TTY");
-
                return 1;
        }
        // Switch to binary mode and consume the text "> ".
@@ -146,15 +297,13 @@ static int init_tty(const char *path)
        return 0;
 }
 
-//! Run a command on the daemon; return the answer or NULL on failure.
-static char *run_cmd(const char *cmd, uint32_t * msg_len)
+//! Run a command on the daemon; return the answer or NULL on failure, puts answer length to out_len.
+static char *run_cmd(const char *cmd, size_t * out_len)
 {
        if (!g_tty || !cmd) {
                assert(false);
                return NULL;
        }
-       printf("cmd: %s\n", cmd);
-
        if (fprintf(g_tty, "%s", cmd) < 0 || fflush(g_tty))
                return NULL;
        uint32_t len;
@@ -168,21 +317,19 @@ static char *run_cmd(const char *cmd, uint32_t * msg_len)
                return NULL;
        }
        msg[len] = '\0';
-       *msg_len = len;
+       *out_len = len;
        return msg;
 }
 
 static int interact()
 {
-
        EditLine *el;
        History *hist;
        int count;
        const char *line;
-       int keepreading = 1;
        HistEvent ev;
        el = el_init(PROGRAM_NAME, stdin, stdout, stderr);
-       el_set(el, EL_PROMPT, &prompt);
+       el_set(el, EL_PROMPT, prompt);
        el_set(el, EL_EDITOR, "emacs");
        el_set(el, EL_ADDFN, PROGRAM_NAME "-complete",
               "Perform " PROGRAM_NAME " completion.", complete);
@@ -196,14 +343,49 @@ static int interact()
        history(hist, &ev, H_SETSIZE, 800);
        el_set(el, EL_HIST, history, hist);
 
-       const char hist_file[] = HISTORY_FILE;
-       history(hist, &ev, H_LOAD, hist_file);
+       char *hist_file = NULL;
+
+       char *data_home = getenv("XDG_DATA_HOME");
+
+       //Check whether $XDG_DATA_HOME is set.
+       if (!data_home || *data_home == '\0') {
+               const char *home = getenv("HOME");      //This should be set on any POSIX compliant OS, even for nobody
+
+               //Create necessary folders.
+               char *dirs[3] =
+                   { afmt("%s/.local", home), afmt("%s/.local/share", home),
+                       afmt("%s/.local/share/kresd/", home)
+               };
+               bool ok = true;
+               for (int i = 0; i < 3; i++) {
+                       if (mkdir(dirs[i], 0755) && errno != EEXIST) {
+                               ok = false;
+                               break;
+                       }
+               }
+               if (ok) {
+                       hist_file =
+                           afmt("%s/.local/share/kresd/" HISTORY_FILE, home);
+               }
+       } else {
+               if (!mkdir(afmt("%s/kresd/", data_home), 0755)
+                   || errno == EEXIST) {
+                       hist_file = afmt("%s/kresd/" HISTORY_FILE, data_home);
+               }
+       }
+
+       //Load history file
+       if (hist_file) {
+               history(hist, &ev, H_LOAD, hist_file);
+       } else {
+               perror("While opening history file");
+       }
 
-       while (keepreading) {
+       while (1) {
                line = el_gets(el, &count);
                if (count > 0) {
                        history(hist, &ev, H_ENTER, line);
-                       uint32_t msg_len;
+                       size_t msg_len;
                        char *msg = run_cmd(line, &msg_len);
                        if (!msg) {
                                perror("While communication with daemon");
@@ -216,13 +398,14 @@ static int interact()
                        if (msg_len == 0 || msg[msg_len - 1] != '\n') {
                                printf("\n");
                        }
-                       printf("%d\n", msg_len);
-                       history(hist, &ev, H_SAVE, hist_file);
-
+                       if (hist_file) {
+                               history(hist, &ev, H_SAVE, hist_file);
+                       }
                        free(msg);
                }
        }
        history_end(hist);
+       free(hist_file);
        el_end(el);
        if (feof(stdin))
                return 0;
index bc3d42700a9d58592473d0dbd8ca8cb2a601ba60..fb60f30d514db151a73c476e65f0851e97092b73 100644 (file)
@@ -93,6 +93,8 @@ static void tty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
                char *cmd = buf->base;
                if (cmd[nread - 1] == '\n') {
                        cmd[nread - 1] = '\0';
+               } else {
+                       cmd[nread] = '\0';
                }
 
                /* Pseudo-command for switching to "binary output" */