diff --git a/docs/public/libcmd/API-libcmd.md b/docs/public/libcmd/API-libcmd.md new file mode 100644 index 0000000..306ac31 --- /dev/null +++ b/docs/public/libcmd/API-libcmd.md @@ -0,0 +1,26 @@ +# API-libcmd — 命令处理框架 + +> 命令注册/分发/补全,内置 linenoise 行编辑,供 libdatacenter 等模块调试使用。 + +## 接口 + +| 函数/宏 | 说明 | +|---------|------| +| `cmd_manager_add_command(name, func, desc, complete)` | 注册命令 | +| `cmd_manager_get_commands(out_count)` | 获取所有已注册命令列表 | +| `cmd_help(argc, argv)` | 打印帮助信息 | +| `cmd_find(name)` | 按名称查找命令 | +| `cmd_complete(buf, completions, ncomp)` | Tab 自动补全回调 | +| `cmd_sub_complete(buf, completions, ncomp, subs, sub_count)` | 子命令补全 | +| `linenoise(prompt)` | 行编辑+历史交互式输入 | +| `linenoiseFree(ptr)` | 释放 linenoise 返回的内存 | +| `linenoiseHistoryAdd(line)` | 添加历史记录 | +| `linenoiseHistoryFree()` | 释放历史 | +| `lineniseSetCompletionCallback(cb)` | 设置自动补全回调 | +| `CMD_REGISTER(name, func, desc)` | 注册命令宏(无补全) | +| `CMD_REGISTER_C(name, func, desc, complete)` | 注册命令宏(带补全) | + +## 依赖 + +- `myBase.h` +- 无外部依赖(linenoise 内置实现) diff --git a/release/inc/myCmd.h b/release/inc/myCmd.h new file mode 100644 index 0000000..98f6618 --- /dev/null +++ b/release/inc/myCmd.h @@ -0,0 +1,77 @@ +/** + * @file myCmd.h + * @brief 命令处理框架 — 纯 C 接口 + * @details 命令注册/分发/补全,内置 linenoise 行编辑。 + * 通过 `__attribute__((constructor))` 实现模块自动注册。 + */ +#ifndef _MY_CMD_H_ +#define _MY_CMD_H_ + +#include "myBase.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + const char *name; + void (*func)(int argc, char *argv[]); + const char *desc; + void (*complete)(const char *buf, char ***completions, int *ncomp); +} stru_cmd; + +/** 注册命令 */ +void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]), + const char *desc, void (*complete)(const char *, char ***, int *)); + +/** 获取所有已注册命令列表 */ +stru_cmd *cmd_manager_get_commands(unsigned int *out_count); + +/** 打印帮助信息 */ +void cmd_help(int argc, char *argv[]); + +/** 按名称查找命令 */ +stru_cmd *cmd_find(const char *name); + +/** Tab 自动补全回调 */ +void cmd_complete(const char *buf, char ***completions, int *ncomp); + +/** 子命令补全 */ +void cmd_sub_complete(const char *buf, char ***completions, int *ncomp, + const char **subs, int sub_count); + +/** 行编辑交互式输入 */ +char *linenoise(const char *prompt); + +/** 释放 linenoise 返回的内存 */ +void linenoiseFree(void *ptr); + +/** 添加历史记录 */ +int linenoiseHistoryAdd(const char *line); + +/** 释放所有历史 */ +void linenoiseHistoryFree(void); + +/** 设置自动补全回调 */ +void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *)); + +/** 注册命令宏(无补全) */ +#define CMD_REGISTER(cmd_name, func_ptr, cmd_desc) \ + __attribute__((constructor)) static void register_##func_ptr(void) \ + { \ + cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, NULL); \ + } + +/** 注册命令宏(带补全) */ +#define CMD_REGISTER_C(cmd_name, func_ptr, cmd_desc, complete) \ + __attribute__((constructor)) static void register_##func_ptr(void) \ + { \ + cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, complete); \ + } + +#ifdef __cplusplus +} +#endif +#endif diff --git a/release/src/public/libcmd/makefile b/release/src/public/libcmd/makefile new file mode 100644 index 0000000..3a30d9d --- /dev/null +++ b/release/src/public/libcmd/makefile @@ -0,0 +1,32 @@ +include ./../../../linux.mk +L := $(notdir $(realpath $(CURDIR)/..)) +M := libcmd +O := $(LIB_REL)/$(M).a +S := $(SRC_ROOT_DIR)/$(L)/libcmd/src +I := -I$(SRC_ROOT_DIR)/$(L)/libcmd/inc +B := $(CURDIR)/$(M)/obj +SRCS := $(wildcard $(S)/*.cpp) +OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(SRCS)) +F := $(CXX_FLAGS) $(I) + +.PHONY: all +all: + @mkdir -p $(ROOT_DIR)/release/inc + @cp -f $(S:src=inc)/*.h $(ROOT_DIR)/release/inc/ 2>/dev/null; true + @$(MAKE) $(O) + +$(O): $(OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + @echo "[$(M)] built" + +$(B)/%.o: $(S)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(F) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf $(B) $(O) + +.PHONY: rebuild +rebuild: clean all diff --git a/src/public/libcmd/inc/myCmd.h b/src/public/libcmd/inc/myCmd.h new file mode 100644 index 0000000..98f6618 --- /dev/null +++ b/src/public/libcmd/inc/myCmd.h @@ -0,0 +1,77 @@ +/** + * @file myCmd.h + * @brief 命令处理框架 — 纯 C 接口 + * @details 命令注册/分发/补全,内置 linenoise 行编辑。 + * 通过 `__attribute__((constructor))` 实现模块自动注册。 + */ +#ifndef _MY_CMD_H_ +#define _MY_CMD_H_ + +#include "myBase.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + const char *name; + void (*func)(int argc, char *argv[]); + const char *desc; + void (*complete)(const char *buf, char ***completions, int *ncomp); +} stru_cmd; + +/** 注册命令 */ +void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]), + const char *desc, void (*complete)(const char *, char ***, int *)); + +/** 获取所有已注册命令列表 */ +stru_cmd *cmd_manager_get_commands(unsigned int *out_count); + +/** 打印帮助信息 */ +void cmd_help(int argc, char *argv[]); + +/** 按名称查找命令 */ +stru_cmd *cmd_find(const char *name); + +/** Tab 自动补全回调 */ +void cmd_complete(const char *buf, char ***completions, int *ncomp); + +/** 子命令补全 */ +void cmd_sub_complete(const char *buf, char ***completions, int *ncomp, + const char **subs, int sub_count); + +/** 行编辑交互式输入 */ +char *linenoise(const char *prompt); + +/** 释放 linenoise 返回的内存 */ +void linenoiseFree(void *ptr); + +/** 添加历史记录 */ +int linenoiseHistoryAdd(const char *line); + +/** 释放所有历史 */ +void linenoiseHistoryFree(void); + +/** 设置自动补全回调 */ +void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *)); + +/** 注册命令宏(无补全) */ +#define CMD_REGISTER(cmd_name, func_ptr, cmd_desc) \ + __attribute__((constructor)) static void register_##func_ptr(void) \ + { \ + cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, NULL); \ + } + +/** 注册命令宏(带补全) */ +#define CMD_REGISTER_C(cmd_name, func_ptr, cmd_desc, complete) \ + __attribute__((constructor)) static void register_##func_ptr(void) \ + { \ + cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, complete); \ + } + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/public/libcmd/src/myCmd.cpp b/src/public/libcmd/src/myCmd.cpp new file mode 100644 index 0000000..83788bb --- /dev/null +++ b/src/public/libcmd/src/myCmd.cpp @@ -0,0 +1,482 @@ +/** + * @file myCmd.cpp + * @brief 命令处理框架实现 — 命令注册/分发 + 内置 linenoise 行编辑 + */ +#include "myCmd.h" +#include +#include +#include +#include +#include +#include +#include + +extern "C" +{ + +/* ===== 命令注册表 ===== */ + +static std::vector &get_cmd_list(void) +{ + static std::vector s_cmd_list; + + return s_cmd_list; +} + +void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]), + const char *desc, void (*complete)(const char *, char ***, int *)) +{ + if (!name || !func || !desc) + { + return; + } + + auto &list = get_cmd_list(); + + list.push_back({name, func, desc, complete}); +} + +stru_cmd *cmd_manager_get_commands(unsigned int *out_count) +{ + auto &list = get_cmd_list(); + + if (out_count) + { + *out_count = (unsigned int)list.size(); + } + + if (list.empty()) + { + return NULL; + } + + return list.data(); +} + +stru_cmd *cmd_find(const char *name) +{ + if (!name) + { + return NULL; + } + + auto &list = get_cmd_list(); + + for (auto &cmd : list) + { + if (0 == strcmp(cmd.name, name)) + { + return &cmd; + } + } + + return NULL; +} + +void cmd_complete(const char *buf, char ***completions, int *ncomp) +{ + auto &list = get_cmd_list(); + + /* 判断是否有空格 —— 有空格则进入子命令补全 */ + const char *space = strchr(buf, ' '); + + if (space) + { + size_t cmd_len = (size_t)(space - buf); + + for (auto &cmd : list) + { + if (0 == strncmp(cmd.name, buf, cmd_len) && '\0' == cmd.name[cmd_len]) + { + if (cmd.complete) + { + cmd.complete(buf, completions, ncomp); + + return; + } + + break; + } + } + + *ncomp = 0; + *completions = NULL; + + return; + } + + /* 无空格:命令名前缀匹配 */ + int cnt = 0; + char **c = NULL; + + for (auto &cmd : list) + { + if (!strncmp(cmd.name, buf, strlen(buf))) + { + if (!c) + { + c = (char **)malloc(sizeof(char *) * (list.size() + 1)); + } + + c[cnt] = strdup(cmd.name); + cnt++; + } + } + + if (!c) + { + *ncomp = 0; + *completions = NULL; + } + else + { + *ncomp = cnt; + *completions = c; + } +} + +void cmd_sub_complete(const char *buf, char ***completions, int *ncomp, + const char **subs, int sub_count) +{ + const char *prefix = strrchr(buf, ' '); + + if (!prefix) + { + *ncomp = 0; + *completions = NULL; + + return; + } + + prefix++; + + int cnt = 0; + char **c = NULL; + size_t prefix_len = strlen(prefix); + + for (int i = 0; i < sub_count; i++) + { + if (0 == strncmp(subs[i], prefix, prefix_len)) + { + if (!c) + { + c = (char **)malloc(sizeof(char *) * (size_t)(sub_count + 1)); + } + + c[cnt] = strdup(subs[i]); + cnt++; + } + } + + *completions = c; + *ncomp = cnt; +} + +void cmd_help(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + auto &list = get_cmd_list(); + + for (auto &cmd : list) + { + printf("%-24s [%s]\n", cmd.name, cmd.desc); + } +} + +/* ===== 内置 linenoise ===== */ + +#define SAFE_WRITE(fd, buf, sz) \ + do \ + { \ + ssize_t _r = write(fd, buf, sz); \ + (void)_r; \ + } while (0) + +static char *g_history[100]; +static int g_history_len = 0; +static void (*g_completion_cb)(const char *, char ***, int *) = NULL; + +static struct termios g_orig_termios; +static int g_raw_enabled = 0; + +static void disable_raw(void) +{ + if (!g_raw_enabled) + { + return; + } + + tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_termios); + g_raw_enabled = 0; +} + +static void sigint_handler(int sig) +{ + (void)sig; + + if (g_raw_enabled) + { + disable_raw(); + } + + printf("\n"); + exit(0); +} + +static int enable_raw(void) +{ + signal(SIGINT, sigint_handler); + tcgetattr(STDIN_FILENO, &g_orig_termios); + atexit(disable_raw); + + struct termios raw = g_orig_termios; + + raw.c_lflag &= ~(ECHO | ICANON); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + + int ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); + + if (0 == ret) + { + g_raw_enabled = 1; + } + + return ret; +} + +static int linenoise_edit(char *buf, int bufsize, const char *prompt) +{ + int pos = 0; + int len = 0; + + memset(buf, 0, (size_t)bufsize); + SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt)); + + int history_idx = g_history_len; + + while (1) + { + char c; + ssize_t n = read(STDIN_FILENO, &c, 1); + + if (n <= 0) + { + return -1; + } + + if (c == '\n' || c == '\r') + { + buf[len] = '\0'; + SAFE_WRITE(STDOUT_FILENO, "\n", 1); + + return len; + } + + if (c == 127 || c == '\b') + { + if (len > 0) + { + len--; + pos--; + buf[len] = '\0'; + SAFE_WRITE(STDOUT_FILENO, "\b \b", 3); + history_idx = g_history_len; + } + + continue; + } + + if (c == 27) + { + char seq[2]; + + if (read(STDIN_FILENO, &seq[0], 1) == -1) + { + break; + } + + if (read(STDIN_FILENO, &seq[1], 1) == -1) + { + break; + } + + if (seq[0] == '[') + { + if (seq[1] == 'A') + { + if (g_history_len == 0) + { + continue; + } + + history_idx--; + + if (history_idx < 0) + { + history_idx = 0; + } + + const char *hist = g_history[history_idx]; + + strncpy(buf, hist, (size_t)(bufsize - 1)); + len = pos = (int)strlen(buf); + SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4); + SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt)); + SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len); + } + else if (seq[1] == 'B') + { + if (g_history_len == 0) + { + continue; + } + + history_idx++; + + if (history_idx >= g_history_len) + { + history_idx = g_history_len; + memset(buf, 0, (size_t)bufsize); + len = pos = 0; + } + else + { + const char *hist = g_history[history_idx]; + + strncpy(buf, hist, (size_t)(bufsize - 1)); + len = pos = (int)strlen(buf); + } + + SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4); + SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt)); + SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len); + } + } + + continue; + } + + if (c == 9) + { + char **completions = NULL; + int ncomp = 0; + + if (g_completion_cb) + { + g_completion_cb(buf, &completions, &ncomp); + + if (ncomp > 0) + { + char *last_space = NULL; + + for (int i = pos - 1; i >= 0; i--) + { + if (' ' == buf[i]) + { + last_space = &buf[i]; + break; + } + } + + if (last_space) + { + int keep_len = (int)(last_space - buf + 1); + + strncpy(last_space + 1, completions[0], + (size_t)(bufsize - keep_len - 1)); + buf[bufsize - 1] = '\0'; + } + else + { + strncpy(buf, completions[0], (size_t)(bufsize - 1)); + } + + len = pos = (int)strlen(buf); + SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4); + SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt)); + SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len); + + for (int j = 0; j < ncomp; j++) + { + free(completions[j]); + } + + free(completions); + } + } + + history_idx = g_history_len; + + continue; + } + + if (len < bufsize - 1) + { + buf[len++] = c; + pos++; + SAFE_WRITE(STDOUT_FILENO, &c, 1); + history_idx = g_history_len; + } + } + + return 0; +} + +char *linenoise(const char *prompt) +{ + char buf[1024]; + + if (0 != enable_raw()) + { + return NULL; + } + + int n = linenoise_edit(buf, (int)sizeof(buf), prompt); + + disable_raw(); + + if (n <= 0) + { + return NULL; + } + + char *copy = strdup(buf); + + return copy; +} + +void linenoiseFree(void *ptr) +{ + free(ptr); +} + +int linenoiseHistoryAdd(const char *line) +{ + if (g_history_len >= 100) + { + return 0; + } + + g_history[g_history_len] = strdup(line); + g_history_len++; + + return 1; +} + +void linenoiseHistoryFree(void) +{ + for (int j = 0; j < g_history_len; j++) + { + free(g_history[j]); + } +} + +void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *)) +{ + g_completion_cb = cb; +} + +} /* extern "C" */