538 lines
16 KiB
C
538 lines
16 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#ifdef __cplusplus
|
|
extern "C"
|
|
{
|
|
#endif
|
|
#include "cJSON.h"
|
|
#include "sds/sds.h"
|
|
#include "monitor_private.h"
|
|
#include "monitor_cmd_assistant.h"
|
|
#include "monitor_utils.h"
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
struct stm_cmd_spec
|
|
{
|
|
const char *cmd_name;
|
|
const char *cmd_flags;
|
|
const char *cmd_hint;
|
|
int cmd_arity;
|
|
int cmd_first_key_offset;
|
|
};
|
|
|
|
struct stm_cmd_assistant
|
|
{
|
|
cJSON *cjson_root;
|
|
stm_cmd_assistant_completion_cb *cmd_completion_cb;
|
|
stm_cmd_assistant_hints_cb *cmd_hints_cb;
|
|
sds hints_result; // mallo and free for every hits
|
|
};
|
|
static __thread struct stm_cmd_assistant *__g_stm_cli_assistant = NULL;
|
|
|
|
static cJSON *stm_cli_register_cmd(cJSON *father_next_array, const char *cur_cmd_prefix)
|
|
{
|
|
cJSON *cur_level_item = NULL;
|
|
|
|
/* search current cmd (name is prefix) in father->next array[] */
|
|
int array_size = cJSON_GetArraySize(father_next_array);
|
|
for (int i = 0; i < array_size; i++)
|
|
{
|
|
cur_level_item = cJSON_GetArrayItem(father_next_array, i);
|
|
cJSON *cmd_name_item = cJSON_GetObjectItem(cur_level_item, "prefix");
|
|
if (cmd_name_item != NULL && (0 == strcmp(cur_cmd_prefix, cmd_name_item->valuestring)))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
cur_level_item = NULL;
|
|
}
|
|
}
|
|
if (NULL == cur_level_item)
|
|
{
|
|
/* if not exist, create new cmd (name is prefix) */
|
|
cur_level_item = cJSON_CreateObject();
|
|
cJSON_AddItemToObject(cur_level_item, "prefix", cJSON_CreateString(cur_cmd_prefix));
|
|
cJSON *new_cmd_next_array = cJSON_CreateArray();
|
|
cJSON_AddItemToObject(cur_level_item, "next", new_cmd_next_array);
|
|
|
|
/* insert into father->next array */
|
|
cJSON_AddItemToArray(father_next_array, cur_level_item);
|
|
}
|
|
else
|
|
{
|
|
; // already exist, do nothing
|
|
}
|
|
return cur_level_item;
|
|
}
|
|
|
|
/*
|
|
search json object by cli_cmd_line, if exsit , return 0, do nothing,
|
|
the search json object function can be used for register_usage();
|
|
if not exist, create new json object and insert into father->next array
|
|
*/
|
|
int stm_cmd_assistant_register(struct stm_cmd_assistant *aide, const char *cli_cmd_line)
|
|
{
|
|
int argc = 0;
|
|
if (NULL == aide->cjson_root)
|
|
{
|
|
aide->cjson_root = cJSON_CreateArray();
|
|
}
|
|
sds *array = sdssplitargs(cli_cmd_line, &argc);
|
|
cJSON *father_candidate_array = aide->cjson_root;
|
|
cJSON *cur_cmd_obj = NULL;
|
|
for (int i = 0; i < argc; i++)
|
|
{
|
|
cur_cmd_obj = stm_cli_register_cmd(father_candidate_array, array[i]);
|
|
father_candidate_array = cJSON_GetObjectItem(cur_cmd_obj, "next");
|
|
}
|
|
sdsfreesplitres(array, argc);
|
|
return 0;
|
|
}
|
|
|
|
static cJSON *stm_cli_search_cmd(cJSON *father_next_array, const char *cur_cmd_prefix)
|
|
{
|
|
cJSON *cur_level_item = NULL;
|
|
/* search current cmd (name is prefix) in father->next array[] */
|
|
int array_size = cJSON_GetArraySize(father_next_array);
|
|
for (int i = 0; i < array_size; i++)
|
|
{
|
|
cur_level_item = cJSON_GetArrayItem(father_next_array, i);
|
|
cJSON *cmd_name_item = cJSON_GetObjectItem(cur_level_item, "prefix");
|
|
if (cmd_name_item != NULL && (0 == strcmp(cur_cmd_prefix, cmd_name_item->valuestring)))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
cur_level_item = NULL;
|
|
}
|
|
}
|
|
return cur_level_item;
|
|
}
|
|
|
|
cJSON *stm_cmd_assistant_search(struct stm_cmd_assistant *aide, const char *cli_cmd_line)
|
|
{
|
|
int argc = 0;
|
|
sds *array = sdssplitargs(cli_cmd_line, &argc);
|
|
cJSON *father_candidate_array = aide->cjson_root;
|
|
cJSON *match_item = NULL;
|
|
for (int i = 0; i < argc; i++)
|
|
{
|
|
match_item = stm_cli_search_cmd(father_candidate_array, array[i]);
|
|
if (NULL == match_item)
|
|
{
|
|
return NULL;
|
|
}
|
|
father_candidate_array = cJSON_GetObjectItem(match_item, "next");
|
|
}
|
|
sdsfreesplitres(array, argc);
|
|
return match_item;
|
|
}
|
|
|
|
int stm_cmd_assistant_register_usage(struct stm_cmd_assistant *aide, const char *cli_cmd_line, const char *usage)
|
|
{
|
|
if (NULL == aide || NULL == cli_cmd_line || NULL == usage)
|
|
{
|
|
return -1;
|
|
}
|
|
if (strlen(cli_cmd_line) == 0)
|
|
{
|
|
cJSON *obj = cJSON_CreateObject();
|
|
cJSON_AddItemToObjectCS(obj, "usage", cJSON_CreateString(usage));
|
|
cJSON_AddItemToArray(aide->cjson_root, obj);
|
|
return 0;
|
|
}
|
|
|
|
cJSON *obj = stm_cmd_assistant_search(aide, cli_cmd_line);
|
|
if (NULL == obj)
|
|
{
|
|
stm_cmd_assistant_register(aide, cli_cmd_line);
|
|
obj = stm_cmd_assistant_search(aide, cli_cmd_line);
|
|
if (NULL == obj)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
cJSON_AddItemToObject(obj, "usage", cJSON_CreateString(usage));
|
|
return 0;
|
|
}
|
|
|
|
char *stm_cmd_assistant_verbose_print(int format)
|
|
{
|
|
struct stm_cmd_assistant *aide = __g_stm_cli_assistant;
|
|
if (NULL == aide->cjson_root)
|
|
{
|
|
return (char *)strdup("[]");
|
|
}
|
|
char *debug_print;
|
|
if (format == 1)
|
|
{
|
|
debug_print = cJSON_Print(aide->cjson_root);
|
|
}
|
|
else
|
|
{
|
|
debug_print = cJSON_PrintUnformatted(aide->cjson_root);
|
|
}
|
|
return debug_print;
|
|
}
|
|
|
|
char *stm_cmd_assistant_brief_print(void)
|
|
{
|
|
struct stm_cmd_assistant *aide = __g_stm_cli_assistant;
|
|
sds s = sdsempty();
|
|
s = sdscat(s, "Usage:\r\n");
|
|
int array_size = cJSON_GetArraySize(aide->cjson_root);
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *item = cJSON_GetArrayItem(aide->cjson_root, sz);
|
|
cJSON *cmd_name_item = cJSON_GetObjectItem(item, "prefix");
|
|
s = sdscatprintf(s, "\t%s\r\n", cmd_name_item->valuestring);
|
|
}
|
|
char *print_str = strdup(s);
|
|
sdsfree(s);
|
|
return print_str;
|
|
}
|
|
|
|
int stm_cmd_assistant_json_load(struct stm_cmd_assistant *aide, const char *json_str)
|
|
{
|
|
if (NULL == aide || NULL == json_str || strlen(json_str) == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
cJSON *root = cJSON_Parse(json_str);
|
|
if (NULL == root)
|
|
{
|
|
return -1;
|
|
}
|
|
aide->cjson_root = root;
|
|
return 0;
|
|
}
|
|
|
|
void stm_cmd_assistant_free(struct stm_cmd_assistant *aide)
|
|
{
|
|
if (aide)
|
|
{
|
|
if (aide->cjson_root)
|
|
{
|
|
cJSON_Delete(aide->cjson_root);
|
|
}
|
|
if (aide->hints_result)
|
|
{
|
|
sdsfree(aide->hints_result);
|
|
}
|
|
free(aide);
|
|
}
|
|
}
|
|
|
|
struct stm_cmd_assistant *stm_cmd_assistant_new(void)
|
|
{
|
|
struct stm_cmd_assistant *aide = calloc(1, sizeof(struct stm_cmd_assistant));
|
|
aide->cjson_root = NULL;
|
|
aide->hints_result = NULL;
|
|
__g_stm_cli_assistant = aide;
|
|
return aide;
|
|
}
|
|
|
|
struct stm_cmd_assistant *stm_cmd_assistant_get(void)
|
|
{
|
|
return __g_stm_cli_assistant;
|
|
}
|
|
|
|
static int stm_cmd_exist(struct stm_cmd_assistant *aide, const char *cmd_name)
|
|
{
|
|
cJSON *root = aide->cjson_root;
|
|
if (NULL == root)
|
|
{
|
|
return 0;
|
|
}
|
|
int array_size = cJSON_GetArraySize(root);
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *item = cJSON_GetArrayItem(root, sz);
|
|
cJSON *cmd_name_item = cJSON_GetObjectItem(item, "prefix");
|
|
if (cmd_name_item && cmd_name_item->valuestring)
|
|
{
|
|
if (strcasecmp(cmd_name, cmd_name_item->valuestring) == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* return value:
|
|
* 0: success
|
|
* -1: failed
|
|
* 1: already exist
|
|
*/
|
|
int stm_cmd_assistant_register_cmd(struct stm_cmd_assistant *aide, const char *cmd_name, void *cmd_cb, void *cmd_arg,
|
|
const char *flags, const char *hint, const char *desc)
|
|
{
|
|
if (NULL == aide || NULL == cmd_name || NULL == cmd_cb)
|
|
{
|
|
return -1;
|
|
}
|
|
if (stm_cmd_exist(aide, cmd_name))
|
|
{
|
|
return 1;
|
|
}
|
|
if (stm_strncasecmp_exactly(flags, "readonly", 8) != 0 && stm_strncasecmp_exactly(flags, "write", 5) != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
cJSON *obj = cJSON_CreateObject();
|
|
cJSON_AddItemToObject(obj, "prefix", cJSON_CreateString(cmd_name));
|
|
cJSON_AddItemToObject(obj, "flags", cJSON_CreateString(flags));
|
|
cJSON_AddItemToObject(obj, "hints", cJSON_CreateString(hint));
|
|
cJSON_AddItemToObject(obj, "desc", cJSON_CreateString(desc));
|
|
|
|
char tmp_str[32] = {};
|
|
snprintf(tmp_str, sizeof(tmp_str), "%p", cmd_cb);
|
|
cJSON_AddItemToObject(obj, "cmd_cb", cJSON_CreateString(tmp_str));
|
|
|
|
snprintf(tmp_str, sizeof(tmp_str), "%p", cmd_arg);
|
|
cJSON_AddItemToObject(obj, "cmd_arg", cJSON_CreateString(tmp_str));
|
|
|
|
if (NULL == aide->cjson_root)
|
|
{
|
|
aide->cjson_root = cJSON_CreateArray();
|
|
}
|
|
cJSON_AddItemToArray(aide->cjson_root, obj);
|
|
return 0;
|
|
}
|
|
|
|
char *stm_cmd_assistant_serialize(struct stm_cmd_assistant *aide)
|
|
{
|
|
if (NULL == aide)
|
|
{
|
|
return NULL;
|
|
}
|
|
char *debug_print = cJSON_Print(aide->cjson_root);
|
|
return debug_print;
|
|
}
|
|
|
|
int stm_cmd_assistant_dserialize(struct stm_cmd_assistant *aide, const char *json)
|
|
{
|
|
cJSON *tmp_root = cJSON_Parse(json);
|
|
if (NULL == tmp_root)
|
|
{
|
|
return -1;
|
|
}
|
|
aide->cjson_root = tmp_root;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
return value:
|
|
0: equal exactly, uesed for hints, or get_cmd_cb()
|
|
-1: cli_array < register_cmd_array //raw command1 and command2 line has same section, and command1 line is shorter than command2, used for tab auto completion
|
|
1: cli_array > register_cmd_array //raw command1 and command2 line has same section, and command1 line is longer than command2, used for get_cmd_cb()
|
|
-2: not match any secions
|
|
*/
|
|
int stm_cmd_assistant_sds_compare(sds *cli_array, int cli_argc, sds *register_cmd_array, int register_argc)
|
|
{
|
|
if (0 == cli_argc || 0 == register_argc)
|
|
{
|
|
return -2;
|
|
}
|
|
// compare the first n-1 words, must be exactly match
|
|
int min_argc = MIN(cli_argc, register_argc);
|
|
for (int i = 0; i < min_argc - 1; i++)
|
|
{
|
|
// previous words must be exactly match, so use strcasecmp()
|
|
if (strcasecmp(cli_array[i], register_cmd_array[i]) != 0)
|
|
{
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
// compare the last common word use substring match
|
|
if (strncasecmp(cli_array[min_argc - 1], register_cmd_array[min_argc - 1], strlen(cli_array[min_argc - 1])) == 0)
|
|
{
|
|
if (strcasecmp(cli_array[min_argc - 1], register_cmd_array[min_argc - 1]) == 0)
|
|
{
|
|
if (cli_argc == register_argc)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (cli_argc < register_argc)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cli command is not complete
|
|
return -1;
|
|
}
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
int stm_cmd_assistant_set_completion_cb(struct stm_cmd_assistant *aide, stm_cmd_assistant_completion_cb *cb)
|
|
{
|
|
aide->cmd_completion_cb = cb;
|
|
return 0;
|
|
}
|
|
|
|
int stm_cmd_assistant_set_hints_cb(struct stm_cmd_assistant *aide, stm_cmd_assistant_hints_cb *cb)
|
|
{
|
|
aide->cmd_hints_cb = cb;
|
|
return 0;
|
|
}
|
|
|
|
static void cjson_traversal_completion(struct stm_cmd_assistant *aide, sds *sds_cli_array, int cli_argc, cJSON *cjson_root, void *arg)
|
|
{
|
|
int array_size = cJSON_GetArraySize(cjson_root);
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *array_item = cJSON_GetArrayItem(cjson_root, sz);
|
|
cJSON *prefix_item = cJSON_GetObjectItem(array_item, "prefix");
|
|
int register_cmd_section_num;
|
|
sds *register_cmd_array = sdssplitargs(prefix_item->valuestring, ®ister_cmd_section_num);
|
|
|
|
int match = stm_cmd_assistant_sds_compare(sds_cli_array, cli_argc, register_cmd_array, register_cmd_section_num);
|
|
sdsfreesplitres(register_cmd_array, register_cmd_section_num);
|
|
if (match == 0 || match == -1)
|
|
{
|
|
aide->cmd_completion_cb(arg, prefix_item->valuestring);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void stm_cmd_assistant_completion_cb_fun(struct stm_cmd_assistant *aide, const char *buf, void *arg)
|
|
{
|
|
/* input cmd buf must <= registed command
|
|
if buf_len < command_len ,auto completion the longest prefix
|
|
if buf_len == command_len, add a blank space
|
|
*/
|
|
int argc = 0;
|
|
sds *cli_cmd_array = sdssplitargs(buf, &argc);
|
|
cjson_traversal_completion(aide, cli_cmd_array, argc, aide->cjson_root, arg);
|
|
sdsfreesplitres(cli_cmd_array, argc);
|
|
}
|
|
|
|
void stm_cmd_assistant_input_line(struct stm_cmd_assistant *aide, const char *line, void *arg)
|
|
{
|
|
stm_cmd_assistant_completion_cb_fun(aide, line, arg);
|
|
}
|
|
|
|
static const char *cjson_traversal_hints(sds *sds_cli_array, int cli_argc, cJSON *cjson_root)
|
|
{
|
|
int array_size = cJSON_GetArraySize(cjson_root);
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *array_item = cJSON_GetArrayItem(cjson_root, sz);
|
|
cJSON *prefix_item = cJSON_GetObjectItem(array_item, "prefix");
|
|
int register_cmd_section_num;
|
|
sds *register_cmd_array = sdssplitargs(prefix_item->valuestring, ®ister_cmd_section_num);
|
|
|
|
int match = stm_cmd_assistant_sds_compare(sds_cli_array, cli_argc, register_cmd_array, register_cmd_section_num);
|
|
sdsfreesplitres(register_cmd_array, register_cmd_section_num);
|
|
if (match == 0) // hints must be exactly match
|
|
{
|
|
cJSON *hint_item = cJSON_GetObjectItem(array_item, "hints");
|
|
if (hint_item)
|
|
{
|
|
return hint_item->valuestring;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char *stm_cmd_assistant_input_line_for_hints(struct stm_cmd_assistant *aide, const char *line)
|
|
{
|
|
int argc = 0;
|
|
sds *cli_cmd_array = sdssplitargs(line, &argc);
|
|
const char *hints = cjson_traversal_hints(cli_cmd_array, argc, aide->cjson_root);
|
|
sdsfreesplitres(cli_cmd_array, argc);
|
|
return hints;
|
|
}
|
|
|
|
static long long stm_cmd_assistant_get_item(struct stm_cmd_assistant *aide, const char *cmd_line, const char *json_item_name, const char *format)
|
|
{
|
|
long long item_value = 0;
|
|
int array_size = cJSON_GetArraySize(aide->cjson_root);
|
|
int cli_argc;
|
|
sds *cli_cmd_array = sdssplitargs(cmd_line, &cli_argc);
|
|
int last_match_cmd_section_num = -1;
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *array_item = cJSON_GetArrayItem(aide->cjson_root, sz);
|
|
cJSON *prefix_item = cJSON_GetObjectItem(array_item, "prefix");
|
|
int register_cmd_section_num;
|
|
sds *register_cmd_array = sdssplitargs(prefix_item->valuestring, ®ister_cmd_section_num);
|
|
|
|
int match = stm_cmd_assistant_sds_compare(cli_cmd_array, cli_argc, register_cmd_array, register_cmd_section_num);
|
|
sdsfreesplitres(register_cmd_array, register_cmd_section_num);
|
|
if (match >= 0)
|
|
{
|
|
cJSON *hint_item = cJSON_GetObjectItem(array_item, json_item_name);
|
|
if (hint_item)
|
|
{
|
|
/* longest match */
|
|
if (register_cmd_section_num > last_match_cmd_section_num)
|
|
{
|
|
last_match_cmd_section_num = register_cmd_section_num;
|
|
sscanf(hint_item->valuestring, format, &item_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sdsfreesplitres(cli_cmd_array, cli_argc);
|
|
return item_value;
|
|
}
|
|
|
|
void *stm_cmd_assistant_get_cb(struct stm_cmd_assistant *aide, const char *cmd_line)
|
|
{
|
|
return (void *)stm_cmd_assistant_get_item(aide, cmd_line, "cmd_cb", "%p");
|
|
}
|
|
|
|
void *stm_cmd_assistant_get_user_arg(struct stm_cmd_assistant *aide, const char *cmd_line)
|
|
{
|
|
return (void *)stm_cmd_assistant_get_item(aide, cmd_line, "cmd_arg", "%p");
|
|
}
|
|
|
|
int stm_cmd_assistant_get_arity(struct stm_cmd_assistant *aide, const char *cmd_line)
|
|
{
|
|
return (int)stm_cmd_assistant_get_item(aide, cmd_line, "arity", "%d");
|
|
}
|
|
|
|
sds stm_cmd_assistant_list_cmd_brief(struct stm_cmd_assistant *aide)
|
|
{
|
|
sds res = sdsempty();
|
|
int array_size = cJSON_GetArraySize(aide->cjson_root);
|
|
for (int sz = 0; sz < array_size; sz++)
|
|
{
|
|
cJSON *item = cJSON_GetArrayItem(aide->cjson_root, sz);
|
|
cJSON *cmd_name_item = cJSON_GetObjectItem(item, "prefix");
|
|
cJSON *cmd_desc_item = cJSON_GetObjectItem(item, "desc");
|
|
res = sdscatprintf(res, "\"%s\", %s \r\n", cmd_name_item->valuestring, cmd_desc_item ? cmd_desc_item->valuestring : "");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
sds stm_cmd_assistant_list_cmd_verbose(struct stm_cmd_assistant *aide)
|
|
{
|
|
if (NULL == aide->cjson_root)
|
|
{
|
|
return sdsempty();
|
|
}
|
|
char *json_str = cJSON_PrintUnformatted(aide->cjson_root);
|
|
sds res = sdsnew(json_str);
|
|
free(json_str);
|
|
return res;
|
|
}
|