mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
commands: centralize usage validation in catalog
This commit is contained in:
parent
0aaba8e1f9
commit
f2942e9c9e
12 changed files with 176 additions and 79 deletions
|
|
@ -250,9 +250,9 @@ TNT/
|
|||
├── src/ # source code
|
||||
│ ├── main.c # entry point
|
||||
│ ├── cli_text.c # startup CLI help and option text
|
||||
│ ├── command_catalog.c # command metadata
|
||||
│ ├── command_catalog.c # command metadata, usage, and argument shape
|
||||
│ ├── commands.c # COMMAND-mode command dispatch
|
||||
│ ├── exec_catalog.c # SSH exec command matching and metadata
|
||||
│ ├── exec_catalog.c # SSH exec command matching, usage, and argument shape
|
||||
│ ├── exec.c # SSH exec command dispatch
|
||||
│ ├── ssh_server.c # SSH server implementation
|
||||
│ ├── bootstrap.c # SSH authentication and session bootstrap
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
public documentation does not imply a specific production host.
|
||||
- Moved SSH exec usage text and argument-shape checks into `exec_catalog`, so
|
||||
`src/exec.c` no longer duplicates `--json` and required-message validation.
|
||||
- Moved interactive command usage text and first-pass argument-shape checks
|
||||
into `command_catalog`, so known commands with bad arguments now show usage
|
||||
instead of unknown-command guidance.
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ make check
|
|||
```
|
||||
main.c → entry point, signal handling
|
||||
cli_text.c → startup CLI text
|
||||
command_catalog.c → COMMAND-mode command metadata
|
||||
command_catalog.c → COMMAND-mode command metadata, usage, and argument shape
|
||||
commands.c → COMMAND-mode command dispatch
|
||||
exec_catalog.c → SSH exec command matching and help metadata
|
||||
exec_catalog.c → SSH exec command matching, usage, and argument shape
|
||||
exec.c → SSH exec command dispatch
|
||||
ssh_server.c → SSH listener setup
|
||||
bootstrap.c → SSH authentication/session bootstrap
|
||||
|
|
@ -83,7 +83,8 @@ utf8.c → UTF-8 string handling
|
|||
|
||||
## Adding Features
|
||||
|
||||
1. Add interactive command metadata in `src/command_catalog.c`.
|
||||
1. Add interactive command metadata, usage text, and argument shape in
|
||||
`src/command_catalog.c`.
|
||||
2. Add interactive command behavior in `src/commands.c`.
|
||||
3. Add SSH exec metadata in `src/exec_catalog.c` and dispatch in `src/exec.c`
|
||||
only when the feature should be scriptable.
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ INSERT MODE
|
|||
STRUCTURE
|
||||
src/main.c entry, signals
|
||||
src/cli_text.c startup CLI text
|
||||
src/command_catalog.c command metadata
|
||||
src/command_catalog.c command metadata, usage, argument shape
|
||||
src/ssh_server.c SSH listener and server setup
|
||||
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 matching and metadata
|
||||
src/exec_catalog.c SSH exec command matching, usage, argument shape
|
||||
src/exec.c SSH exec command dispatch
|
||||
src/message.c persistence, search
|
||||
src/history_view.c message viewport / scroll state
|
||||
|
|
|
|||
|
|
@ -22,16 +22,18 @@ typedef struct {
|
|||
tnt_command_id_t id;
|
||||
const char *canonical;
|
||||
const char *names[4];
|
||||
bool accepts_args;
|
||||
} tnt_command_spec_t;
|
||||
|
||||
const tnt_command_spec_t *command_catalog_get(tnt_command_id_t id);
|
||||
bool command_catalog_match(const char *line, tnt_command_id_t *id,
|
||||
const char **args);
|
||||
bool command_catalog_args_valid(tnt_command_id_t id, const char *args);
|
||||
const char *command_catalog_suggest(const char *name);
|
||||
void command_catalog_append_full(char *buffer, size_t buf_size, size_t *pos,
|
||||
ui_lang_t lang);
|
||||
void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos,
|
||||
ui_lang_t lang);
|
||||
void command_catalog_append_usage(char *buffer, size_t buf_size, size_t *pos,
|
||||
tnt_command_id_t id, ui_lang_t lang);
|
||||
|
||||
#endif /* COMMAND_CATALOG_H */
|
||||
|
|
|
|||
|
|
@ -29,19 +29,15 @@ typedef enum {
|
|||
I18N_SYSTEM_LEAVE_FORMAT,
|
||||
I18N_SYSTEM_NICK_FORMAT,
|
||||
I18N_USERS_TITLE,
|
||||
I18N_MSG_USAGE,
|
||||
I18N_MSG_SENT_FORMAT,
|
||||
I18N_MSG_USER_NOT_FOUND_FORMAT,
|
||||
I18N_INBOX_TITLE,
|
||||
I18N_INBOX_EMPTY,
|
||||
I18N_NICK_USAGE,
|
||||
I18N_NICK_INVALID,
|
||||
I18N_NICK_TAKEN_FORMAT,
|
||||
I18N_NICK_UNCHANGED,
|
||||
I18N_NICK_CHANGED_FORMAT,
|
||||
I18N_LAST_USAGE,
|
||||
I18N_LAST_HEADER_FORMAT,
|
||||
I18N_SEARCH_USAGE,
|
||||
I18N_SEARCH_HEADER_FORMAT,
|
||||
I18N_MUTE_JOINS_FORMAT,
|
||||
I18N_MUTE_JOINS_MUTED,
|
||||
|
|
|
|||
|
|
@ -10,76 +10,106 @@ typedef struct {
|
|||
const char *summary_zh;
|
||||
const char *manual_usage_en;
|
||||
const char *manual_usage_zh;
|
||||
const char *error_usage_en;
|
||||
const char *error_usage_zh;
|
||||
int manual_group;
|
||||
bool no_args;
|
||||
bool requires_args;
|
||||
} command_catalog_entry_t;
|
||||
|
||||
static const command_catalog_entry_t entries[] = {
|
||||
{
|
||||
{TNT_COMMAND_USERS, "users", {"users", "list", "who", NULL}, false},
|
||||
{TNT_COMMAND_USERS, "users", {"users", "list", "who", NULL}},
|
||||
":users, :list, :who", ":users, :list, :who",
|
||||
"Show online users", "显示在线用户",
|
||||
":users", ":users", 1
|
||||
":users", ":users",
|
||||
"Usage: users\n", "用法: users\n",
|
||||
1, true, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_MSG, "msg", {"msg", "w", NULL}, true},
|
||||
{TNT_COMMAND_MSG, "msg", {"msg", "w", NULL}},
|
||||
":msg <user> <message>, :w <user> <message>",
|
||||
":msg <user> <message>, :w <user> <message>",
|
||||
"Send private message", "发送私信",
|
||||
":msg <user> <message>", ":msg <user> <message>", 2
|
||||
":msg <user> <message>", ":msg <user> <message>",
|
||||
"Usage: msg <user> <message>\n"
|
||||
" w <user> <message>\n",
|
||||
"用法: msg <user> <message>\n"
|
||||
" w <user> <message>\n",
|
||||
2, false, true
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_INBOX, "inbox", {"inbox", NULL}, false},
|
||||
{TNT_COMMAND_INBOX, "inbox", {"inbox", NULL}},
|
||||
":inbox", ":inbox",
|
||||
"Show private messages", "查看私信",
|
||||
":inbox", ":inbox", 2
|
||||
":inbox", ":inbox",
|
||||
"Usage: inbox\n", "用法: inbox\n",
|
||||
2, true, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_NICK, "nick", {"nick", "name", NULL}, true},
|
||||
{TNT_COMMAND_NICK, "nick", {"nick", "name", NULL}},
|
||||
":nick <name>, :name <name>", ":nick <name>, :name <name>",
|
||||
"Change nickname", "更改昵称",
|
||||
":nick <name>", ":nick <name>", 2
|
||||
":nick <name>", ":nick <name>",
|
||||
"Usage: nick <name>\n", "用法: nick <name>\n",
|
||||
2, false, true
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_LAST, "last", {"last", NULL}, true},
|
||||
{TNT_COMMAND_LAST, "last", {"last", NULL}},
|
||||
":last [N]", ":last [N]",
|
||||
"Show last N messages (max 50)", "显示最后 N 条消息(最多50)",
|
||||
":last [N]", ":last [N]", 1
|
||||
":last [N]", ":last [N]",
|
||||
"Usage: last [N] (N: 1-50, default 10)\n",
|
||||
"用法: last [N] (N: 1-50,默认 10)\n",
|
||||
1, false, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_SEARCH, "search", {"search", NULL}, true},
|
||||
{TNT_COMMAND_SEARCH, "search", {"search", NULL}},
|
||||
":search <keyword>", ":search <keyword>",
|
||||
"Search message history", "搜索消息历史",
|
||||
":search <keyword>", ":search <keyword>", 1
|
||||
":search <keyword>", ":search <keyword>",
|
||||
"Usage: search <keyword>\n", "用法: search <keyword>\n",
|
||||
1, false, true
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_MUTE_JOINS, "mute-joins", {"mute-joins", "mute", NULL}, false},
|
||||
{TNT_COMMAND_MUTE_JOINS, "mute-joins", {"mute-joins", "mute", NULL}},
|
||||
":mute-joins, :mute", ":mute-joins, :mute",
|
||||
"Toggle join/leave notices", "切换加入/离开提示",
|
||||
":mute-joins", ":mute-joins", 3
|
||||
":mute-joins", ":mute-joins",
|
||||
"Usage: mute-joins\n", "用法: mute-joins\n",
|
||||
3, true, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_HELP, "help", {"help", NULL}, false},
|
||||
{TNT_COMMAND_HELP, "help", {"help", NULL}},
|
||||
":help", ":help",
|
||||
"Show concise manual", "显示简明手册",
|
||||
NULL, NULL, 0
|
||||
NULL, NULL,
|
||||
"Usage: help\n", "用法: help\n",
|
||||
0, true, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_LANG, "lang", {"lang", "language", NULL}, true},
|
||||
{TNT_COMMAND_LANG, "lang", {"lang", "language", NULL}},
|
||||
":lang <en|zh>", ":lang <en|zh>",
|
||||
"Switch UI language", "切换界面语言",
|
||||
NULL, NULL, 0
|
||||
NULL, NULL,
|
||||
"Usage: lang <en|zh>\n", "用法: lang <en|zh>\n",
|
||||
0, false, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_CLEAR, "clear", {"clear", "cls", NULL}, false},
|
||||
{TNT_COMMAND_CLEAR, "clear", {"clear", "cls", NULL}},
|
||||
":clear, :cls", ":clear, :cls",
|
||||
"Clear command output", "清空命令输出",
|
||||
":clear", ":clear", 3
|
||||
":clear", ":clear",
|
||||
"Usage: clear\n", "用法: clear\n",
|
||||
3, true, false
|
||||
},
|
||||
{
|
||||
{TNT_COMMAND_QUIT, "q", {"q", "quit", "exit", NULL}, false},
|
||||
{TNT_COMMAND_QUIT, "q", {"q", "quit", "exit", NULL}},
|
||||
":q, :quit, :exit", ":q, :quit, :exit",
|
||||
"Disconnect", "断开连接",
|
||||
":q", ":q", 3
|
||||
":q", ":q",
|
||||
"Usage: q\n", "用法: q\n",
|
||||
3, true, false
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -177,10 +207,6 @@ bool command_catalog_match(const char *line, tnt_command_id_t *id,
|
|||
if (!name_matches(line, spec->names[n], &candidate_args)) {
|
||||
continue;
|
||||
}
|
||||
if (candidate_args && candidate_args[0] != '\0' &&
|
||||
!spec->accepts_args) {
|
||||
continue;
|
||||
}
|
||||
if (id) {
|
||||
*id = spec->id;
|
||||
}
|
||||
|
|
@ -194,6 +220,22 @@ bool command_catalog_match(const char *line, tnt_command_id_t *id,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool command_catalog_args_valid(tnt_command_id_t id, const char *args) {
|
||||
const command_catalog_entry_t *entry = entry_for_id(id);
|
||||
args = skip_spaces(args);
|
||||
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
if (entry->no_args) {
|
||||
return !args || args[0] == '\0';
|
||||
}
|
||||
if (entry->requires_args) {
|
||||
return args && args[0] != '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *command_catalog_suggest(const char *name) {
|
||||
const char *best = NULL;
|
||||
int best_distance = 99;
|
||||
|
|
@ -257,3 +299,17 @@ void command_catalog_append_manual(char *buffer, size_t buf_size, size_t *pos,
|
|||
buffer_appendf(buffer, buf_size, pos, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void command_catalog_append_usage(char *buffer, size_t buf_size, size_t *pos,
|
||||
tnt_command_id_t id, ui_lang_t lang) {
|
||||
const command_catalog_entry_t *entry = entry_for_id(id);
|
||||
const char *usage;
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
usage = lang == UI_LANG_ZH ? entry->error_usage_zh
|
||||
: entry->error_usage_en;
|
||||
buffer_appendf(buffer, buf_size, pos, "%s", usage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ static void append_highlighted(char *output, size_t buf_size, size_t *pos,
|
|||
}
|
||||
}
|
||||
|
||||
static void append_command_usage(char *output, size_t buf_size, size_t *pos,
|
||||
tnt_command_id_t id, ui_lang_t lang) {
|
||||
command_catalog_append_usage(output, buf_size, pos, id, lang);
|
||||
}
|
||||
|
||||
void commands_dispatch(client_t *client) {
|
||||
char cmd_buf[256];
|
||||
strncpy(cmd_buf, client->command_input, sizeof(cmd_buf) - 1);
|
||||
|
|
@ -107,6 +112,12 @@ void commands_dispatch(client_t *client) {
|
|||
goto cmd_done;
|
||||
}
|
||||
|
||||
if (!command_catalog_args_valid(command_id, arg)) {
|
||||
append_command_usage(output, sizeof(output), &pos, command_id,
|
||||
client->ui_lang);
|
||||
goto cmd_done;
|
||||
}
|
||||
|
||||
if (command_id == TNT_COMMAND_USERS) {
|
||||
pthread_rwlock_rdlock(&g_room->lock);
|
||||
int total = g_room->client_count;
|
||||
|
|
@ -171,8 +182,8 @@ void commands_dispatch(client_t *client) {
|
|||
while (*rest == ' ') rest++;
|
||||
|
||||
if (target_name[0] == '\0' || rest[0] == '\0') {
|
||||
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||
i18n_text(client->ui_lang, I18N_MSG_USAGE));
|
||||
append_command_usage(output, sizeof(output), &pos,
|
||||
TNT_COMMAND_MSG, client->ui_lang);
|
||||
} else {
|
||||
bool found = false;
|
||||
client_t *target = NULL;
|
||||
|
|
@ -267,8 +278,8 @@ void commands_dispatch(client_t *client) {
|
|||
while (*new_name == ' ') new_name++;
|
||||
|
||||
if (new_name[0] == '\0') {
|
||||
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||
i18n_text(client->ui_lang, I18N_NICK_USAGE));
|
||||
append_command_usage(output, sizeof(output), &pos,
|
||||
TNT_COMMAND_NICK, client->ui_lang);
|
||||
} else if (!is_valid_username(new_name)) {
|
||||
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||
i18n_text(client->ui_lang, I18N_NICK_INVALID));
|
||||
|
|
@ -331,8 +342,8 @@ void commands_dispatch(client_t *client) {
|
|||
char *endp;
|
||||
long val = strtol(arg, &endp, 10);
|
||||
if (*endp != '\0' || val < 1 || val > 50) {
|
||||
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||
i18n_text(client->ui_lang, I18N_LAST_USAGE));
|
||||
append_command_usage(output, sizeof(output), &pos,
|
||||
TNT_COMMAND_LAST, client->ui_lang);
|
||||
goto cmd_done;
|
||||
}
|
||||
n = (int)val;
|
||||
|
|
@ -357,8 +368,8 @@ void commands_dispatch(client_t *client) {
|
|||
const char *query = arg;
|
||||
while (*query == ' ') query++;
|
||||
if (*query == '\0') {
|
||||
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||
i18n_text(client->ui_lang, I18N_SEARCH_USAGE));
|
||||
append_command_usage(output, sizeof(output), &pos,
|
||||
TNT_COMMAND_SEARCH, client->ui_lang);
|
||||
} else {
|
||||
message_t *found = NULL;
|
||||
int found_count = message_search(query, &found, 15);
|
||||
|
|
|
|||
|
|
@ -106,12 +106,6 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
|
|||
"Online users",
|
||||
"在线用户"
|
||||
},
|
||||
[I18N_MSG_USAGE] = {
|
||||
"Usage: msg <user> <message>\n"
|
||||
" w <user> <message>\n",
|
||||
"用法: msg <user> <message>\n"
|
||||
" w <user> <message>\n"
|
||||
},
|
||||
[I18N_MSG_SENT_FORMAT] = {
|
||||
"Private message sent to %s\n",
|
||||
"私信已发送给 %s\n"
|
||||
|
|
@ -128,10 +122,6 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
|
|||
"(empty)",
|
||||
"(空)"
|
||||
},
|
||||
[I18N_NICK_USAGE] = {
|
||||
"Usage: nick <name>\n",
|
||||
"用法: nick <name>\n"
|
||||
},
|
||||
[I18N_NICK_INVALID] = {
|
||||
"Invalid username\n",
|
||||
"用户名无效\n"
|
||||
|
|
@ -148,18 +138,10 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
|
|||
"Nickname changed: %s -> %s\n",
|
||||
"昵称已修改: %s -> %s\n"
|
||||
},
|
||||
[I18N_LAST_USAGE] = {
|
||||
"Usage: last [N] (N: 1-50, default 10)\n",
|
||||
"用法: last [N] (N: 1-50,默认 10)\n"
|
||||
},
|
||||
[I18N_LAST_HEADER_FORMAT] = {
|
||||
"--- Last %d message(s) ---\n",
|
||||
"--- 最近 %d 条消息 ---\n"
|
||||
},
|
||||
[I18N_SEARCH_USAGE] = {
|
||||
"Usage: search <keyword>\n",
|
||||
"用法: search <keyword>\n"
|
||||
},
|
||||
[I18N_SEARCH_HEADER_FORMAT] = {
|
||||
"--- Search: \"%s\" (%d match(es)) ---\n",
|
||||
"--- 搜索: \"%s\" (%d 条匹配) ---\n"
|
||||
|
|
|
|||
|
|
@ -305,6 +305,20 @@ send -- "last 999\r"
|
|||
expect "Usage: last \\[N\\]"
|
||||
expect "q:close"
|
||||
send -- "q"
|
||||
expect "NORMAL"
|
||||
send -- ":"
|
||||
expect ":"
|
||||
send -- "users extra\r"
|
||||
expect "Usage: users"
|
||||
expect "q:close"
|
||||
send -- "q"
|
||||
expect "NORMAL"
|
||||
send -- ":"
|
||||
expect ":"
|
||||
send -- "help now\r"
|
||||
expect "Usage: help"
|
||||
expect "q:close"
|
||||
send -- "q"
|
||||
sleep 0.2
|
||||
send -- "\003"
|
||||
sleep 0.2
|
||||
|
|
|
|||
|
|
@ -40,13 +40,37 @@ TEST(matches_canonical_names_and_aliases) {
|
|||
assert(strcmp(args, "zh") == 0);
|
||||
}
|
||||
|
||||
TEST(rejects_arguments_for_no_arg_commands) {
|
||||
TEST(matches_known_commands_before_argument_validation) {
|
||||
tnt_command_id_t id;
|
||||
const char *args;
|
||||
|
||||
assert(!command_catalog_match("users extra", &id, &args));
|
||||
assert(!command_catalog_match("help now", &id, &args));
|
||||
assert(!command_catalog_match("q now", &id, &args));
|
||||
assert(command_catalog_match("users extra", &id, &args));
|
||||
assert(id == TNT_COMMAND_USERS);
|
||||
assert(strcmp(args, "extra") == 0);
|
||||
|
||||
assert(command_catalog_match("help now", &id, &args));
|
||||
assert(id == TNT_COMMAND_HELP);
|
||||
assert(strcmp(args, "now") == 0);
|
||||
|
||||
assert(command_catalog_match("q now", &id, &args));
|
||||
assert(id == TNT_COMMAND_QUIT);
|
||||
assert(strcmp(args, "now") == 0);
|
||||
}
|
||||
|
||||
TEST(validates_argument_shapes) {
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_USERS, NULL));
|
||||
assert(!command_catalog_args_valid(TNT_COMMAND_USERS, "extra"));
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_HELP, NULL));
|
||||
assert(!command_catalog_args_valid(TNT_COMMAND_HELP, "now"));
|
||||
|
||||
assert(!command_catalog_args_valid(TNT_COMMAND_MSG, NULL));
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_MSG, "alice hello"));
|
||||
assert(!command_catalog_args_valid(TNT_COMMAND_SEARCH, ""));
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_SEARCH, "needle"));
|
||||
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_LAST, NULL));
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_LAST, "999"));
|
||||
assert(command_catalog_args_valid(TNT_COMMAND_LANG, "fr"));
|
||||
}
|
||||
|
||||
TEST(suggests_from_catalog_aliases) {
|
||||
|
|
@ -81,13 +105,31 @@ TEST(generates_localized_help_sections) {
|
|||
assert_ascii_angle_placeholders(zh);
|
||||
}
|
||||
|
||||
TEST(generates_localized_usage) {
|
||||
char en[256] = {0};
|
||||
char zh[256] = {0};
|
||||
size_t en_pos = 0;
|
||||
size_t zh_pos = 0;
|
||||
|
||||
command_catalog_append_usage(en, sizeof(en), &en_pos,
|
||||
TNT_COMMAND_LAST, UI_LANG_EN);
|
||||
command_catalog_append_usage(zh, sizeof(zh), &zh_pos,
|
||||
TNT_COMMAND_MSG, UI_LANG_ZH);
|
||||
|
||||
assert(strcmp(en, "Usage: last [N] (N: 1-50, default 10)\n") == 0);
|
||||
assert(strcmp(zh, "用法: msg <user> <message>\n"
|
||||
" w <user> <message>\n") == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running command catalog unit tests...\n\n");
|
||||
|
||||
RUN_TEST(matches_canonical_names_and_aliases);
|
||||
RUN_TEST(rejects_arguments_for_no_arg_commands);
|
||||
RUN_TEST(matches_known_commands_before_argument_validation);
|
||||
RUN_TEST(validates_argument_shapes);
|
||||
RUN_TEST(suggests_from_catalog_aliases);
|
||||
RUN_TEST(generates_localized_help_sections);
|
||||
RUN_TEST(generates_localized_usage);
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -111,12 +111,6 @@ TEST(text_lookup_matches_language) {
|
|||
"idle timeout") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_IDLE_TIMEOUT_FORMAT),
|
||||
"空闲超时") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_EN, I18N_MSG_USAGE),
|
||||
"msg <user>") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_USAGE),
|
||||
"msg <user>") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_USAGE),
|
||||
"<用户>") == NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_EN, I18N_MSG_SENT_FORMAT),
|
||||
"Private message sent") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_MSG_SENT_FORMAT),
|
||||
|
|
@ -125,14 +119,10 @@ TEST(text_lookup_matches_language) {
|
|||
"Private messages") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_TITLE),
|
||||
"私信") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_NICK_USAGE),
|
||||
"nick <name>") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_EN, I18N_SEARCH_HEADER_FORMAT),
|
||||
"Search") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_HEADER_FORMAT),
|
||||
"搜索") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_USAGE),
|
||||
"search <keyword>") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_EN, I18N_LANG_CURRENT_FORMAT),
|
||||
"lang <en|zh>") != NULL);
|
||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_LANG_CURRENT_FORMAT),
|
||||
|
|
|
|||
Loading…
Reference in a new issue