#include #include #include #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; }