Commit b7f24e23 authored by Pavel Tvrdik's avatar Pavel Tvrdik
Browse files

CLI: Improved auto-completion behavior

Auto-complete keywords (for, where, filter, ...) and symbol names (names
of protocols, tables, ...). Client can request daemon for list of all
symbols using new cli command `refresh symbols`.

Next changes:
  - Behavior is configured by *.Y files using flags CLI_SF_*
  - The file doc/reply_codes was moved to header file
    client/reply_codes.h.
  - Share birdcl input_read() function code for birdc non-interactive
    mode.
  - BIRD daemon notifies the client about new symbol set.
  - BIRD pushes notification to the client about new symbol set and then
    the client should request a package with all symbols (`refresh
    symbols`).
  - File-based history of previous commands(). In interactive mode in
    birdc is stored history of all commands in ~/.birdc_history file.
  - BIRD daemon sends notification to clients about interface updates
  - Maintains a list of all connected cli clients to daemon. Daemon
    sends to all cli clients notification about interfaces states up and
    down.
parent 5de0e848
Loading
Loading
Loading
Loading
+47 −8
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
 */

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
@@ -20,6 +21,8 @@
#include "lib/string.h"
#include "client/client.h"

#define BIRDC_HISTORY_FILENAME 	".birdc_history"

static int input_hidden_end;
static int prompt_active;

@@ -78,7 +81,7 @@ static int
input_complete(int arg UNUSED, int key UNUSED)
{
  static int complete_flag;
  char buf[256];
  char buf[256] = {};

  if (rl_last_func != input_complete)
    complete_flag = 0;
@@ -137,20 +140,48 @@ input_help(int arg, int key UNUSED)
  return 0;
}

/**
 * get_rl_history_filepath - return the string with path to readline history file
 * @buf: buffer for writing
 * @size: size of @buf including a |NULL| terminator at the end
 *
 * This function returns the string with path to readline history file ($HOME/.birdc_history).
 * A function is copied from readline repository histfile.c from function history_filename()
 */
static char *
get_rl_history_filepath(char *buf, size_t size)
{
  const char *home = getenv("HOME");
  if (!home)
    return NULL;

  if (snprintf(buf, size, "%s/%s", home, BIRDC_HISTORY_FILENAME) >= size)
    return NULL;

  return buf;
}

void
input_init(void)
{
  char path[PATH_MAX];
  prompt_active = 0;

  if (interactive)
  {
    prompt_active = 1;

    rl_readline_name = "birdc";
    rl_add_defun("bird-complete", input_complete, '\t');
    rl_add_defun("bird-help", input_help, '?');
    read_history(get_rl_history_filepath(path, sizeof(path)));
    rl_callback_handler_install("bird> ", input_got_line);
  }

  // rl_get_screen_size();
  term_lns = LINES;
  term_cls = COLS;

  prompt_active = 1;

  // readline library does strange things when stdin is nonblocking.
  // if (fcntl(0, F_SETFL, O_NONBLOCK) < 0)
  //   DIE("fcntl");
@@ -197,7 +228,10 @@ input_notify(int prompt)
void
input_read(void)
{
  if (interactive)
    rl_callback_read_char();
  else
    simple_input_read();
}

void
@@ -213,9 +247,14 @@ more_end(void)
void
cleanup(void)
{
  char path[PATH_MAX];

  if (init)
    return;

  if (interactive)
    write_history(get_rl_history_filepath(path, sizeof(path)));

  input_hide();
  rl_callback_handler_remove();
}
+3 −47
Original line number Diff line number Diff line
@@ -22,8 +22,6 @@
#include "client/client.h"
#include "sysdep/unix/unix.h"

#define INPUT_BUF_LEN 2048

struct termios tty_save;

void
@@ -42,59 +40,17 @@ void
input_notify(int prompt)
{
  /* No ncurses -> no status to reveal/hide, print prompt manually. */
  if (!prompt)
  if (!prompt || !interactive)
    return;

  printf("bird> ");
  printf("\rbird> ");
  fflush(stdout);
}


static int
lastnb(char *str, int i)
{
  while (i--)
    if ((str[i] != ' ') && (str[i] != '\t'))
      return str[i];

  return 0;
}

void
input_read(void)
{
  char buf[INPUT_BUF_LEN];

  if ((fgets(buf, INPUT_BUF_LEN, stdin) == NULL) || (buf[0] == 0))
  {
    putchar('\n');
    cleanup();
    exit(0);
  }

  int l = strlen(buf);
  if ((l+1) == INPUT_BUF_LEN)
    {
      printf("Input too long.\n");
      return;
    }

  if (buf[l-1] == '\n')
    buf[--l] = '\0';

  if (!interactive)
    printf("%s\n", buf);

  if (l == 0)
    return;

  if (lastnb(buf, l) == '?')
    {
      cmd_help(buf, strlen(buf));
      return;
    }

  submit_command(buf);
  simple_input_read();
}

static struct termios stored_tty;
+168 −10
Original line number Diff line number Diff line
@@ -34,8 +34,11 @@
#include "lib/string.h"
#include "client/client.h"
#include "sysdep/unix/unix.h"
#include "client/reply_codes.h"

#define SERVER_READ_BUF_LEN 	4096
#define INPUT_BUF_LEN 		2048
#define REFRESH_SYMBOLS_CMD 	"refresh symbols" /* Name of cli command for retrieve new symbols from daemon */

static char *opt_list = "s:vrl";
static int verbose, restricted, once;
@@ -47,12 +50,16 @@ static byte server_read_buf[SERVER_READ_BUF_LEN];
static byte *server_read_pos = server_read_buf;

int init = 1;		/* During intial sequence */
int busy = 1;		/* Executing BIRD command */
int busy = 0;		/* Executing BIRD command */
int interactive;	/* Whether stdin is terminal */
int welcomed = 0;	/* Was welcome message with BIRD version printed out? */

static int num_lines, skip_input;
int term_lns, term_cls;

static list symbols;
static uint longest_symbol_len;


/*** Parsing of arguments ***/

@@ -107,7 +114,7 @@ parse_args(int argc, char **argv)
	  tmp += strlen(tmp);
	  *tmp++ = ' ';
	}
      tmp[-1] = 0;
      tmp[-1] = 0; /* Put ending null-terminator */

      once = 1;
      interactive = 0;
@@ -132,6 +139,12 @@ handle_internal_command(char *cmd)
      puts("Press `?' for context sensitive help.");
      return 1;
    }
  if (!strncmp(cmd, REFRESH_SYMBOLS_CMD, sizeof(REFRESH_SYMBOLS_CMD)-1))
  {
    retrieve_symbols();
    return 1;
  }

  return 0;
}

@@ -172,6 +185,41 @@ submit_command(char *cmd_raw)
  free(cmd);
}

static void
add_to_symbols(int flag, const char *name)
{
  struct cli_symbol *sym = malloc(sizeof(struct cli_symbol));
  sym->flags = flag;

  sym->len = strlen(name);
  char *name_ = malloc(sym->len + 1);
  memcpy(name_, name, sym->len + 1);
  sym->name = name_;
  add_tail(&symbols, &sym->n);

  if (longest_symbol_len < sym->len)
    longest_symbol_len = sym->len;
}

void
retrieve_symbols(void)
{
  /* Purge old symbols */
  list *syms = cli_get_symbol_list();
  struct cli_symbol *sym, *next;
  WALK_LIST_DELSAFE(sym, next, *syms)
  {
    rem_node(&sym->n);
    free((char *) sym->name);
    free(sym);
  }

  add_to_symbols(CLI_SF_KW_ALL, "all");
  add_to_symbols(CLI_SF_KW_OFF, "off");

  submit_server_command(REFRESH_SYMBOLS_CMD);
}

static void
init_commands(void)
{
@@ -198,6 +246,13 @@ init_commands(void)
      exit(0);
    }

  init_list(&symbols);
  longest_symbol_len = 1; /* Be careful, it's used as denominator! */

  /* In symbol list is a BIRD version for welcome message too */
  if (interactive)
    retrieve_symbols();

  input_init();

  term_lns = (term_lns > 0) ? term_lns : 25;
@@ -262,25 +317,83 @@ server_connect(void)
    DIE("fcntl");
}

list *
cli_get_symbol_list(void)
{
  return &symbols;
}

uint
cli_get_symbol_maxlen(void)
{
  return longest_symbol_len;
}

static void
process_internal_message(int reply_code, const char *name)
{
  u32 flag = 0;

  switch (reply_code)
  {
  case RC_BIRD_VERSION_NUM:
    if (interactive && !welcomed)
    {
      welcomed = 1;
      printf("BIRD %s ready.\n", name);
    }
    return;

  case RC_NOTIFY:
    if (interactive)
      retrieve_symbols();
    return;

  /* Symbols */
  case RC_CONSTANT_NAME:	flag = CLI_SF_CONSTANT; break;
  case RC_VARIABLE_NAME:	flag = CLI_SF_VARIABLE; break;
  case RC_FILTER_NAME:		flag = CLI_SF_FILTER; break;
  case RC_FUNCTION_NAME:	flag = CLI_SF_FUNCTION; break;
  case RC_PROTOCOL_NAME:	flag = CLI_SF_PROTOCOL; break;
  case RC_TABLE_NAME:		flag = CLI_SF_TABLE; break;
  case RC_TEMPLATE_NAME:	flag = CLI_SF_TEMPLATE; break;
  case RC_INTERFACE_NAME:	flag = CLI_SF_INTERFACE; break;
  default:
    printf("Undefined %d: %s", reply_code, name);
    return;
  }

  if (flag && name && *name)
    add_to_symbols(flag, name);
}

#define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0)

static void
server_got_reply(char *x)
{
  int code;
  int code = 0;
  int len = 0;

  if (*x == '+')                        /* Async reply */
  if (*x == '+') {                       /* Async reply */
    busy = 1;
    input_notify(0);
    PRINTF(len, ">>> %s\n", x+1);
  }
  else if (x[0] == ' ')                 /* Continuation */
    PRINTF(len, "%s%s\n", verbose ? "     " : "", x+1);
  else if (strlen(x) > 4 &&
           sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 &&
           (x[4] == ' ' || x[4] == '-'))
    {
      if (code)
      if (code >= 3000 && code < 4000)
      {
	process_internal_message(code, x+5);
      }
      else if (code)
      {
	PRINTF(len, "%s\n", verbose ? x : x+5);
      }

      if (x[4] == ' ')
      {
@@ -341,6 +454,51 @@ server_read(void)
    }
}

static int
lastnb(char *str, int i)
{
  while (i--)
    if ((str[i] != ' ') && (str[i] != '\t'))
      return str[i];

  return 0;
}

void
simple_input_read(void)
{
  char buf[INPUT_BUF_LEN];

  if ((fgets(buf, INPUT_BUF_LEN, stdin) == NULL) || (buf[0] == 0))
  {
    if (interactive)
      putchar('\n');
    cleanup();
    exit(0);
  }

  int l = strlen(buf);
  if ((l+1) == INPUT_BUF_LEN)
    {
      printf("Input too long.\n");
      return;
    }

  if (buf[l-1] == '\n')
    buf[--l] = '\0';

  if (l == 0)
    return;

  if (lastnb(buf, l) == '?')
    {
      cmd_help(buf, strlen(buf));
      return;
    }

  submit_command(buf);
}

static void
select_loop(void)
{
+36 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#ifndef _BIRD_CLIENT_H_
#define _BIRD_CLIENT_H_

extern int init, busy, interactive;
extern int term_lns, term_cls;
@@ -33,7 +35,41 @@ char *cmd_expand(char *cmd);

/* client.c */

/* Client Symbol Flags: Types */
#define CLI_SF_CONSTANT		(1 << 0)
#define CLI_SF_VARIABLE		(1 << 1)
#define CLI_SF_FILTER		(1 << 2)
#define CLI_SF_FUNCTION		(1 << 3)
#define CLI_SF_PROTOCOL		(1 << 4)
#define CLI_SF_TABLE		(1 << 5)
#define CLI_SF_TEMPLATE		(1 << 6)
#define CLI_SF_INTERFACE	(1 << 7)

#define CLI_SF_OPTIONAL		(1 << 8) /* This node is optional not mandatory */
#define CLI_SF_PARAMETER	(1 << 9) /* A parameter/word will follow after this node */

/* Client Symbol Flags: Keywords */
#define CLI_SF_KW_ALL		(1 << 10)
#define CLI_SF_KW_OFF 		(1 << 11)


struct cli_symbol
{
  node n;
  const char *name;
  uint len;
  u32 flags;			/* CLI_SF_* */
};

void submit_command(char *cmd_raw);

/* die() with system error messages */
#define DIE(x, y...) die(x ": %s", ##y, strerror(errno))

void retrieve_symbols(void);
void add_keywords_to_symbols(void);
list *cli_get_symbol_list(void);
uint cli_get_symbol_maxlen(void);
void simple_input_read(void);

#endif
+124 −31
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@
#include "client/client.h"

struct cmd_info {
  /* use for build cmd tree and cli commands */
  char *command;
  char *args;
  char *help;

  /* only for build tree */
  int is_real_cmd;
  u32 flags;			/* Mask of (CLI_SF_*) */
};

static struct cmd_info command_table[] = {
@@ -27,11 +31,15 @@ static struct cmd_info command_table[] = {
};

struct cmd_node {
  struct cmd_node *sibling, *son, **plastson;
  struct cmd_info *cmd, *help;
  int len;
  signed char prio;
  char token[1];
  struct cmd_node *sibling;
  struct cmd_node *son;
  struct cmd_node **plastson;	/* Helping pointer to son */
  struct cmd_info *cmd;		/* Short info */
  struct cmd_info *help;	/* Detailed info */
  signed char prio; 		/* Priority */
  u32 flags;			/* Mask of (CLI_SF_*) */
  int len; 			/* Length of string in token */
  char token[1];		/* Name of command */
};

static struct cmd_node cmd_root;
@@ -78,6 +86,7 @@ cmd_build_tree(void)
	old->cmd = cmd;
      else
	old->help = cmd;
      old->flags |= cmd->flags;
    }
}

@@ -134,6 +143,14 @@ cmd_list_ambiguous(struct cmd_node *root, char *cmd, int len)
  for(m=root->son; m; m=m->sibling)
    if (m->len > len && !memcmp(m->token, cmd, len))	
      cmd_display_help(m->help, m->cmd);

  struct cli_symbol *sym;
  list *syms = cli_get_symbol_list();
  WALK_LIST(sym, *syms)
  {
    if ((sym->flags & root->flags) && sym->len > len && memcmp(sym->name, cmd, len) == 0)
      printf("%s\n", sym->name);
  }
}

void
@@ -170,14 +187,43 @@ cmd_help(char *cmd, int len)
    cmd_display_help(m->help, m->cmd);
}

/*
 * Return length of common prefix of all matches,
 * Write common prefix string into buf
 */
static int
cmd_merge_match_with_others(int max_common_len, const char *token_name, int token_len, char *buf, int from)
{
  if (max_common_len < 0)
  {
    /* For a case that we'll have exactly one match */
    strcpy(buf, token_name + from);
    max_common_len = token_len - from;
  }
  else
  {
    int i = 0;
    while (i < max_common_len && i < token_len - from && buf[i] == token_name[from+i])
      i++;
    max_common_len = i;
  }
  return max_common_len;
}

/*
 * Return length of common prefix of all matches,
 * Write count of all matches into pcount,
 * Write common prefix string into buf
 */
static int
cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, char *buf)
{
  struct cmd_node *m;
  int best, best_prio, i;
  int max_common_len;
  int best_prio;

  *pcount = 0;
  best = -1;
  max_common_len = -1;
  best_prio = -1;
  for(m=root->son; m; m=m->sibling)
    {
@@ -190,25 +236,31 @@ cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, ch
      if (best_prio < m->prio)
	{
	  *pcount = 0;
	  best = -1;
	  max_common_len = -1;
	}

      (*pcount)++;
      if (best < 0)
	{
	  strcpy(buf, m->token + len);
	  best = m->len - len;
      if (max_common_len < 0)
	best_prio = m->prio;

      (*pcount)++;
      max_common_len = cmd_merge_match_with_others(max_common_len, m->token, m->len, buf, len);
    }
      else

  list *syms = cli_get_symbol_list();
  struct cli_symbol *sym;
  WALK_LIST(sym, *syms)
  {
	  i = 0;
	  while (i < best && i < m->len - len && buf[i] == m->token[len+i])
	    i++;
	  best = i;
	}
    if (!(sym->flags & root->flags))
      continue;

    if (sym->len < len || memcmp(sym->name, cmd, len))
      continue;

    (*pcount)++;
    max_common_len = cmd_merge_match_with_others(max_common_len, sym->name, sym->len, buf, len);
  }
  return best;

  return max_common_len;
}

int
@@ -217,7 +269,7 @@ cmd_complete(char *cmd, int len, char *buf, int again)
  char *start = cmd;
  char *end = cmd + len;
  char *fin;
  struct cmd_node *n, *m;
  struct cmd_node *n, *m = NULL;
  char *z;
  int ambig, cnt = 0, common;

@@ -227,7 +279,7 @@ cmd_complete(char *cmd, int len, char *buf, int again)

  /* Find the context */
  n = &cmd_root;
  while (cmd < fin && n->son)
  while (cmd < fin)
    {
      if (isspace(*cmd))
	{
@@ -249,12 +301,39 @@ cmd_complete(char *cmd, int len, char *buf, int again)
	}
      if (!m)
	return -1;

      /* Try skip a parameter/word */
      if (m->flags & CLI_SF_PARAMETER)
      {
	z = cmd;

	/* Skip spaces before parameter */
	while (cmd < fin && isspace(*cmd))
	  cmd++;

	/* Skip one parameter/word */
	while (cmd < fin && !isspace(*cmd))
	  cmd++;

	/* Check ending of parameter */
	if (isspace(*cmd))
	{
	  if (m->flags & CLI_SF_OPTIONAL)
	    m = n;
	  continue;
	}
	else
	  cmd = z;
      }

      /* Do not enter to optional command nodes */
      if (!(m->flags & CLI_SF_OPTIONAL))
	n = m;
    }

  /* Completion of parameters is not yet supported */
  if (!n->son)
    return -1;
  /* Enter to the last command node */
  if (m && (m->flags & CLI_SF_PARAMETER))
    n = m;

  /* We know the context, let's try to complete */
  common = cmd_find_common_match(n, fin, end-fin, &cnt, buf);
@@ -282,8 +361,8 @@ cmd_complete(char *cmd, int len, char *buf, int again)
char *
cmd_expand(char *cmd)
{
  struct cmd_node *n, *m;
  char *c, *b, *args;
  struct cmd_node *n, *m, *last_real_cmd = NULL;
  char *c, *b, *args, *lrc_args = NULL;
  int ambig;

  args = c = cmd;
@@ -307,14 +386,28 @@ cmd_expand(char *cmd)
	  cmd_list_ambiguous(n, b, c-b);
	  return NULL;
	}

      args = c;
      n = m;

      if (m->cmd)
      {
	last_real_cmd = m;
	lrc_args = c;
      }
  if (!n->cmd)
    }

  if (!n->cmd && !last_real_cmd)
    {
      puts("No such command. Press `?' for help.");
      return NULL;
    }

  if (last_real_cmd && last_real_cmd != n)
  {
    n = last_real_cmd;
    args = lrc_args;
  }
  b = malloc(strlen(n->cmd->command) + strlen(args) + 1);
  sprintf(b, "%s%s", n->cmd->command, args);
  return b;
Loading