diff --git a/.gitignore b/.gitignore index 2814bee..9f369cb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ tests/unit/test_i18n tests/unit/test_system_message tests/unit/test_help_text tests/unit/test_support_text +tests/unit/test_cli_text diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5bd2048..c13ca6a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -36,6 +36,8 @@ - Exec-mode help, usage errors, unknown-command feedback, and post validation messages now follow `TNT_LANG` while preserving stable machine-readable command output. +- Startup CLI help and option errors now live in a dedicated `cli_text` module + and follow `TNT_LANG` / locale for English and Chinese users. ### Changed - NORMAL mode now opens at the latest visible messages instead of the oldest diff --git a/include/cli_text.h b/include/cli_text.h new file mode 100644 index 0000000..ae08dc5 --- /dev/null +++ b/include/cli_text.h @@ -0,0 +1,12 @@ +#ifndef CLI_TEXT_H +#define CLI_TEXT_H + +#include "common.h" + +void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos, + const char *program_name, help_lang_t lang); +const char *cli_text_invalid_port_format(help_lang_t lang); +const char *cli_text_unknown_option_format(help_lang_t lang); +const char *cli_text_short_usage_format(help_lang_t lang); + +#endif /* CLI_TEXT_H */ diff --git a/src/cli_text.c b/src/cli_text.c new file mode 100644 index 0000000..a9ff7ca --- /dev/null +++ b/src/cli_text.c @@ -0,0 +1,62 @@ +#include "cli_text.h" + +void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos, + const char *program_name, help_lang_t lang) { + const char *program = (program_name && program_name[0] != '\0') + ? program_name + : "tnt"; + + if (lang == LANG_ZH) { + buffer_appendf(buffer, buf_size, pos, + "tnt %s - 匿名 SSH 聊天服务器\n\n" + "用法: %s [选项]\n\n" + "选项:\n" + " -p, --port PORT 监听 PORT (默认: %d)\n" + " -d, --state-dir DIR 将主机密钥和日志存放在 DIR\n" + " -V, --version 显示版本\n" + " -h, --help 显示此帮助\n" + "\n" + "环境变量:\n" + " PORT 默认监听端口\n" + " TNT_STATE_DIR 状态目录\n" + " TNT_ACCESS_TOKEN 要求 SSH 认证使用此密码\n" + " TNT_LANG UI 语言: en 或 zh (默认跟随 locale)\n" + " TNT_MAX_CONNECTIONS 全局连接数限制 (默认: 64)\n" + " TNT_RATE_LIMIT 设为 0 可禁用速率限制\n" + " TNT_IDLE_TIMEOUT 空闲断开时间,单位秒 (默认: 1800)\n", + TNT_VERSION, program, DEFAULT_PORT); + return; + } + + buffer_appendf(buffer, buf_size, pos, + "tnt %s - anonymous SSH chat server\n\n" + "Usage: %s [options]\n\n" + "Options:\n" + " -p, --port PORT Listen on PORT (default: %d)\n" + " -d, --state-dir DIR Store host key and logs in DIR\n" + " -V, --version Show version\n" + " -h, --help Show this help\n" + "\n" + "Environment:\n" + " PORT Default listening port\n" + " TNT_STATE_DIR State directory\n" + " TNT_ACCESS_TOKEN Require this password for SSH auth\n" + " TNT_LANG UI language: en or zh (default: locale)\n" + " TNT_MAX_CONNECTIONS Global connection limit (default: 64)\n" + " TNT_RATE_LIMIT Set to 0 to disable rate limiting\n" + " TNT_IDLE_TIMEOUT Idle disconnect timeout in seconds (default: 1800)\n", + TNT_VERSION, program, DEFAULT_PORT); +} + +const char *cli_text_invalid_port_format(help_lang_t lang) { + return lang == LANG_ZH ? "端口无效: %s\n" : "Invalid port: %s\n"; +} + +const char *cli_text_unknown_option_format(help_lang_t lang) { + return lang == LANG_ZH ? "未知选项: %s\n" : "Unknown option: %s\n"; +} + +const char *cli_text_short_usage_format(help_lang_t lang) { + return lang == LANG_ZH ? "用法: %s [-p PORT] [-d DIR] [-h]\n" + : "Usage: %s [-p PORT] [-d DIR] [-h]\n"; +} diff --git a/src/main.c b/src/main.c index 16be8b8..983941b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,9 @@ -#include "common.h" -#include "ssh_server.h" #include "chat_room.h" +#include "cli_text.h" +#include "common.h" +#include "i18n.h" #include "message.h" +#include "ssh_server.h" #include #include @@ -18,6 +20,7 @@ static void signal_handler(int sig) { int main(int argc, char **argv) { int port = DEFAULT_PORT; + help_lang_t lang = i18n_default_lang(); /* Environment provides defaults; command-line flags override it. */ const char *port_env = getenv("PORT"); @@ -36,7 +39,8 @@ int main(int argc, char **argv) { char *end; long val = strtol(argv[i + 1], &end, 10); if (*end != '\0' || val <= 0 || val > 65535) { - fprintf(stderr, "Invalid port: %s\n", argv[i + 1]); + fprintf(stderr, cli_text_invalid_port_format(lang), + argv[i + 1]); return 1; } port = (int)val; @@ -52,25 +56,15 @@ int main(int argc, char **argv) { printf("tnt %s\n", TNT_VERSION); return 0; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - printf("tnt %s - anonymous SSH chat server\n\n", TNT_VERSION); - printf("Usage: %s [options]\n\n", argv[0]); - printf("Options:\n"); - printf(" -p, --port PORT Listen on PORT (default: %d)\n", DEFAULT_PORT); - printf(" -d, --state-dir DIR Store host key and logs in DIR\n"); - printf(" -V, --version Show version\n"); - printf(" -h, --help Show this help\n"); - printf("\nEnvironment:\n"); - printf(" PORT Default listening port\n"); - printf(" TNT_STATE_DIR State directory\n"); - printf(" TNT_ACCESS_TOKEN Require this password for SSH auth\n"); - printf(" TNT_LANG UI language: en or zh (default: locale)\n"); - printf(" TNT_MAX_CONNECTIONS Global connection limit (default: 64)\n"); - printf(" TNT_RATE_LIMIT Set to 0 to disable rate limiting\n"); - printf(" TNT_IDLE_TIMEOUT Idle disconnect timeout in seconds (default: 1800)\n"); + char output[2048] = {0}; + size_t pos = 0; + + cli_text_append_help(output, sizeof(output), &pos, argv[0], lang); + fputs(output, stdout); return 0; } else { - fprintf(stderr, "Unknown option: %s\n", argv[i]); - fprintf(stderr, "Usage: %s [-p PORT] [-d DIR] [-h]\n", argv[0]); + fprintf(stderr, cli_text_unknown_option_format(lang), argv[i]); + fprintf(stderr, cli_text_short_usage_format(lang), argv[0]); return 1; } } diff --git a/tests/unit/Makefile b/tests/unit/Makefile index ac86c56..ef2dafe 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -13,6 +13,7 @@ endif UTF8_SRC = ../../src/utf8.c MESSAGE_SRC = ../../src/message.c COMMON_SRC = ../../src/common.c +CLI_TEXT_SRC = ../../src/cli_text.c CHAT_ROOM_SRC = ../../src/chat_room.c HISTORY_VIEW_SRC = ../../src/history_view.c I18N_SRC = ../../src/i18n.c @@ -20,7 +21,7 @@ SYSTEM_MESSAGE_SRC = ../../src/system_message.c HELP_TEXT_SRC = ../../src/help_text.c SUPPORT_TEXT_SRC = ../../src/support_text.c -TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_help_text test_support_text +TESTS = test_utf8 test_message test_chat_room test_history_view test_i18n test_system_message test_help_text test_support_text test_cli_text .PHONY: all clean run @@ -50,6 +51,9 @@ test_help_text: test_help_text.c $(HELP_TEXT_SRC) $(COMMON_SRC) test_support_text: test_support_text.c $(SUPPORT_TEXT_SRC) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +test_cli_text: test_cli_text.c $(CLI_TEXT_SRC) $(COMMON_SRC) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + run: all @echo "=== Running UTF-8 Tests ===" ./test_utf8 @@ -74,6 +78,9 @@ run: all @echo "" @echo "=== Running Support Text Tests ===" ./test_support_text + @echo "" + @echo "=== Running CLI Text Tests ===" + ./test_cli_text clean: rm -f $(TESTS) *.o test_messages.log diff --git a/tests/unit/test_cli_text.c b/tests/unit/test_cli_text.c new file mode 100644 index 0000000..e650c00 --- /dev/null +++ b/tests/unit/test_cli_text.c @@ -0,0 +1,58 @@ +/* Unit tests for command-line help and error text */ + +#include "../../include/cli_text.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(help_matches_language) { + char output[2048] = {0}; + size_t pos = 0; + + cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_EN); + assert(strstr(output, "anonymous SSH chat server") != NULL); + assert(strstr(output, "Usage: tnt [options]") != NULL); + assert(strstr(output, "TNT_LANG") != NULL); + + memset(output, 0, sizeof(output)); + pos = 0; + cli_text_append_help(output, sizeof(output), &pos, "tnt", LANG_ZH); + assert(strstr(output, "匿名 SSH 聊天服务器") != NULL); + assert(strstr(output, "用法: tnt [选项]") != NULL); + assert(strstr(output, "TNT_LANG") != NULL); +} + +TEST(error_formats_match_language) { + assert(strcmp(cli_text_invalid_port_format(LANG_EN), + "Invalid port: %s\n") == 0); + assert(strcmp(cli_text_invalid_port_format(LANG_ZH), + "端口无效: %s\n") == 0); + assert(strcmp(cli_text_unknown_option_format(LANG_EN), + "Unknown option: %s\n") == 0); + assert(strcmp(cli_text_unknown_option_format(LANG_ZH), + "未知选项: %s\n") == 0); + assert(strcmp(cli_text_short_usage_format(LANG_EN), + "Usage: %s [-p PORT] [-d DIR] [-h]\n") == 0); + assert(strcmp(cli_text_short_usage_format(LANG_ZH), + "用法: %s [-p PORT] [-d DIR] [-h]\n") == 0); +} + +int main(void) { + printf("Running CLI text unit tests...\n\n"); + + RUN_TEST(help_matches_language); + RUN_TEST(error_formats_match_language); + + printf("\n✓ All %d tests passed!\n", tests_passed); + return 0; +}