feat(libcmd): add command processing framework with built-in linenoise

- 纯 C 接口:命令注册/查找/补全/帮助
- CMD_REGISTER / CMD_REGISTER_C 宏(__attribute__((constructor)) 自动注册)
- 内置 linenoise 行编辑(termios raw mode + 历史 + Tab 补全)
- x86 编译通过,产物 libcmd.a
This commit is contained in:
ypc 2026-07-07 17:30:00 +08:00
parent d9546b7c2a
commit dfb0a46146
5 changed files with 694 additions and 0 deletions

View File

@ -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 内置实现)

77
release/inc/myCmd.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,482 @@
/**
* @file myCmd.cpp
* @brief / + linenoise
*/
#include "myCmd.h"
#include <vector>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
extern "C"
{
/* ===== 命令注册表 ===== */
static std::vector<stru_cmd> &get_cmd_list(void)
{
static std::vector<stru_cmd> 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" */