exec: extract help text into catalog

This commit is contained in:
m1ngsama 2026-05-24 12:41:05 +08:00
parent 5eda6ed127
commit e911a2d469
13 changed files with 116 additions and 36 deletions

1
.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

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

9
include/exec_catalog.h Normal file
View file

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

View file

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

View file

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

35
src/exec_catalog.c Normal file
View file

@ -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);
}
}

View file

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

View file

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

View file

@ -0,0 +1,49 @@
/* Unit tests for SSH exec command help catalog */
#include "../../include/exec_catalog.h"
#include "text_assert.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#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;
}

View file

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