From e911a2d4698fab1524aa1fa6b06edba48be68aa7 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 24 May 2026 12:41:05 +0800 Subject: [PATCH] exec: extract help text into catalog --- .gitignore | 1 + README.md | 3 +++ docs/CHANGELOG.md | 2 ++ docs/Development-Guide.md | 1 + docs/QUICKREF.md | 1 + include/exec_catalog.h | 9 +++++++ include/i18n.h | 1 - src/exec.c | 9 +++++-- src/exec_catalog.c | 35 ++++++++++++++++++++++++ src/i18n_text.c | 24 ----------------- tests/unit/Makefile | 9 ++++++- tests/unit/test_exec_catalog.c | 49 ++++++++++++++++++++++++++++++++++ tests/unit/test_i18n.c | 8 ------ 13 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 include/exec_catalog.h create mode 100644 src/exec_catalog.c create mode 100644 tests/unit/test_exec_catalog.c diff --git a/.gitignore b/.gitignore index 8579b02..b559a17 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ tests/unit/test_history_view tests/unit/test_i18n tests/unit/test_system_message tests/unit/test_command_catalog +tests/unit/test_exec_catalog tests/unit/test_help_text tests/unit/test_manual_text tests/unit/test_support_text diff --git a/README.md b/README.md index 620ef8b..4332739 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,9 @@ TNT/ │ ├── main.c # entry point │ ├── 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.c # SSH exec command dispatch │ ├── ssh_server.c # SSH server implementation │ ├── bootstrap.c # SSH authentication and session bootstrap │ ├── chat_room.c # chat room logic diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4e4dac1..8d3901c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,8 @@ message" / "私信" instead of mixing it with "whisper" wording. - Kept localized startup CLI syntax stable by using `用法: tnt [options]` instead of localizing the `[options]` metavariable. +- Moved SSH exec help rows into an `exec_catalog` module so command metadata + no longer lives as one large translated blob inside the shared i18n table. - 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. diff --git a/docs/Development-Guide.md b/docs/Development-Guide.md index 81ea825..95afdd2 100644 --- a/docs/Development-Guide.md +++ b/docs/Development-Guide.md @@ -74,6 +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.c - SSH exec command dispatch ├── chat_room.c - Chat room logic and message broadcasting ├── message.c - Message persistence (RFC3339 format) diff --git a/docs/QUICKREF.md b/docs/QUICKREF.md index 79fca83..87a3a83 100644 --- a/docs/QUICKREF.md +++ b/docs/QUICKREF.md @@ -51,6 +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.c SSH exec command dispatch src/message.c persistence, search src/history_view.c message viewport / scroll state diff --git a/include/exec_catalog.h b/include/exec_catalog.h new file mode 100644 index 0000000..8698317 --- /dev/null +++ b/include/exec_catalog.h @@ -0,0 +1,9 @@ +#ifndef EXEC_CATALOG_H +#define EXEC_CATALOG_H + +#include "common.h" + +void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos, + ui_lang_t lang); + +#endif /* EXEC_CATALOG_H */ diff --git a/include/i18n.h b/include/i18n.h index 4372bc4..b7ee83d 100644 --- a/include/i18n.h +++ b/include/i18n.h @@ -53,7 +53,6 @@ typedef enum { I18N_UNKNOWN_COMMAND_FORMAT, I18N_DID_YOU_MEAN_FORMAT, I18N_UNKNOWN_GUIDANCE, - I18N_EXEC_HELP, I18N_EXEC_USERS_USAGE, I18N_EXEC_STATS_USAGE, I18N_EXEC_TAIL_USAGE, diff --git a/src/exec.c b/src/exec.c index 9289de7..74e3884 100644 --- a/src/exec.c +++ b/src/exec.c @@ -2,6 +2,7 @@ #include "chat_room.h" #include "client.h" #include "common.h" +#include "exec_catalog.h" #include "i18n.h" #include "input.h" #include "message.h" @@ -116,9 +117,13 @@ static void resolve_exec_username(const client_t *client, char *buffer, } static int exec_command_help(client_t *client) { - const char *help_text = i18n_text(client->ui_lang, I18N_EXEC_HELP); + char help_text[1024]; + size_t pos = 0; - return client_send(client, help_text, strlen(help_text)) == 0 ? 0 : 1; + help_text[0] = '\0'; + exec_catalog_append_help(help_text, sizeof(help_text), &pos, + client->ui_lang); + return client_send(client, help_text, pos) == 0 ? 0 : 1; } static int exec_command_health(client_t *client) { diff --git a/src/exec_catalog.c b/src/exec_catalog.c new file mode 100644 index 0000000..b311e32 --- /dev/null +++ b/src/exec_catalog.c @@ -0,0 +1,35 @@ +#include "exec_catalog.h" + +typedef struct { + 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", "成功退出"} +}; + +void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos, + ui_lang_t lang) { + if (lang == UI_LANG_ZH) { + buffer_appendf(buffer, buf_size, pos, "TNT exec 接口\n命令:\n"); + } else { + buffer_appendf(buffer, buf_size, pos, "TNT exec interface\nCommands:\n"); + } + + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { + const char *summary = lang == UI_LANG_ZH ? entries[i].summary_zh + : entries[i].summary_en; + buffer_appendf(buffer, buf_size, pos, " %-15s %s\n", + entries[i].usage, summary); + } +} diff --git a/src/i18n_text.c b/src/i18n_text.c index 7a0b87e..7c51be5 100644 --- a/src/i18n_text.c +++ b/src/i18n_text.c @@ -208,30 +208,6 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = { "Type :help for help\n", "输入 :help 查看帮助\n" }, - [I18N_EXEC_HELP] = { - "TNT exec interface\n" - "Commands:\n" - " help Show this help\n" - " health Print service health\n" - " users [--json] List online users\n" - " stats [--json] Print room statistics\n" - " tail [N] Print recent messages\n" - " tail -n N Print recent messages\n" - " post MESSAGE Post a message non-interactively\n" - " post \"/me act\" Post an action message\n" - " exit Exit successfully\n", - "TNT exec 接口\n" - "命令:\n" - " help 显示此帮助\n" - " health 输出服务健康状态\n" - " users [--json] 列出在线用户\n" - " stats [--json] 输出房间统计\n" - " tail [N] 输出最近消息\n" - " tail -n N 输出最近消息\n" - " post MESSAGE 非交互发送消息\n" - " post \"/me act\" 发送动作消息\n" - " exit 成功退出\n" - }, [I18N_EXEC_USERS_USAGE] = { "users: usage: users [--json]\n", "users: 用法: users [--json]\n" diff --git a/tests/unit/Makefile b/tests/unit/Makefile index c1bcbb6..0a69667 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -19,12 +19,13 @@ CHAT_ROOM_SRC = ../../src/chat_room.c HISTORY_VIEW_SRC = ../../src/history_view.c I18N_SRC = ../../src/i18n.c I18N_TEXT_SRC = ../../src/i18n_text.c +EXEC_CATALOG_SRC = ../../src/exec_catalog.c SYSTEM_MESSAGE_SRC = ../../src/system_message.c HELP_TEXT_SRC = ../../src/help_text.c MANUAL_TEXT_SRC = ../../src/manual_text.c RATELIMIT_SRC = ../../src/ratelimit.c -TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_command_catalog test_help_text test_manual_text test_cli_text test_ratelimit +TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_command_catalog test_exec_catalog test_help_text test_manual_text test_cli_text test_ratelimit .PHONY: all clean run @@ -51,6 +52,9 @@ test_system_message: test_system_message.c $(SYSTEM_MESSAGE_SRC) $(I18N_SRC) $(I test_command_catalog: test_command_catalog.c $(COMMAND_CATALOG_SRC) $(COMMON_SRC) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +test_exec_catalog: test_exec_catalog.c $(EXEC_CATALOG_SRC) $(COMMON_SRC) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + test_help_text: test_help_text.c $(HELP_TEXT_SRC) $(COMMAND_CATALOG_SRC) $(COMMON_SRC) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) @@ -85,6 +89,9 @@ run: all @echo "=== Running Command Catalog Tests ===" ./test_command_catalog @echo "" + @echo "=== Running Exec Catalog Tests ===" + ./test_exec_catalog + @echo "" @echo "=== Running Help Text Tests ===" ./test_help_text @echo "" diff --git a/tests/unit/test_exec_catalog.c b/tests/unit/test_exec_catalog.c new file mode 100644 index 0000000..2a40cbb --- /dev/null +++ b/tests/unit/test_exec_catalog.c @@ -0,0 +1,49 @@ +/* Unit tests for SSH exec command help catalog */ + +#include "../../include/exec_catalog.h" +#include "text_assert.h" +#include +#include +#include + +#define TEST(name) static void test_##name() +#define RUN_TEST(name) do { \ + printf("Running %s... ", #name); \ + test_##name(); \ + printf("✓\n"); \ + tests_passed++; \ +} while(0) + +static int tests_passed = 0; + +TEST(generates_localized_exec_help) { + char en[2048] = {0}; + char zh[2048] = {0}; + size_t en_pos = 0; + size_t zh_pos = 0; + + exec_catalog_append_help(en, sizeof(en), &en_pos, UI_LANG_EN); + exec_catalog_append_help(zh, sizeof(zh), &zh_pos, UI_LANG_ZH); + + assert(strstr(en, "TNT exec interface") != NULL); + assert(strstr(en, "Commands:") != NULL); + assert(strstr(en, "users [--json]") != NULL); + assert(strstr(en, "post MESSAGE") != NULL); + assert(strstr(en, "support") == NULL); + + assert(strstr(zh, "TNT exec 接口") != NULL); + assert(strstr(zh, "命令:") != NULL); + assert(strstr(zh, "users [--json]") != NULL); + assert(strstr(zh, "post MESSAGE") != NULL); + assert(strstr(zh, "support") == NULL); + assert_ascii_angle_placeholders(zh); +} + +int main(void) { + printf("Running exec catalog unit tests...\n\n"); + + RUN_TEST(generates_localized_exec_help); + + printf("\n✓ All %d tests passed!\n", tests_passed); + return 0; +} diff --git a/tests/unit/test_i18n.c b/tests/unit/test_i18n.c index e2f6a82..889cf3c 100644 --- a/tests/unit/test_i18n.c +++ b/tests/unit/test_i18n.c @@ -141,14 +141,6 @@ TEST(text_lookup_matches_language) { "Unknown command") != NULL); assert(strstr(i18n_text(UI_LANG_ZH, I18N_UNKNOWN_COMMAND_FORMAT), "未知命令") != NULL); - assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_HELP), - "TNT exec interface") != NULL); - assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_HELP), - "support") == NULL); - assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_HELP), - "TNT exec 接口") != NULL); - assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_HELP), - "support") == NULL); assert(strstr(i18n_text(UI_LANG_EN, I18N_EXEC_POST_EMPTY), "message cannot be empty") != NULL); assert(strstr(i18n_text(UI_LANG_ZH, I18N_EXEC_POST_EMPTY),