CLI Console
This article provides a very simple implementation of CLI interaction. The code basically shows how to leverage ‘string’ functions of libc. Notice the implementation of handler functions (e.g., cmd_start_scan) are omitted. The following code could be running in a thread.
/*****************************************************************************
@brief: Inspired by https://github.com/brenns10/lsh.git
*****************************************************************************/
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define LSH_TOK_BUFSIZE (64)
#define LSH_RL_BUFSIZE (1024)
#define LSH_TOK_DELIM " \t\r\n\a"
typedef uint8_t (* cli_cmd_handler)(uint8_t len, char *param[]);
typedef struct _Cli_Cmd {
char * cmd;
char * help;
char * padding;
cli_cmd_handler handler;
uint8_t hidden;
} Cli_Cmd;
/*
`cmd_start` and `cmd_exit` could be implemented in other c files.
*/
static Cli_Cmd cmdsMap[] = {
{
"start", "@brief: start the program.", "\t",
cmd_start , 0
},
{
"exit", "@brief: exit the program.", "\t",
cmd_exit, 0
},
{ NULL, NULL, NULL, 0}
};
/**
* @brief: After split, the arguments contains the command name.
* The method is to get rid of the command name and return
* a list of string pointers to the arguments.
* Please free the return list.
*/
static char **get_true_arguments(uint8_t argc, char **argv)
{
uint8_t idx;
char **args = malloc(argc * sizeof(char*));
if(!args) {
fprintf(stderr, "cli: allocation error\n");
}
for(idx = 0; idx < argc; idx++) {
args[idx] = argv[idx + 1];
}
return args;
}
static uint8_t cli_help()
{
uint8_t idx;
for(idx = 0;; idx++) {
if(cmdsMap[idx].cmd == NULL ||
cmdsMap[idx].help == NULL) {
return 0;
} else if (!cmdsMap[idx].hidden) {
printf("%s %s- %s\r\n", cmdsMap[idx].cmd,
cmdsMap[idx].padding,
cmdsMap[idx].help);
}
}
return 0;
}
static void cli_execute(uint8_t argc, char **argv)
{
char **args;
int idx;
if(argv[0] == NULL) {
return;
}
if(strcmp(argv[0], "?") == 0) {
cli_help();
return;
}
for(idx = 0;; idx++) {
if(cmdsMap[idx].cmd &&
cmdsMap[idx].handler &&
strcmp(argv[0], cmdsMap[idx].cmd) == 0) {
args = get_true_arguments(argc, argv);
cmdsMap[idx].handler(argc, args);
free(args);
return;
} else if(!cmdsMap[idx].cmd ||
!cmdsMap[idx].handler) {
break;
}
}
printf("[console]: %s unknown command\r\n", argv[0]);
}
/**
* @brief: Read a line of input from stdin.
* @param[Out]: pointer to a string.
* @return: The line from stdin.
*/
static char *cli_read_line(void)
{
int bufsize = LSH_RL_BUFSIZE;
int position = 0;
int c;
char *buffer;
buffer = (char *)malloc(sizeof(char) * bufsize);
if(!buffer) {
fprintf(stderr, "[console]: allocation error\n");
}
while (1) {
// Read a character
c = getchar();
if(c == EOF) {
exit(EXIT_SUCCESS);
} else if(c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if(position >= bufsize) {
bufsize += LSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "[console]: allocation error\n");
}
}
}
}
/**
* @brief: Split a line into tokens (very naively).
* @param[In]: line - pointer to a string.
* @param[Out]: argc - pointer to argument count.
* @param[Out]: argv - NULL terminated array of tokens.
*/
static char **cli_split_line(char *line, uint8_t *argc)
{
uint8_t position = 0;
int bufsize = LSH_TOK_BUFSIZE;
char **tokens = malloc(bufsize * sizeof(char*));
char *token, **tokens_backup;
if(!tokens) {
fprintf(stderr, "[console]: allocation error\n");
}
token = strtok(line, LSH_TOK_DELIM);
while(token != NULL) {
tokens[position] = token;
position++;
if(position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens_backup = tokens;
tokens = realloc(tokens, bufsize * sizeof(char*));
if(!tokens) {
free(tokens_backup);
fprintf(stderr, "[console]: allocation error\n");
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
*argc = (position - 1 >= 0) ? (position - 1) : 0;
return tokens;
}
void console_initialize(void)
{
return;
}
void console_task_func(void *param)
{
uint8_t argc;
char *line;
char **argv;
for(;;) {
printf("$ ");
line = cli_read_line();
argv = cli_split_line(line, &argc);
cli_execute(argc, argv);
free(line);
free(argv);
usleep(50000); // 50ms
}
}