Commit b909a109 authored by Daniel Salzman's avatar Daniel Salzman

knotc: add history and completion support based on libedit

parent 1cca7f9e
......@@ -429,6 +429,8 @@ src/utils/common/exec.c
src/utils/common/exec.h
src/utils/common/hex.c
src/utils/common/hex.h
src/utils/common/lookup.c
src/utils/common/lookup.h
src/utils/common/msg.c
src/utils/common/msg.h
src/utils/common/netio.c
......@@ -465,7 +467,11 @@ src/utils/knotc/commands.c
src/utils/knotc/commands.h
src/utils/knotc/estimator.c
src/utils/knotc/estimator.h
src/utils/knotc/interactive.c
src/utils/knotc/interactive.h
src/utils/knotc/main.c
src/utils/knotc/process.c
src/utils/knotc/process.h
src/utils/knotd/main.c
src/utils/knsupdate/knsupdate_exec.c
src/utils/knsupdate/knsupdate_exec.h
......@@ -530,6 +536,7 @@ tests/server.c
tests/sockaddr.c
tests/test_conf.h
tests/tsig_key.c
tests/utils/test_lookup.c
tests/wire.c
tests/wire_ctx.c
tests/worker_pool.c
......
......@@ -7,6 +7,7 @@ Knot DNS has several dependencies:
* liburcu >= 0.5.4
* gnutls >= 3.0
* jansson >= 2.3
* libedit
Embedded libraries:
* lmdb (system library is preferred)
......
......@@ -447,6 +447,38 @@ AS_IF([test "$with_libidn" != "no"],[
]) # Knot DNS utilities dependencies
PKG_CHECK_MODULES([libedit], [libedit], [], [
save_CFLAGS="$CFLAGS"
save_LIBS="$LIBS"
with_libedit=no
for try_path in "" "/usr" "/usr/local"; do
AS_IF([test -d "$try_path"], [
libedit_CFLAGS="-I$try_path/include"
libedit_LIBS="-L$try_path/lib"
],[
continue
])
CFLAGS="$CFLAGS $libedit_CFLAGS"
LIBS="$LIBS $libedit_LIBS"
AC_CHECK_HEADERS([histedit.h], [], [continue])
AC_SEARCH_LIBS([el_init], [edit], [], [continue])
with_libedit=yes
libedit_LIBS="$libedit_LIBS -ledit"
break
done
CFLAGS="$save_CFLAGS"
LIBS="$save_LIBS"
AS_IF([test "$with_libedit" = "no"], [
AC_MSG_ERROR([libedit library not found])
])
])
# Bash completions
AC_ARG_WITH([bash-completions],
AC_HELP_STRING([--with-bash-completions=[DIR]], [Bash completions directory [default=no]]),
......
......@@ -34,6 +34,7 @@ Knot DNS requires few libraries to be compiled:
* GnuTLS, at least 3.0
* Jansson, at least 2.3
* Userspace RCU, at least 0.5.4
* libedit
* lmdb (included)
* libcap-ng, at least 0.6.4 (optional)
* libidn (optional)
......
......@@ -34,6 +34,11 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.sp
\fBknotc\fP [\fIparameters\fP] \fIaction\fP [\fIaction_args\fP]
.SH DESCRIPTION
.sp
If no \fIaction\fP is specified, the program is executed in interactive mode. Then
various command or parameters completion is available via the TAB key. And
command history is enabled (see \fIeditrc(5)\fP for additional
customization).
.SS Parameters
.INDENT 0.0
.TP
......@@ -225,7 +230,7 @@ $ knotc conf\-commit
.UNINDENT
.SH SEE ALSO
.sp
\fIknotd(8)\fP, \fIknot.conf(5)\fP\&.
\fIknotd(8)\fP, \fIknot.conf(5)\fP, \fIeditrc(5)\fP\&.
.SH AUTHOR
CZ.NIC Labs <http://www.knot-dns.cz>
.SH COPYRIGHT
......
......@@ -11,6 +11,11 @@ Synopsis
Description
-----------
If no *action* is specified, the program is executed in interactive mode. Then
various command or parameters completion is available via the TAB key. And
command history is enabled (see :manpage:`editrc(5)` for additional
customization).
Parameters
..........
......@@ -185,4 +190,4 @@ Add example.org zone with a zonefile location
See Also
--------
:manpage:`knotd(8)`, :manpage:`knot.conf(5)`.
:manpage:`knotd(8)`, :manpage:`knot.conf(5)`, :manpage:`editrc(5)`.
......@@ -211,6 +211,10 @@ knotc_SOURCES = \
utils/knotc/commands.h \
utils/knotc/estimator.c \
utils/knotc/estimator.h \
utils/knotc/interactive.c \
utils/knotc/interactive.h \
utils/knotc/process.c \
utils/knotc/process.h \
utils/knotc/main.c
knotd_SOURCES = \
......@@ -370,7 +374,7 @@ libknotd_la_LIBADD = libknot.la libknot-yparser.la zscanner/libzscanner.la $(lib
knotd_CPPFLAGS = $(AM_CPPFLAGS) $(liburcu_CFLAGS)
knotd_LDADD = libknotd.la $(liburcu_LIBS)
knotc_LDADD = libknotd.la
knotc_LDADD = libknotd.la libknotus.la $(libedit_LIBS)
knot1to2_LDADD = libcontrib.la
####################################
......@@ -437,6 +441,8 @@ libknotus_la_SOURCES = \
utils/common/exec.h \
utils/common/hex.c \
utils/common/hex.h \
utils/common/lookup.c \
utils/common/lookup.h \
utils/common/msg.c \
utils/common/msg.h \
utils/common/netio.c \
......
......@@ -30,6 +30,8 @@
#include "utils/knotc/commands.h"
#include "utils/knotc/estimator.h"
#define CMD_EXIT "exit"
#define CMD_STATUS "status"
#define CMD_STOP "stop"
#define CMD_RELOAD "reload"
......@@ -97,10 +99,10 @@ static int check_conf_args(cmd_args_t *args)
return KNOT_EINVAL;
}
static int get_conf_key(char *key, knot_ctl_data_t *data)
static int get_conf_key(const char *key, knot_ctl_data_t *data)
{
// Get key0.
char *key0 = key;
const char *key0 = key;
// Check for id.
char *id = strchr(key, '[');
......@@ -410,7 +412,8 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
.node_table = hattrie_create_n(TRIE_BUCKET_SIZE, &mem_ctx),
};
char *zone_name = knot_dname_to_str_alloc(dname);
char buff[KNOT_DNAME_TXT_MAXLEN + 1];
char *zone_name = knot_dname_to_str(buff, dname, sizeof(buff));
char *zone_file = conf_zonefile(conf(), dname);
zs_scanner_t *zs = malloc(sizeof(zs_scanner_t));
......@@ -419,7 +422,6 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
log_zone_error(dname, "%s", knot_strerror(KNOT_ENOMEM));
hattrie_free(est.node_table);
free(zone_file);
free(zone_name);
free(zs);
return KNOT_ENOMEM;
}
......@@ -434,13 +436,11 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
hattrie_apply_rev(est.node_table, estimator_free_trie_node, NULL);
hattrie_free(est.node_table);
free(zone_file);
free(zone_name);
zs_deinit(zs);
free(zs);
return KNOT_EPARSEFAIL;
}
free(zone_file);
free(zone_name);
zs_deinit(zs);
free(zs);
......@@ -663,48 +663,40 @@ static int cmd_conf_ctl(cmd_args_t *args)
}
const cmd_desc_t cmd_table[] = {
{ CMD_EXIT, NULL, CTL_NONE },
{ CMD_STATUS, cmd_ctl, CTL_STATUS },
{ CMD_STOP, cmd_ctl, CTL_STOP },
{ CMD_RELOAD, cmd_ctl, CTL_RELOAD },
{ CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_CONF_FREAD },
{ CMD_ZONE_STATUS, cmd_zone_ctl, CTL_ZONE_STATUS },
{ CMD_ZONE_RELOAD, cmd_zone_ctl, CTL_ZONE_RELOAD },
{ CMD_ZONE_REFRESH, cmd_zone_ctl, CTL_ZONE_REFRESH },
{ CMD_ZONE_RETRANSFER, cmd_zone_ctl, CTL_ZONE_RETRANSFER },
{ CMD_ZONE_FLUSH, cmd_zone_ctl, CTL_ZONE_FLUSH },
{ CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN },
{ CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_IMPORT, cmd_conf_import, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_EXPORT, cmd_conf_export, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_LIST, cmd_conf_ctl, CTL_CONF_LIST, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_READ, cmd_conf_ctl, CTL_CONF_READ, CMD_CONF_FOPT_ITEM },
{ CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_CONF_FOPT_ZONE | CMD_CONF_FREAD },
{ CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_CONF_FOPT_ZONE | CMD_CONF_FREAD },
{ CMD_ZONE_STATUS, cmd_zone_ctl, CTL_ZONE_STATUS, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_RELOAD, cmd_zone_ctl, CTL_ZONE_RELOAD, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_REFRESH, cmd_zone_ctl, CTL_ZONE_REFRESH, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_RETRANSFER, cmd_zone_ctl, CTL_ZONE_RETRANSFER, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_FLUSH, cmd_zone_ctl, CTL_ZONE_FLUSH, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN, CMD_CONF_FOPT_ZONE },
{ CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_IMPORT, cmd_conf_import, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_EXPORT, cmd_conf_export, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_LIST, cmd_conf_ctl, CTL_CONF_LIST, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_READ, cmd_conf_ctl, CTL_CONF_READ, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_BEGIN, cmd_conf_ctl, CTL_CONF_BEGIN },
{ CMD_CONF_COMMIT, cmd_conf_ctl, CTL_CONF_COMMIT },
{ CMD_CONF_ABORT, cmd_conf_ctl, CTL_CONF_ABORT },
{ CMD_CONF_DIFF, cmd_conf_ctl, CTL_CONF_DIFF, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_GET, cmd_conf_ctl, CTL_CONF_GET, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_SET, cmd_conf_ctl, CTL_CONF_SET, CMD_CONF_FREQ_ITEM | CMD_CONF_FOPT_DATA },
{ CMD_CONF_UNSET, cmd_conf_ctl, CTL_CONF_UNSET, CMD_CONF_FOPT_ITEM | CMD_CONF_FOPT_DATA },
{ NULL }
};
const cmd_desc_old_t cmd_table_old[] = {
{ "checkzone", CMD_ZONE_CHECK },
{ "memstats", CMD_ZONE_MEMSTATS },
{ "zonestatus", CMD_ZONE_STATUS },
{ "refresh", CMD_ZONE_REFRESH },
{ "flush", CMD_ZONE_FLUSH },
{ "signzone", CMD_ZONE_SIGN },
{ "checkconf", CMD_CONF_CHECK },
{ "conf-desc", CMD_CONF_LIST },
{ CMD_CONF_DIFF, cmd_conf_ctl, CTL_CONF_DIFF, CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_TXN },
{ CMD_CONF_GET, cmd_conf_ctl, CTL_CONF_GET, CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_TXN },
{ CMD_CONF_SET, cmd_conf_ctl, CTL_CONF_SET, CMD_CONF_FREQ_ITEM | CMD_CONF_FOPT_DATA | CMD_CONF_FREQ_TXN },
{ CMD_CONF_UNSET, cmd_conf_ctl, CTL_CONF_UNSET, CMD_CONF_FOPT_ITEM | CMD_CONF_FOPT_DATA | CMD_CONF_FREQ_TXN },
{ NULL }
};
const cmd_help_t cmd_help_table[] = {
static const cmd_help_t cmd_help_table[] = {
{ CMD_EXIT, "", "Exit interactive mode." },
{ "", "", "" },
{ CMD_STATUS, "", "Check if the server is running." },
{ CMD_STOP, "", "Stop the server if running." },
{ CMD_RELOAD, "", "Reload the server configuration and modified zones." },
......@@ -733,3 +725,18 @@ const cmd_help_t cmd_help_table[] = {
{ CMD_CONF_UNSET, "[<item>] [<data>...]", "Unset the item data in the transaction." },
{ NULL }
};
void print_commands(void)
{
printf("\nActions:\n");
for (const cmd_help_t *cmd = cmd_help_table; cmd->name != NULL; cmd++) {
printf(" %-15s %-20s %s\n", cmd->name, cmd->params, cmd->desc);
}
printf("\n"
"Note:\n"
" Empty <zone> parameter means all zones.\n"
" Type <item> parameter in the form of <section>[<identifier>].<name>.\n"
" (*) indicates a local operation which requires a configuration.\n");
}
......@@ -41,6 +41,8 @@ typedef enum {
CMD_CONF_FOPT_ITEM = 1 << 2, /*!< Optional item argument. */
CMD_CONF_FREQ_ITEM = 1 << 3, /*!< Required item argument. */
CMD_CONF_FOPT_DATA = 1 << 4, /*!< Optional item data argument. */
CMD_CONF_FOPT_ZONE = 1 << 5, /*!< Optional zone name argument. */
CMD_CONF_FREQ_TXN = 1 << 6, /*!< Required opened confdb transaction. */
} cmd_conf_flag_t;
struct cmd_desc;
......@@ -51,7 +53,7 @@ typedef struct {
const cmd_desc_t *desc;
knot_ctl_t *ctl;
int argc;
char **argv;
const char **argv;
cmd_flag_t flags;
} cmd_args_t;
......@@ -63,12 +65,6 @@ struct cmd_desc {
cmd_conf_flag_t flags;
};
/*! \brief Old command name translation. */
typedef struct {
const char *old_name;
const char *new_name;
} cmd_desc_old_t;
/*! \brief Command description. */
typedef struct {
const char *name;
......@@ -79,10 +75,7 @@ typedef struct {
/*! \brief Table of commands. */
extern const cmd_desc_t cmd_table[];
/*! \brief Table of command translations. */
extern const cmd_desc_old_t cmd_table_old[];
/*! \brief Table of command descriptions. */
extern const cmd_help_t cmd_help_table[];
/*! \brief Prints commands help. */
void print_commands(void);
/*! @} */
/* Copyright (C) 2016 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <histedit.h>
#include "knot/common/log.h"
#include "utils/common/lookup.h"
#include "utils/knotc/interactive.h"
#include "utils/knotc/commands.h"
#include "contrib/string.h"
#define PROGRAM_NAME "knotc"
#define HISTORY_FILE ".knotc_history"
extern params_t params;
static void cmds_lookup(EditLine *el, const char *str, size_t str_len)
{
lookup_t lookup;
int ret = lookup_init(&lookup);
if (ret != KNOT_EOK) {
return;
}
// Fill the lookup with command names.
for (const cmd_desc_t *desc = cmd_table; desc->name != NULL; desc++) {
ret = lookup_insert(&lookup, desc->name, NULL);
if (ret != KNOT_EOK) {
goto cmds_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, true);
cmds_lookup_finish:
lookup_deinit(&lookup);
}
static void local_zones_lookup(EditLine *el, const char *str, size_t str_len)
{
lookup_t lookup;
int ret = lookup_init(&lookup);
if (ret != KNOT_EOK) {
return;
}
char buff[KNOT_DNAME_TXT_MAXLEN + 1];
// Fill the lookup with local zone names.
for (conf_iter_t iter = conf_iter(conf(), C_ZONE);
iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) {
conf_val_t val = conf_iter_id(conf(), &iter);
char *name = knot_dname_to_str(buff, conf_dname(&val), sizeof(buff));
ret = lookup_insert(&lookup, name, NULL);
if (ret != KNOT_EOK) {
goto local_zones_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, true);
local_zones_lookup_finish:
lookup_deinit(&lookup);
}
static char* get_id_name(const char *section)
{
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
desc++;
}
assert(desc->name != NULL);
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section
};
knot_ctl_t *ctl = NULL;
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Try to get the first group item (possible id).
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK ||
type != KNOT_CTL_TYPE_DATA || reply[KNOT_CTL_IDX_ERROR] != NULL) {
unset_ctl(ctl);
return NULL;
}
char *id_name = strdup(reply[KNOT_CTL_IDX_ITEM]);
unset_ctl(ctl);
return id_name;
}
static void id_lookup(EditLine *el, const char *str, size_t str_len,
const cmd_desc_t *cmd_desc, const char *section, bool add_space)
{
// Decide which confdb transaction to ask.
unsigned ctl_code = (cmd_desc->flags & CMD_CONF_FREQ_TXN) ?
CTL_CONF_GET : CTL_CONF_READ;
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != ctl_code) {
desc++;
}
assert(desc->name != NULL);
char *id_name = get_id_name(section);
if (id_name == NULL) {
return;
}
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section,
[KNOT_CTL_IDX_ITEM] = id_name
};
lookup_t lookup;
knot_ctl_t *ctl = NULL;
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
lookup_init(&lookup) != KNOT_EOK) {
unset_ctl(ctl);
free(id_name);
return;
}
free(id_name);
while (true) {
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Receive one section id.
if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
goto id_lookup_finish;
}
// Stop if finished transfer.
if (type != KNOT_CTL_TYPE_DATA) {
break;
}
// Insert the id into the lookup.
if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
lookup_insert(&lookup, reply[KNOT_CTL_IDX_DATA], NULL) != KNOT_EOK) {
goto id_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, add_space);
id_lookup_finish:
lookup_deinit(&lookup);
unset_ctl(ctl);
}
static void list_lookup(EditLine *el, const char *section, const char *item)
{
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
desc++;
}
assert(desc->name != NULL);
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section
};
lookup_t lookup;
knot_ctl_t *ctl = NULL;
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
lookup_init(&lookup) != KNOT_EOK) {
unset_ctl(ctl);
return;
}
while (true) {
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Receive one section/item name.
if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
goto list_lookup_finish;
}
// Stop if finished transfer.
if (type != KNOT_CTL_TYPE_DATA) {
break;
}
const char *str = (section == NULL) ? reply[KNOT_CTL_IDX_SECTION] :
reply[KNOT_CTL_IDX_ITEM];
// Insert the name into the lookup.
if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
lookup_insert(&lookup, str, NULL) != KNOT_EOK) {
goto list_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, item, strlen(item), el, section != NULL);
list_lookup_finish:
lookup_deinit(&lookup);
unset_ctl(ctl);
}
static void item_lookup(EditLine *el, const char *str, const cmd_desc_t *cmd_desc)
{
// List all sections.
if (str == NULL) {
list_lookup(el, NULL, "");
return;
}
// Check for id specification.
char *id = (strchr(str, '['));
if (id != NULL) {
char *section = strndup(str, id - str);
// Check for completed id specification.
char *id_stop = (strchr(id, ']'));
if (id_stop != NULL) {
// Complete the item name.
if (*(id_stop + 1) == '.') {
list_lookup(el, section, id_stop + 2);
}
} else {
// Complete the section id.
id_lookup(el, id + 1, strlen(id + 1), cmd_desc, section, false);
}
free(section);
} else {
// Check for item specification.
char *dot = (strchr(str, '.'));
if (dot != NULL) {
// Complete the item name.
char *section = strndup(str, dot - str);
list_lookup(el, section, dot + 1);
free(section);
} else {
// Complete the section name.
list_lookup(el, NULL, str);
}
}
}
static unsigned char complete(EditLine *el, int ch)
{
int argc, token, 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, &token, &pos);
if (ret != 0) {
goto complete_exit;
}
// Show possible commands.
if (argc == 0) {
print_commands();
goto complete_exit;
}
// Complete the command name.
if (token == 0) {
cmds_lookup(el, argv[0], pos);
goto complete_exit;
}
// Find the command descriptor.
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && strcmp(desc->name, argv[0]) != 0) {
desc++;
}
if (desc->name == NULL) {
goto complete_exit;
}
if (token > 1 || desc->flags == CMD_CONF_FNONE ||
desc->flags == CMD_CONF_FREAD ||
desc->flags == CMD_CONF_FWRITE) {
goto complete_exit;
}
ret = set_config(desc, &params);
if (ret != KNOT_EOK) {
goto complete_exit;
}
// Complete the zone name.
if (desc->flags & CMD_CONF_FOPT_ZONE) {
if (desc->flags & CMD_CONF_FREAD) {
local_zones_lookup(el, argv[1], pos);
} else {
id_lookup(el, argv[1], pos, desc, "zone", true);
}
goto complete_exit;
}
// Complete the section/id/item name.
if (desc->flags & (CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_ITEM)) {
item_lookup(el, argv[1], desc);
goto complete_exit;
}
complete_exit:
conf_update(NULL);
tok_reset(tok);
tok_end(tok);
return CC_REDISPLAY;
}
static char* prompt(EditLine *el)
{
return PROGRAM_NAME"> ";
}
int interactive_loop(params_t *params)
{
char *hist_file = NULL;
const char *home = getenv("HOME");
if (home != NULL) {
hist_file = sprintf_alloc("%s/%s", home, HISTORY_FILE);
}
if (hist_file == NULL) {
log_notice("failed to get home directory");
}
EditLine *el = el_init(PROGRAM_NAME, stdin, stdout, stderr);
if (el == NULL) {
log_error("interactive mode not available");
free(hist_file);
return KNOT_ERROR;
}
History *hist = history_init();
if (hist == NULL) {
log_error("interactive mode not available");
el_end(el);
free(hist_file);
return KNOT_ERROR;
}
HistEvent hev = { 0 };
history(hist, &hev, H_SETSIZE, 100);
el_set(el, EL_HIST, history, hist);
history(hist, &hev, H_LOAD, hist_file);
el_set(el, EL_TERMINAL, NULL);
el_set(el, EL_EDITOR, "emacs");
el_set(el, EL_PROMPT, prompt);
el_set(el, EL_SIGNAL, 1);
el_source(el, NULL);
el_set(el, EL_ADDFN, PROGRAM_NAME"-complete",
"Perform "PROGRAM_NAME" completion.", complete);
el_set(el, EL_BIND, "^I", PROGRAM_NAME"-complete", NULL);
int count;
const char *line;
while ((line = el_gets(el, &count)) != NULL && count > 0) {
history(hist, &hev, H_ENTER, line);
Tokenizer *tok = tok_init(NULL);