mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:34:39 +08:00
exec: centralize usage validation in catalog
This commit is contained in:
parent
1391ddca07
commit
0aaba8e1f9
8 changed files with 142 additions and 47 deletions
|
|
@ -21,6 +21,8 @@
|
|||
reducing duplicate command knowledge in `src/exec.c`.
|
||||
- Replaced hard-coded `chat.m1ng.space` examples with `chat.example.com` so
|
||||
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.
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ typedef enum {
|
|||
|
||||
bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
|
||||
const char **args);
|
||||
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_usage(char *buffer, size_t buf_size, size_t *pos,
|
||||
tnt_exec_command_id_t id, ui_lang_t lang);
|
||||
|
||||
#endif /* EXEC_CATALOG_H */
|
||||
|
|
|
|||
|
|
@ -53,10 +53,6 @@ typedef enum {
|
|||
I18N_UNKNOWN_COMMAND_FORMAT,
|
||||
I18N_DID_YOU_MEAN_FORMAT,
|
||||
I18N_UNKNOWN_GUIDANCE,
|
||||
I18N_EXEC_USERS_USAGE,
|
||||
I18N_EXEC_STATS_USAGE,
|
||||
I18N_EXEC_TAIL_USAGE,
|
||||
I18N_EXEC_POST_USAGE,
|
||||
I18N_EXEC_POST_EMPTY,
|
||||
I18N_EXEC_POST_INVALID_UTF8,
|
||||
I18N_EXEC_UNKNOWN_COMMAND_FORMAT,
|
||||
|
|
|
|||
35
src/exec.c
35
src/exec.c
|
|
@ -126,6 +126,17 @@ static int exec_command_help(client_t *client) {
|
|||
return client_send(client, help_text, pos) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int exec_command_usage(client_t *client, tnt_exec_command_id_t id) {
|
||||
char usage[128];
|
||||
size_t pos = 0;
|
||||
|
||||
usage[0] = '\0';
|
||||
exec_catalog_append_usage(usage, sizeof(usage), &pos, id,
|
||||
client->ui_lang);
|
||||
client_printf(client, "%s", usage);
|
||||
return 64;
|
||||
}
|
||||
|
||||
static int exec_command_health(client_t *client) {
|
||||
static const char ok[] = "ok\n";
|
||||
return client_send(client, ok, sizeof(ok) - 1) == 0 ? 0 : 1;
|
||||
|
|
@ -289,9 +300,7 @@ static int exec_command_tail(client_t *client, const char *args) {
|
|||
int rc;
|
||||
|
||||
if (parse_tail_count(args, &requested) < 0) {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang, I18N_EXEC_TAIL_USAGE));
|
||||
return 64;
|
||||
return exec_command_usage(client, TNT_EXEC_COMMAND_TAIL);
|
||||
}
|
||||
|
||||
pthread_rwlock_rdlock(&g_room->lock);
|
||||
|
|
@ -343,9 +352,7 @@ static int exec_command_post(client_t *client, const char *args) {
|
|||
};
|
||||
|
||||
if (!args || args[0] == '\0') {
|
||||
client_printf(client, "%s",
|
||||
i18n_text(client->ui_lang, I18N_EXEC_POST_USAGE));
|
||||
return 64;
|
||||
return exec_command_usage(client, TNT_EXEC_COMMAND_POST);
|
||||
}
|
||||
|
||||
strncpy(content, args, sizeof(content) - 1);
|
||||
|
|
@ -409,26 +416,18 @@ int exec_dispatch(client_t *client) {
|
|||
}
|
||||
|
||||
if (exec_catalog_match(command_copy, &command_id, &args)) {
|
||||
if (!exec_catalog_args_valid(command_id, args)) {
|
||||
return exec_command_usage(client, command_id);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -5,31 +5,52 @@ typedef struct {
|
|||
const char *name;
|
||||
const char *alias;
|
||||
const char *usage;
|
||||
const char *usage_syntax;
|
||||
const char *summary_en;
|
||||
const char *summary_zh;
|
||||
bool no_args;
|
||||
bool optional_json;
|
||||
bool requires_args;
|
||||
} exec_catalog_entry_t;
|
||||
|
||||
static const exec_catalog_entry_t entries[] = {
|
||||
{TNT_EXEC_COMMAND_HELP, "help", "--help",
|
||||
"help", "Show this help", "显示此帮助"},
|
||||
"help", "help", "Show this help", "显示此帮助", true, false, false},
|
||||
{TNT_EXEC_COMMAND_HEALTH, "health", NULL,
|
||||
"health", "Print service health", "输出服务健康状态"},
|
||||
"health", "health", "Print service health", "输出服务健康状态",
|
||||
true, false, false},
|
||||
{TNT_EXEC_COMMAND_USERS, "users", NULL,
|
||||
"users [--json]", "List online users", "列出在线用户"},
|
||||
"users [--json]", "users [--json]",
|
||||
"List online users", "列出在线用户", false, true, false},
|
||||
{TNT_EXEC_COMMAND_STATS, "stats", NULL,
|
||||
"stats [--json]", "Print room statistics", "输出房间统计"},
|
||||
"stats [--json]", "stats [--json]",
|
||||
"Print room statistics", "输出房间统计", false, true, false},
|
||||
{TNT_EXEC_COMMAND_TAIL, "tail", NULL,
|
||||
"tail [N]", "Print recent messages", "输出最近消息"},
|
||||
"tail [N]", "tail [N] | tail -n N",
|
||||
"Print recent messages", "输出最近消息", false, false, false},
|
||||
{TNT_EXEC_COMMAND_TAIL, "tail", NULL,
|
||||
"tail -n N", "Print recent messages", "输出最近消息"},
|
||||
"tail -n N", "tail [N] | tail -n N",
|
||||
"Print recent messages", "输出最近消息", false, false, false},
|
||||
{TNT_EXEC_COMMAND_POST, "post", NULL,
|
||||
"post MESSAGE", "Post a message non-interactively", "非交互发送消息"},
|
||||
"post MESSAGE", "post MESSAGE",
|
||||
"Post a message non-interactively", "非交互发送消息",
|
||||
false, false, true},
|
||||
{TNT_EXEC_COMMAND_POST, "post", NULL,
|
||||
"post \"/me act\"", "Post an action message", "发送动作消息"},
|
||||
"post \"/me act\"", "post MESSAGE",
|
||||
"Post an action message", "发送动作消息", false, false, true},
|
||||
{TNT_EXEC_COMMAND_EXIT, "exit", NULL,
|
||||
"exit", "Exit successfully", "成功退出"}
|
||||
"exit", "exit", "Exit successfully", "成功退出", true, false, false}
|
||||
};
|
||||
|
||||
static const exec_catalog_entry_t *entry_for_id(tnt_exec_command_id_t id) {
|
||||
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) {
|
||||
if (entries[i].id == id) {
|
||||
return &entries[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *skip_spaces(const char *value) {
|
||||
while (value && *value && (*value == ' ' || *value == '\t')) {
|
||||
value++;
|
||||
|
|
@ -84,6 +105,24 @@ bool exec_catalog_match(const char *line, tnt_exec_command_id_t *id,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool exec_catalog_args_valid(tnt_exec_command_id_t id, const char *args) {
|
||||
const exec_catalog_entry_t *entry = entry_for_id(id);
|
||||
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
if (entry->no_args) {
|
||||
return !args || args[0] == '\0';
|
||||
}
|
||||
if (entry->optional_json) {
|
||||
return !args || strcmp(args, "--json") == 0;
|
||||
}
|
||||
if (entry->requires_args) {
|
||||
return args && args[0] != '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos,
|
||||
ui_lang_t lang) {
|
||||
if (lang == UI_LANG_ZH) {
|
||||
|
|
@ -99,3 +138,19 @@ void exec_catalog_append_help(char *buffer, size_t buf_size, size_t *pos,
|
|||
entries[i].usage, summary);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (lang == UI_LANG_ZH) {
|
||||
buffer_appendf(buffer, buf_size, pos, "%s: 用法: %s\n",
|
||||
entry->name, entry->usage_syntax);
|
||||
return;
|
||||
}
|
||||
buffer_appendf(buffer, buf_size, pos, "%s: usage: %s\n",
|
||||
entry->name, entry->usage_syntax);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,22 +208,6 @@ static const i18n_text_entry_t text_catalog[I18N_TEXT_COUNT] = {
|
|||
"Type :help for help\n",
|
||||
"输入 :help 查看帮助\n"
|
||||
},
|
||||
[I18N_EXEC_USERS_USAGE] = {
|
||||
"users: usage: users [--json]\n",
|
||||
"users: 用法: users [--json]\n"
|
||||
},
|
||||
[I18N_EXEC_STATS_USAGE] = {
|
||||
"stats: usage: stats [--json]\n",
|
||||
"stats: 用法: stats [--json]\n"
|
||||
},
|
||||
[I18N_EXEC_TAIL_USAGE] = {
|
||||
"tail: usage: tail [N] | tail -n N\n",
|
||||
"tail: 用法: tail [N] | tail -n N\n"
|
||||
},
|
||||
[I18N_EXEC_POST_USAGE] = {
|
||||
"post: usage: post MESSAGE\n",
|
||||
"post: 用法: post MESSAGE\n"
|
||||
},
|
||||
[I18N_EXEC_POST_EMPTY] = {
|
||||
"post: message cannot be empty\n",
|
||||
"post: 消息不能为空\n"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,17 @@ else
|
|||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
HEALTH_USAGE=$(ssh $SSH_OPTS localhost health extra 2>/dev/null || true)
|
||||
printf '%s\n' "$HEALTH_USAGE" | grep -q '^health: 用法: health$'
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ no-arg exec usage follows TNT_LANG"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "✗ no-arg exec usage output unexpected"
|
||||
printf '%s\n' "$HEALTH_USAGE"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
STATS_OUTPUT=$(ssh $SSH_OPTS localhost stats 2>/dev/null || true)
|
||||
printf '%s\n' "$STATS_OUTPUT" | grep -q '^status ok$' &&
|
||||
printf '%s\n' "$STATS_OUTPUT" | grep -q '^online_users 0$'
|
||||
|
|
@ -109,6 +120,17 @@ else
|
|||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
USERS_USAGE=$(ssh $SSH_OPTS localhost users --xml 2>/dev/null || true)
|
||||
printf '%s\n' "$USERS_USAGE" | grep -q '^users: 用法: users \[--json\]$'
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ users usage follows TNT_LANG"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "✗ users usage output unexpected"
|
||||
printf '%s\n' "$USERS_USAGE"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
POST_OUTPUT=$(ssh $SSH_OPTS execposter@localhost post "hello from exec" 2>/dev/null || true)
|
||||
if [ "$POST_OUTPUT" = "posted" ]; then
|
||||
echo "✓ post publishes a message"
|
||||
|
|
|
|||
|
|
@ -71,11 +71,45 @@ TEST(matches_exec_commands_and_args) {
|
|||
assert(!exec_catalog_match("nope", &id, &args));
|
||||
}
|
||||
|
||||
TEST(validates_argument_shapes) {
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_HELP, NULL));
|
||||
assert(!exec_catalog_args_valid(TNT_EXEC_COMMAND_HELP, "now"));
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_HEALTH, NULL));
|
||||
assert(!exec_catalog_args_valid(TNT_EXEC_COMMAND_HEALTH, "now"));
|
||||
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_USERS, NULL));
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_USERS, "--json"));
|
||||
assert(!exec_catalog_args_valid(TNT_EXEC_COMMAND_USERS, "--xml"));
|
||||
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_TAIL, NULL));
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_TAIL, "-n 20"));
|
||||
|
||||
assert(!exec_catalog_args_valid(TNT_EXEC_COMMAND_POST, NULL));
|
||||
assert(exec_catalog_args_valid(TNT_EXEC_COMMAND_POST, "hello"));
|
||||
}
|
||||
|
||||
TEST(generates_localized_usage) {
|
||||
char en[256] = {0};
|
||||
char zh[256] = {0};
|
||||
size_t en_pos = 0;
|
||||
size_t zh_pos = 0;
|
||||
|
||||
exec_catalog_append_usage(en, sizeof(en), &en_pos,
|
||||
TNT_EXEC_COMMAND_TAIL, UI_LANG_EN);
|
||||
exec_catalog_append_usage(zh, sizeof(zh), &zh_pos,
|
||||
TNT_EXEC_COMMAND_POST, UI_LANG_ZH);
|
||||
|
||||
assert(strcmp(en, "tail: usage: tail [N] | tail -n N\n") == 0);
|
||||
assert(strcmp(zh, "post: 用法: post MESSAGE\n") == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running exec catalog unit tests...\n\n");
|
||||
|
||||
RUN_TEST(generates_localized_exec_help);
|
||||
RUN_TEST(matches_exec_commands_and_args);
|
||||
RUN_TEST(validates_argument_shapes);
|
||||
RUN_TEST(generates_localized_usage);
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue