This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
stellar-stellar/infra/monitor/monitor_cmd_assistant.c
2024-11-07 18:30:58 +08:00

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, &register_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, &register_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, &register_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;
}