mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:34:39 +08:00
exec: centralize command matching in catalog
This commit is contained in:
parent
da0170d2c0
commit
bfaafb4b35
9 changed files with 162 additions and 60 deletions
|
|
@ -252,7 +252,7 @@ TNT/
|
|||
│ ├── cli_text.c # startup CLI help and option text
|
||||
│ ├── command_catalog.c # command metadata
|
||||
│ ├── commands.c # COMMAND-mode command dispatch
|
||||
│ ├── exec_catalog.c # SSH exec command metadata
|
||||
│ ├── exec_catalog.c # SSH exec command matching and metadata
|
||||
│ ├── exec.c # SSH exec command dispatch
|
||||
│ ├── ssh_server.c # SSH server implementation
|
||||
│ ├── bootstrap.c # SSH authentication and session bootstrap
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
- Refreshed contributor and development guidance so new commands are added
|
||||
through `command_catalog`, `exec_catalog`, and `i18n_text` instead of stale
|
||||
`ssh_server.c` / inline-`strcmp` instructions.
|
||||
- `exec_catalog` now owns SSH exec command matching as well as help metadata,
|
||||
reducing duplicate command knowledge in `src/exec.c`.
|
||||
- Renamed the internal language state from help-oriented names to
|
||||
UI-language names (`ui_lang_t`, `client->ui_lang`, and
|
||||
`i18n_*_ui_lang`) so future i18n work has a correctly named seam.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ main.c → entry point, signal handling
|
|||
cli_text.c → startup CLI text
|
||||
command_catalog.c → COMMAND-mode command metadata
|
||||
commands.c → COMMAND-mode command dispatch
|
||||
exec_catalog.c → SSH exec help metadata
|
||||
exec_catalog.c → SSH exec command matching and help metadata
|
||||
exec.c → SSH exec command dispatch
|
||||
ssh_server.c → SSH listener setup
|
||||
bootstrap.c → SSH authentication/session bootstrap
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ src/
|
|||
├── input.c - Interactive session loop and key handling
|
||||
├── commands.c - COMMAND-mode command dispatch
|
||||
├── command_catalog.c - COMMAND-mode names, aliases, and help summaries
|
||||
├── exec_catalog.c - SSH exec command help metadata
|
||||
├── exec_catalog.c - SSH exec command matching and help metadata
|
||||
├── exec.c - SSH exec command dispatch
|
||||
├── chat_room.c - Chat room logic and message broadcasting
|
||||
├── message.c - Message persistence (RFC3339 format)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ STRUCTURE
|
|||
src/bootstrap.c SSH auth/session bootstrap
|
||||
src/chat_room.c broadcast and room state
|
||||
src/commands.c COMMAND-mode command dispatch
|
||||
src/exec_catalog.c SSH exec command metadata
|
||||
src/exec_catalog.c SSH exec command matching and metadata
|
||||
src/exec.c SSH exec command dispatch
|
||||
src/message.c persistence, search
|
||||
src/history_view.c message viewport / scroll state
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
|
||||
#include "common.h"
|
||||
|
||||
typedef enum {
|
||||
TNT_EXEC_COMMAND_HELP,
|
||||
TNT_EXEC_COMMAND_HEALTH,
|
||||
TNT_EXEC_COMMAND_USERS,
|
||||
TNT_EXEC_COMMAND_STATS,
|
||||
TNT_EXEC_COMMAND_TAIL,
|
||||
TNT_EXEC_COMMAND_POST,
|
||||
TNT_EXEC_COMMAND_EXIT
|
||||
} tnt_exec_command_id_t;
|
||||
|
||||
bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
|
||||
const char **args);
|
||||
void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos,
|
||||
ui_lang_t lang);
|
||||
|
||||
|
|
|
|||
83
src/exec.c
83
src/exec.c
|
|
@ -397,68 +397,57 @@ static int exec_command_post(client_t *client, const char *args) {
|
|||
|
||||
int exec_dispatch(client_t *client) {
|
||||
char command_copy[MAX_EXEC_COMMAND_LEN];
|
||||
char *cmd;
|
||||
char *args;
|
||||
tnt_exec_command_id_t command_id;
|
||||
const char *args = NULL;
|
||||
|
||||
strncpy(command_copy, client->exec_command, sizeof(command_copy) - 1);
|
||||
command_copy[sizeof(command_copy) - 1] = '\0';
|
||||
trim_ascii_whitespace(command_copy);
|
||||
|
||||
cmd = command_copy;
|
||||
if (*cmd == '\0') {
|
||||
if (command_copy[0] == '\0') {
|
||||
return exec_command_help(client);
|
||||
}
|
||||
|
||||
args = cmd;
|
||||
while (*args && !isspace((unsigned char)*args)) {
|
||||
args++;
|
||||
}
|
||||
if (*args) {
|
||||
*args++ = '\0';
|
||||
while (*args && isspace((unsigned char)*args)) {
|
||||
args++;
|
||||
if (exec_catalog_match(command_copy, &command_id, &args)) {
|
||||
switch (command_id) {
|
||||
case TNT_EXEC_COMMAND_HELP:
|
||||
return exec_command_help(client);
|
||||
case TNT_EXEC_COMMAND_HEALTH:
|
||||
return exec_command_health(client);
|
||||
case TNT_EXEC_COMMAND_USERS:
|
||||
if (args && strcmp(args, "--json") != 0) {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang,
|
||||
I18N_EXEC_USERS_USAGE));
|
||||
return 64;
|
||||
}
|
||||
return exec_command_users(client, args != NULL);
|
||||
case TNT_EXEC_COMMAND_STATS:
|
||||
if (args && strcmp(args, "--json") != 0) {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang,
|
||||
I18N_EXEC_STATS_USAGE));
|
||||
return 64;
|
||||
}
|
||||
return exec_command_stats(client, args != NULL);
|
||||
case TNT_EXEC_COMMAND_TAIL:
|
||||
return exec_command_tail(client, args);
|
||||
case TNT_EXEC_COMMAND_POST:
|
||||
return exec_command_post(client, args);
|
||||
case TNT_EXEC_COMMAND_EXIT:
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
args = NULL;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "--help") == 0) {
|
||||
return exec_command_help(client);
|
||||
}
|
||||
if (strcmp(cmd, "health") == 0) {
|
||||
return exec_command_health(client);
|
||||
}
|
||||
if (strcmp(cmd, "users") == 0) {
|
||||
if (args && strcmp(args, "--json") != 0) {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang,
|
||||
I18N_EXEC_USERS_USAGE));
|
||||
return 64;
|
||||
for (char *p = command_copy; *p; p++) {
|
||||
if (isspace((unsigned char)*p)) {
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
return exec_command_users(client, args != NULL);
|
||||
}
|
||||
if (strcmp(cmd, "stats") == 0) {
|
||||
if (args && strcmp(args, "--json") != 0) {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang,
|
||||
I18N_EXEC_STATS_USAGE));
|
||||
return 64;
|
||||
}
|
||||
return exec_command_stats(client, args != NULL);
|
||||
}
|
||||
if (strcmp(cmd, "tail") == 0) {
|
||||
return exec_command_tail(client, args);
|
||||
}
|
||||
if (strcmp(cmd, "post") == 0) {
|
||||
return exec_command_post(client, args);
|
||||
}
|
||||
if (strcmp(cmd, "exit") == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
client_printf(client,
|
||||
i18n_text(client->ui_lang,
|
||||
I18N_EXEC_UNKNOWN_COMMAND_FORMAT),
|
||||
cmd);
|
||||
command_copy);
|
||||
return 64;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,89 @@
|
|||
#include "exec_catalog.h"
|
||||
|
||||
typedef struct {
|
||||
tnt_exec_command_id_t id;
|
||||
const char *name;
|
||||
const char *alias;
|
||||
const char *usage;
|
||||
const char *summary_en;
|
||||
const char *summary_zh;
|
||||
} exec_catalog_entry_t;
|
||||
|
||||
static const exec_catalog_entry_t entries[] = {
|
||||
{"help", "Show this help", "显示此帮助"},
|
||||
{"health", "Print service health", "输出服务健康状态"},
|
||||
{"users [--json]", "List online users", "列出在线用户"},
|
||||
{"stats [--json]", "Print room statistics", "输出房间统计"},
|
||||
{"tail [N]", "Print recent messages", "输出最近消息"},
|
||||
{"tail -n N", "Print recent messages", "输出最近消息"},
|
||||
{"post MESSAGE", "Post a message non-interactively", "非交互发送消息"},
|
||||
{"post \"/me act\"", "Post an action message", "发送动作消息"},
|
||||
{"exit", "Exit successfully", "成功退出"}
|
||||
{TNT_EXEC_COMMAND_HELP, "help", "--help",
|
||||
"help", "Show this help", "显示此帮助"},
|
||||
{TNT_EXEC_COMMAND_HEALTH, "health", NULL,
|
||||
"health", "Print service health", "输出服务健康状态"},
|
||||
{TNT_EXEC_COMMAND_USERS, "users", NULL,
|
||||
"users [--json]", "List online users", "列出在线用户"},
|
||||
{TNT_EXEC_COMMAND_STATS, "stats", NULL,
|
||||
"stats [--json]", "Print room statistics", "输出房间统计"},
|
||||
{TNT_EXEC_COMMAND_TAIL, "tail", NULL,
|
||||
"tail [N]", "Print recent messages", "输出最近消息"},
|
||||
{TNT_EXEC_COMMAND_TAIL, "tail", NULL,
|
||||
"tail -n N", "Print recent messages", "输出最近消息"},
|
||||
{TNT_EXEC_COMMAND_POST, "post", NULL,
|
||||
"post MESSAGE", "Post a message non-interactively", "非交互发送消息"},
|
||||
{TNT_EXEC_COMMAND_POST, "post", NULL,
|
||||
"post \"/me act\"", "Post an action message", "发送动作消息"},
|
||||
{TNT_EXEC_COMMAND_EXIT, "exit", NULL,
|
||||
"exit", "Exit successfully", "成功退出"}
|
||||
};
|
||||
|
||||
static const char *skip_spaces(const char *value) {
|
||||
while (value && *value && (*value == ' ' || *value == '\t')) {
|
||||
value++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool name_matches(const char *line, const char *name,
|
||||
const char **args) {
|
||||
size_t len;
|
||||
|
||||
if (!line || !name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
len = strlen(name);
|
||||
if (strncmp(line, name, len) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (line[len] != '\0' && line[len] != ' ' && line[len] != '\t') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args) {
|
||||
const char *candidate_args = skip_spaces(line + len);
|
||||
*args = candidate_args && candidate_args[0] != '\0'
|
||||
? candidate_args
|
||||
: NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
|
||||
const char **args) {
|
||||
line = skip_spaces(line);
|
||||
if (!line || line[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) {
|
||||
if (!name_matches(line, entries[i].name, args) &&
|
||||
!name_matches(line, entries[i].alias, args)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
*id = entries[i].id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos,
|
||||
ui_lang_t lang) {
|
||||
if (lang == UI_LANG_ZH) {
|
||||
|
|
|
|||
|
|
@ -39,10 +39,43 @@ TEST(generates_localized_exec_help) {
|
|||
assert_ascii_angle_placeholders(zh);
|
||||
}
|
||||
|
||||
TEST(matches_exec_commands_and_args) {
|
||||
tnt_exec_command_id_t id;
|
||||
const char *args;
|
||||
|
||||
assert(exec_catalog_match("help", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_HELP);
|
||||
assert(args == NULL);
|
||||
|
||||
assert(exec_catalog_match("--help", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_HELP);
|
||||
assert(args == NULL);
|
||||
|
||||
assert(exec_catalog_match("users --json", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_USERS);
|
||||
assert(strcmp(args, "--json") == 0);
|
||||
|
||||
assert(exec_catalog_match("tail -n 20", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_TAIL);
|
||||
assert(strcmp(args, "-n 20") == 0);
|
||||
|
||||
assert(exec_catalog_match("post hello world", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_POST);
|
||||
assert(strcmp(args, "hello world") == 0);
|
||||
|
||||
assert(exec_catalog_match("exit", &id, &args));
|
||||
assert(id == TNT_EXEC_COMMAND_EXIT);
|
||||
assert(args == NULL);
|
||||
|
||||
assert(!exec_catalog_match("usersx", &id, &args));
|
||||
assert(!exec_catalog_match("nope", &id, &args));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running exec catalog unit tests...\n\n");
|
||||
|
||||
RUN_TEST(generates_localized_exec_help);
|
||||
RUN_TEST(matches_exec_commands_and_args);
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue