Generate tntctl command list from exec catalog

This commit is contained in:
m1ngsama 2026-05-28 10:36:22 +08:00
parent f2be702a15
commit 8affea2508
10 changed files with 99 additions and 37 deletions

View file

@ -37,6 +37,8 @@
and server survival stay responsive.
### Changed
- `tntctl --help` now gets its exec command list from `exec_catalog`, reducing
duplicate command metadata between the local wrapper and SSH exec mode.
- Updated `tnt(1)` to document the current TUI search and pager keys, and
added script coverage to keep active help surfaces free of removed support
commands.

View file

@ -11,7 +11,8 @@ typedef enum {
TNT_EXEC_COMMAND_TAIL,
TNT_EXEC_COMMAND_DUMP,
TNT_EXEC_COMMAND_POST,
TNT_EXEC_COMMAND_EXIT
TNT_EXEC_COMMAND_EXIT,
TNT_EXEC_COMMAND_COUNT
} tnt_exec_command_id_t;
bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
@ -19,6 +20,8 @@ bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
bool exec_catalog_args_valid(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);
void exec_catalog_append_command_list(char *buffer, size_t buf_size,
size_t *pos);
void exec_catalog_append_usage(char *buffer, size_t buf_size, size_t *pos,
tnt_exec_command_id_t id, ui_lang_t lang);

View file

@ -4,7 +4,6 @@
#include "common.h"
typedef enum {
TNTCTL_TEXT_USAGE,
TNTCTL_TEXT_INVALID_PORT,
TNTCTL_TEXT_INVALID_LOGIN,
TNTCTL_TEXT_INVALID_HOST_KEY_MODE,
@ -23,6 +22,8 @@ typedef enum {
TNTCTL_TEXT_COUNT
} tntctl_text_id_t;
void tntctl_text_append_usage(char *buffer, size_t buf_size, size_t *pos,
ui_lang_t lang);
const char *tntctl_text(ui_lang_t lang, tntctl_text_id_t id);
#endif /* TNTCTL_TEXT_H */

View file

@ -517,6 +517,8 @@ int exec_dispatch(client_t *client) {
return exec_command_post(client, args);
case TNT_EXEC_COMMAND_EXIT:
return TNT_EXIT_OK;
case TNT_EXEC_COMMAND_COUNT:
break;
}
}

View file

@ -155,6 +155,26 @@ void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos,
}
}
void exec_catalog_append_command_list(char *buffer, size_t buf_size,
size_t *pos) {
bool seen[TNT_EXEC_COMMAND_COUNT] = {0};
size_t count = 0;
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) {
tnt_exec_command_id_t id = entries[i].id;
if (id < 0 || id >= TNT_EXEC_COMMAND_COUNT || seen[id]) {
continue;
}
if (count > 0) {
buffer_appendf(buffer, buf_size, pos, ", ");
}
buffer_appendf(buffer, buf_size, pos, "%s", entries[i].name);
seen[id] = true;
count++;
}
}
void exec_catalog_append_usage(char *buffer, size_t buf_size, size_t *pos,
tnt_exec_command_id_t id, ui_lang_t lang) {
const exec_catalog_entry_t *entry = entry_for_id(id);

View file

@ -10,7 +10,12 @@
#include <unistd.h>
static void print_usage(FILE *stream, ui_lang_t lang) {
fputs(tntctl_text(lang, TNTCTL_TEXT_USAGE), stream);
char output[2048];
size_t pos = 0;
output[0] = '\0';
tntctl_text_append_usage(output, sizeof(output), &pos, lang);
fputs(output, stream);
}
static void print_error(ui_lang_t lang, tntctl_text_id_t id) {

View file

@ -1,36 +1,9 @@
#include "tntctl_text.h"
#include "exec_catalog.h"
#include "i18n.h"
static const i18n_string_t text_catalog[TNTCTL_TEXT_COUNT] = {
[TNTCTL_TEXT_USAGE] = I18N_STRING(
"Usage: tntctl [options] host command [args...]\n"
"\n"
"Options:\n"
" -p, --port PORT SSH port (default: 2222)\n"
" -l, --login USER SSH login name for exec identity\n"
" --host-key-checking MODE\n"
" OpenSSH host-key mode: yes, accept-new, no\n"
" --known-hosts FILE OpenSSH known_hosts file\n"
" -V, --version Print version and exit\n"
" -h, --help Print this help and exit\n"
"\n"
"Commands mirror the TNT SSH exec interface: health, stats, users,\n"
"tail, dump, post, help, and exit.\n",
"用法: tntctl [options] host command [args...]\n"
"\n"
"选项:\n"
" -p, --port PORT SSH 端口 (默认: 2222)\n"
" -l, --login USER SSH 登录名,用作 exec 身份\n"
" --host-key-checking MODE\n"
" OpenSSH 主机密钥模式: yes, accept-new, no\n"
" --known-hosts FILE OpenSSH known_hosts 文件\n"
" -V, --version 输出版本并退出\n"
" -h, --help 输出此帮助并退出\n"
"\n"
"命令对应 TNT SSH exec 接口: health, stats, users,\n"
"tail, dump, post, help 和 exit.\n"
),
[TNTCTL_TEXT_INVALID_PORT] = I18N_STRING(
"invalid port", "端口无效"
),
@ -79,7 +52,45 @@ static const i18n_string_t text_catalog[TNTCTL_TEXT_COUNT] = {
)
};
typedef char text_catalog_must_cover_enum[
sizeof(text_catalog) / sizeof(text_catalog[0]) == TNTCTL_TEXT_COUNT ? 1 : -1];
sizeof(text_catalog) / sizeof(text_catalog[0]) == TNTCTL_TEXT_COUNT ? 1 : -1
];
void tntctl_text_append_usage(char *buffer, size_t buf_size, size_t *pos,
ui_lang_t lang) {
static const i18n_string_t before_commands = I18N_STRING(
"Usage: tntctl [options] host command [args...]\n"
"\n"
"Options:\n"
" -p, --port PORT SSH port (default: 2222)\n"
" -l, --login USER SSH login name for exec identity\n"
" --host-key-checking MODE\n"
" OpenSSH host-key mode: yes, accept-new, no\n"
" --known-hosts FILE OpenSSH known_hosts file\n"
" -V, --version Print version and exit\n"
" -h, --help Print this help and exit\n"
"\n"
"Commands:\n"
" ",
"用法: tntctl [options] host command [args...]\n"
"\n"
"选项:\n"
" -p, --port PORT SSH 端口 (默认: 2222)\n"
" -l, --login USER SSH 登录名,用作 exec 身份\n"
" --host-key-checking MODE\n"
" OpenSSH 主机密钥模式: yes, accept-new, no\n"
" --known-hosts FILE OpenSSH known_hosts 文件\n"
" -V, --version 输出版本并退出\n"
" -h, --help 输出此帮助并退出\n"
"\n"
"命令:\n"
" "
);
buffer_appendf(buffer, buf_size, pos, "%s",
i18n_string(before_commands, lang));
exec_catalog_append_command_list(buffer, buf_size, pos);
buffer_appendf(buffer, buf_size, pos, "\n");
}
const char *tntctl_text(ui_lang_t lang, tntctl_text_id_t id) {
if (id < 0 || id >= TNTCTL_TEXT_COUNT) {

View file

@ -66,7 +66,7 @@ test_manual_text: test_manual_text.c $(MANUAL_TEXT_SRC) $(COMMAND_CATALOG_SRC) $
test_cli_text: test_cli_text.c $(CLI_TEXT_SRC) $(COMMON_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_tntctl_text: test_tntctl_text.c $(TNTCTL_TEXT_SRC)
test_tntctl_text: test_tntctl_text.c $(TNTCTL_TEXT_SRC) $(EXEC_CATALOG_SRC) $(COMMON_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_ratelimit: test_ratelimit.c $(RATELIMIT_SRC) $(COMMON_SRC)

View file

@ -124,6 +124,16 @@ TEST(generates_localized_usage) {
assert(strcmp(en, "dump: usage: dump [N] | dump -n N\n") == 0);
}
TEST(generates_unique_command_list) {
char output[256] = {0};
size_t pos = 0;
exec_catalog_append_command_list(output, sizeof(output), &pos);
assert(strcmp(output,
"help, health, users, stats, tail, dump, post, exit") == 0);
}
int main(void) {
printf("Running exec catalog unit tests...\n\n");
@ -131,6 +141,7 @@ int main(void) {
RUN_TEST(matches_exec_commands_and_args);
RUN_TEST(validates_argument_shapes);
RUN_TEST(generates_localized_usage);
RUN_TEST(generates_unique_command_list);
printf("\n✓ All %d tests passed!\n", tests_passed);
return 0;

View file

@ -16,15 +16,22 @@
static int tests_passed = 0;
TEST(usage_matches_language) {
const char *en = tntctl_text(UI_LANG_EN, TNTCTL_TEXT_USAGE);
const char *zh = tntctl_text(UI_LANG_ZH, TNTCTL_TEXT_USAGE);
char en[2048] = {0};
char zh[2048] = {0};
size_t en_pos = 0;
size_t zh_pos = 0;
tntctl_text_append_usage(en, sizeof(en), &en_pos, UI_LANG_EN);
tntctl_text_append_usage(zh, sizeof(zh), &zh_pos, UI_LANG_ZH);
assert(strstr(en, "Usage: tntctl [options] host command [args...]") != NULL);
assert(strstr(en, "--host-key-checking MODE") != NULL);
assert(strstr(en, "health, stats, users") != NULL);
assert(strstr(en,
"help, health, users, stats, tail, dump, post, exit") != NULL);
assert(strstr(zh, "用法: tntctl [options] host command [args...]") != NULL);
assert(strstr(zh, "OpenSSH 主机密钥模式") != NULL);
assert(strstr(zh, "health, stats, users") != NULL);
assert(strstr(zh,
"help, health, users, stats, tail, dump, post, exit") != NULL);
}
TEST(errors_match_language) {