diff --git a/ChangeLog.adoc b/ChangeLog.adoc index 7f3268697..b9b36477f 100644 --- a/ChangeLog.adoc +++ b/ChangeLog.adoc @@ -37,6 +37,7 @@ New features:: * core: add options weechat.buffer.* to save buffer properties set by user, add option `setauto` in command `/buffer` (issue #352) * core: add parameters and key bindings to move to edges of current area with commands `/cursor go` and `/cursor move` (issue #1282) * core: add variables "_chat_focused_line_bol" and "_chat_focused_line_eol" in focus data (issue #1955) + * api: add support of format/translation of command arguments description line by line (issue #2005) * api: add function string_concat (issue #2005) * api: add support of path to variable and hashtable comparison in function hdata_compare (issue #1066) * api: add infos "nick_color_ignore_case" and "nick_color_name_ignore_case" (issue #194) diff --git a/doc/en/weechat_dev.en.adoc b/doc/en/weechat_dev.en.adoc index e63d127cb..7adb71348 100644 --- a/doc/en/weechat_dev.en.adoc +++ b/doc/en/weechat_dev.en.adoc @@ -424,6 +424,8 @@ WeeChat "core" is located in following directories: |          test-core-utf8.cpp | Tests: UTF-8. |          test-core-util.cpp | Tests: utility functions. |          test-core-sys.cpp | Tests: system functions. +|          hook/ | Root of unit tests for hooks. +|             test-hook-command.cpp | Tests: hooks "command". |       gui/ | Root of unit tests for interfaces. |          test-gui-bar-window.cpp | Tests: bar window functions. |          test-gui-buffer.cpp | Tests: buffer functions. diff --git a/doc/fr/weechat_dev.fr.adoc b/doc/fr/weechat_dev.fr.adoc index d58a602c4..94fd37c03 100644 --- a/doc/fr/weechat_dev.fr.adoc +++ b/doc/fr/weechat_dev.fr.adoc @@ -150,7 +150,7 @@ Le cœur de WeeChat est situé dans les répertoires suivants : |    wee-util.c | Quelques autres fonctions utilitaires. |    wee-version.c | Fonctions pour la version de WeeChat. |    weechat.c | Fonctions principales : options de ligne de commande, démarrage. -|    hook/ | Hook functions. +|    hook/ | Fonctions "hook". |       wee-hook-command-run.c | Hook "command_run". |       wee-hook-command.c | Hook "command". |       wee-hook-completion.c | Hook "completion". @@ -426,6 +426,8 @@ Le cœur de WeeChat est situé dans les répertoires suivants : |          test-core-utf8.cpp | Tests : UTF-8. |          test-core-util.cpp | Tests : fonctions utiles. |          test-core-sys.cpp | Tests : fonctions système. +|          hook/ | Racine des tests pour les hooks. +|             test-hook-command.cpp | Tests : hooks "command". |       gui/ | Racine des tests unitaires pour les interfaces. |          test-gui-bar-window.cpp | Tests : fonctions de fenêtres de barre. |          test-gui-buffer.cpp | Tests : fonctions de tampons. diff --git a/doc/ja/weechat_dev.ja.adoc b/doc/ja/weechat_dev.ja.adoc index 80660eec7..ff533dcaf 100644 --- a/doc/ja/weechat_dev.ja.adoc +++ b/doc/ja/weechat_dev.ja.adoc @@ -459,6 +459,10 @@ WeeChat "core" は以下のディレクトリに配置されています: |          test-core-util.cpp | テスト: ユーティリティ関数 // TRANSLATION MISSING |          test-core-sys.cpp | Tests: system functions. +// TRANSLATION MISSING +|          hook/ | Root of unit tests for hooks. +// TRANSLATION MISSING +|             test-hook-command.cpp | Tests: hooks "command". |       gui/ | インターフェースの単体テストを収める最上位ディレクトリ // TRANSLATION MISSING |          test-gui-bar-window.cpp | Tests: bar window functions. diff --git a/doc/sr/weechat_dev.sr.adoc b/doc/sr/weechat_dev.sr.adoc index b586bac4d..f494368ca 100644 --- a/doc/sr/weechat_dev.sr.adoc +++ b/doc/sr/weechat_dev.sr.adoc @@ -427,6 +427,11 @@ WeeChat „језгро” се налази у следећим директо |          test-core-utf8.cpp | Тестови: UTF-8. |          test-core-util.cpp | Тестови: помоћне функције. |          test-core-sys.cpp | Тестови: системске функције. +// TRANSLATION MISSING +|          hook/ | Root of unit tests for hooks. +// TRANSLATION MISSING +|             test-hook-command.cpp | Tests: hooks "command". +>>>>>>> 94c43287a (core: add a way to format and translate description of command arguments line by line) |       gui/ | Корен unit тестова интерфејса. |          test-gui-bar-window.cpp | Тестови: функције прозора траке. |          test-gui-buffer.cpp | Тестови: бафер функције. diff --git a/src/core/hook/wee-hook-command.c b/src/core/hook/wee-hook-command.c index 355b3c63a..3e985fa01 100644 --- a/src/core/hook/wee-hook-command.c +++ b/src/core/hook/wee-hook-command.c @@ -269,6 +269,236 @@ hook_command_build_completion (struct t_hook_command *hook_command) free (completion); } +/* + * Removes all raw markers from a string: converts "raw[xxx]" to "xxx". + * + * Note: result must be freed after use. + */ + +char * +hook_command_remove_raw_markers (const char *string) +{ + const char *ptr_string, *pos_raw, *pos_end; + char **result; + + if (!string) + return NULL; + + result = string_dyn_alloc (128); + if (!result) + return NULL; + + ptr_string = string; + + while (ptr_string[0]) + { + pos_raw = strstr (ptr_string, "raw["); + if (!pos_raw) + { + string_dyn_concat (result, ptr_string, -1); + break; + } + pos_end = strchr (pos_raw, ']'); + if (!pos_end) + { + string_dyn_concat (result, ptr_string, -1); + break; + } + if (pos_raw > ptr_string) + string_dyn_concat (result, ptr_string, pos_raw - ptr_string); + if (pos_end > pos_raw + 4) + string_dyn_concat (result, pos_raw + 4, pos_end - pos_raw - 4); + ptr_string = pos_end + 1; + } + + return string_dyn_free (result, 0); +} + +/* + * Frees an argument description. + */ + +void +hook_command_arraylist_arg_desc_free (void *data, struct t_arraylist *arraylist, + void *pointer) +{ + /* make C compiler happy */ + (void) data; + (void) arraylist; + + free (pointer); +} + +/* + * Formats and translates arguments description of a command. + * + * Note: result must be freed after use. + */ + +char * +hook_command_format_args_description (const char *args_description) +{ + struct t_arraylist *args; + const char *pos, *ptr_line; + char **lines, **result, *arg_translated, *arg_name, *line_translated; + int i, j, num_lines, length, max_length_arg, size, line_after_args; + int lines_added; + + if (!args_description) + return NULL; + + if (!args_description[0]) + return strdup (args_description); + + /* if args description is not formatted, translate the whole string */ + if (strncmp (args_description, + WEECHAT_HOOK_COMMAND_STR_FORMATTED "\n", + strlen (WEECHAT_HOOK_COMMAND_STR_FORMATTED) + 1) != 0) + { + return strdup (_(args_description)); + } + + /* translate line by line and indent properly arguments */ + result = NULL; + lines = NULL; + args = NULL; + + result = string_dyn_alloc (1024); + if (!result) + goto error; + + lines = string_split (args_description, "\n", NULL, 0, 0, &num_lines); + if (!lines) + goto error; + + if (num_lines == 0) + { + string_free_split (lines); + return string_dyn_free (result, 0); + } + + args = arraylist_new (num_lines, 0, 1, + NULL, NULL, + &hook_command_arraylist_arg_desc_free, NULL); + if (!args) + goto error; + + /* store description of arguments and find longest argument name on screen */ + line_after_args = -1; + max_length_arg = 0; + for (i = 0; i < num_lines; i++) + { + if (!lines[i][0]) + { + line_after_args = i; + break; + } + if (strcmp (lines[i], WEECHAT_HOOK_COMMAND_STR_FORMATTED) == 0) + continue; + arg_translated = hook_command_remove_raw_markers (_(lines[i])); + if (!arg_translated) + continue; + arraylist_add (args, arg_translated); + if ((strncmp (arg_translated, "> ", 2) == 0) + || (strncmp (arg_translated, ">> ", 3) == 0)) + continue; + pos = strchr (arg_translated, ':'); + if (!pos) + continue; + arg_name = string_strndup (arg_translated, pos - arg_translated); + if (arg_name) + { + length = utf8_strlen_screen (arg_name); + if (length > max_length_arg) + max_length_arg = length; + free (arg_name); + } + } + + /* add arguments with their description */ + lines_added = 0; + size = arraylist_size (args); + for (i = 0; i < size; i++) + { + ptr_line = (const char *)arraylist_get (args, i); + if (!ptr_line) + continue; + if (lines_added > 0) + string_dyn_concat (result, "\n", -1); + if (strncmp (ptr_line, "> ", 2) == 0) + { + /* indented line: after the argument name */ + for (j = 0; j < max_length_arg + 2; j++) + { + string_dyn_concat (result, " ", -1); + } + ptr_line += 2; + } + else if (strncmp (ptr_line, ">> ", 3) == 0) + { + /* indented line: after the argument name (+ 2 spaces) */ + for (j = 0; j < max_length_arg + 4; j++) + { + string_dyn_concat (result, " ", -1); + } + ptr_line += 3; + } + else + { + pos = strchr (ptr_line, ':'); + if (pos) + { + arg_name = string_strndup (ptr_line, pos - ptr_line); + if (arg_name) + { + length = utf8_strlen_screen (arg_name); + for (j = length; j < max_length_arg; j++) + { + string_dyn_concat (result, " ", -1); + } + free (arg_name); + } + } + } + string_dyn_concat (result, ptr_line, -1); + lines_added++; + } + + /* add additional description (after arguments) */ + if (line_after_args >= 0) + { + for (i = line_after_args; i < num_lines; i++) + { + if (lines_added > 0) + string_dyn_concat (result, "\n", -1); + if (lines[i][0]) + { + line_translated = hook_command_remove_raw_markers (_(lines[i])); + if (line_translated) + { + string_dyn_concat (result, line_translated, -1); + lines_added++; + free (line_translated); + } + } + } + } + + arraylist_free (args); + string_free_split (lines); + + return string_dyn_free (result, 0); + +error: + if (args) + arraylist_free (args); + if (result) + string_dyn_free (result, 1); + if (lines) + string_free_split (lines); + return NULL; +} + /* * Hooks a command. * @@ -820,6 +1050,8 @@ int hook_command_add_to_infolist (struct t_infolist_item *item, struct t_hook *hook) { + char *args_desc_nls; + if (!item || !hook || !hook->hook_data) return 0; @@ -846,11 +1078,13 @@ hook_command_add_to_infolist (struct t_infolist_item *item, if (!infolist_new_var_string (item, "args_description", HOOK_COMMAND(hook, args_description))) return 0; + args_desc_nls = hook_command_format_args_description ( + HOOK_COMMAND(hook, args_description)); if (!infolist_new_var_string (item, "args_description_nls", - (HOOK_COMMAND(hook, args_description) - && HOOK_COMMAND(hook, args_description)[0]) ? - _(HOOK_COMMAND(hook, args_description)) : "")) + (args_desc_nls) ? args_desc_nls : "")) return 0; + if (args_desc_nls) + free (args_desc_nls); if (!infolist_new_var_string (item, "completion", HOOK_COMMAND(hook, completion))) return 0; diff --git a/src/core/hook/wee-hook-command.h b/src/core/hook/wee-hook-command.h index ab56ee3b0..83ca8340e 100644 --- a/src/core/hook/wee-hook-command.h +++ b/src/core/hook/wee-hook-command.h @@ -76,6 +76,7 @@ struct t_hook_command_similar }; extern char *hook_command_get_description (struct t_hook *hook); +extern char *hook_command_format_args_description (const char *args_description); extern struct t_hook *hook_command (struct t_weechat_plugin *plugin, const char *command, const char *description, diff --git a/src/core/wee-command.c b/src/core/wee-command.c index 6becca420..b4d7ce9bc 100644 --- a/src/core/wee-command.c +++ b/src/core/wee-command.c @@ -3006,7 +3006,7 @@ COMMAND_CALLBACK(help) struct t_weechat_plugin *ptr_plugin; struct t_config_option *ptr_option; int i, length, command_found, first_line_displayed, verbose; - char *string, *ptr_string, *pos_double_pipe, *pos_end; + char *string, *ptr_string, *pos_double_pipe, *pos_end, *args_desc; char empty_string[1] = { '\0' }, str_format[64]; /* make C compiler happy */ @@ -3121,12 +3121,13 @@ COMMAND_CALLBACK(help) gui_chat_printf (NULL, "%s", _(HOOK_COMMAND(ptr_hook, description))); } - if (HOOK_COMMAND(ptr_hook, args_description) - && HOOK_COMMAND(ptr_hook, args_description)[0]) + args_desc = hook_command_format_args_description ( + HOOK_COMMAND(ptr_hook, args_description)); + if (args_desc) { gui_chat_printf (NULL, ""); - gui_chat_printf (NULL, "%s", - _(HOOK_COMMAND(ptr_hook, args_description))); + gui_chat_printf (NULL, "%s", args_desc); + free (args_desc); } } } diff --git a/src/core/wee-command.h b/src/core/wee-command.h index 580c0eb1d..8cc04c6d8 100644 --- a/src/core/wee-command.h +++ b/src/core/wee-command.h @@ -82,6 +82,9 @@ struct t_gui_buffer; return WEECHAT_RC_ERROR; \ } +#define CMD_ARGS_DESC(args...) \ + STR_CONCAT("\n", WEECHAT_HOOK_COMMAND_STR_FORMATTED, ##args) + struct t_command_repeat { char *buffer_name; /* full buffer name */ diff --git a/src/core/wee-doc.c b/src/core/wee-doc.c index 942a41a03..c6f008b74 100644 --- a/src/core/wee-doc.c +++ b/src/core/wee-doc.c @@ -256,7 +256,7 @@ doc_gen_user_commands (const char *path, const char *lang) struct t_hook *ptr_hook; struct t_arraylist *list_hooks; int i, list_size, length, first_cmd_plugin, first_line; - char old_plugin[1024], format[32], *value; + char old_plugin[1024], format[32], *value, *args_desc; const char *ptr_args, *pos_pipes, *pos_next; file = doc_gen_open_file (path, "user", "commands", lang); @@ -365,12 +365,12 @@ doc_gen_user_commands (const char *path, const char *lang) } ptr_args = pos_next; } - if (HOOK_COMMAND(ptr_hook, args_description) - && HOOK_COMMAND(ptr_hook, args_description[0])) + args_desc = hook_command_format_args_description ( + HOOK_COMMAND(ptr_hook, args_description)); + if (args_desc) { - string_fprintf (file, - "\n%s\n", - TRANS(HOOK_COMMAND(ptr_hook, args_description))); + string_fprintf (file, "\n%s\n", args_desc); + free (args_desc); } } diff --git a/src/core/weechat.h b/src/core/weechat.h index af2ee8994..33551d37c 100644 --- a/src/core/weechat.h +++ b/src/core/weechat.h @@ -48,6 +48,7 @@ #define N_(string) (string) #define gettext(string) (string) #endif /* !defined(_) */ +#define AI(string) (string) #define WEECHAT_COPYRIGHT_DATE "(C) 2003-2023" diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h index e976760bf..e7a1c10f9 100644 --- a/src/plugins/weechat-plugin.h +++ b/src/plugins/weechat-plugin.h @@ -220,6 +220,19 @@ struct timeval; #define WEECHAT_STR_CONCAT(separator, argz...) \ weechat_string_concat (separator, ##argz, NULL) +/* + * string used at beginning of arguments description to format the help text + * and translate it line by line + */ +#define WEECHAT_HOOK_COMMAND_STR_FORMATTED "[fmt]" + +/* macro to concatenate strings for description of command arguments */ +#define WEECHAT_CMD_ARGS_DESC(args...) \ + WEECHAT_STR_CONCAT( \ + "\n", \ + WEECHAT_HOOK_COMMAND_STR_FORMATTED, \ + ##args) + /* * macro to return error in case of missing arguments in callback of * hook_command @@ -1248,6 +1261,7 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin); #define NG_(single,plural,number) \ (weechat_plugin->ngettext)(single, plural, number) #endif /* NG_ */ +#define AI(string) (string) #endif /* WEECHAT_H */ #define weechat_gettext(string) (weechat_plugin->gettext)(string) #define weechat_ngettext(single,plural,number) \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bab962a58..9022a96a3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,6 +44,7 @@ set(LIB_WEECHAT_UNIT_TESTS_CORE_SRC unit/core/test-core-utf8.cpp unit/core/test-core-util.cpp unit/core/test-core-sys.cpp + unit/core/hook/test-hook-command.cpp unit/gui/test-gui-bar.cpp unit/gui/test-gui-bar-item.cpp unit/gui/test-gui-bar-item-custom.cpp diff --git a/tests/tests.cpp b/tests/tests.cpp index f82f21f61..f0e83735f 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -78,6 +78,8 @@ IMPORT_TEST_GROUP(CoreUrl); IMPORT_TEST_GROUP(CoreUtf8); IMPORT_TEST_GROUP(CoreUtil); IMPORT_TEST_GROUP(CoreSys); +/* core/hook */ +IMPORT_TEST_GROUP(HookCommand); /* GUI */ IMPORT_TEST_GROUP(GuiBar); IMPORT_TEST_GROUP(GuiBarItem); diff --git a/tests/unit/core/hook/test-hook-command.cpp b/tests/unit/core/hook/test-hook-command.cpp new file mode 100644 index 000000000..92b667657 --- /dev/null +++ b/tests/unit/core/hook/test-hook-command.cpp @@ -0,0 +1,240 @@ +/* + * test-hook-command.cpp - test hook command functions + * + * Copyright (C) 2023 Sébastien Helleu + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see . + */ + +#include "CppUTest/TestHarness.h" + +#include "tests/tests.h" + +extern "C" +{ +#ifndef HAVE_CONFIG_H +#define HAVE_CONFIG_H +#endif +#include "src/core/weechat.h" +#include "src/core/hook/wee-hook-command.h" +#include "src/plugins/plugin.h" + +extern char *hook_command_remove_raw_markers (const char *string); +} + +TEST_GROUP(HookCommand) +{ +}; + +/* + * Tests functions: + * hook_command_get_description + */ + +TEST(HookCommand, GetDescription) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_search + */ + +TEST(HookCommand, Search) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_build_completion + */ + +TEST(HookCommand, BuildCompletion) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_remove_raw_markers + */ + +TEST(HookCommand, RemoveRawMarkers) +{ + char *str; + + WEE_TEST_STR(NULL, hook_command_remove_raw_markers (NULL)); + WEE_TEST_STR("", hook_command_remove_raw_markers ("")); + + WEE_TEST_STR("test", hook_command_remove_raw_markers ("test")); + WEE_TEST_STR("[test] raw[x", hook_command_remove_raw_markers ("[test] raw[x")); + + WEE_TEST_STR("", hook_command_remove_raw_markers ("raw[]")); + WEE_TEST_STR("x", hook_command_remove_raw_markers ("raw[x]")); + WEE_TEST_STR("x test", hook_command_remove_raw_markers ("raw[x] test")); + WEE_TEST_STR("[test] ", hook_command_remove_raw_markers ("[test] raw[]")); + WEE_TEST_STR("[test] x", hook_command_remove_raw_markers ("[test] raw[x]")); + WEE_TEST_STR("[test] x y", hook_command_remove_raw_markers ("[test] raw[x] raw[y]")); +} + +/* + * Tests functions: + * hook_command_arraylist_arg_desc_free + * hook_command_format_args_description + */ + +TEST(HookCommand, FormatArgsDescription) +{ + char *str; + + WEE_TEST_STR(NULL, hook_command_format_args_description (NULL)); + WEE_TEST_STR("", hook_command_format_args_description ("")); + + WEE_TEST_STR("test format args desc", + hook_command_format_args_description ( + "test format args desc")); + WEE_TEST_STR("raw[list]: list all bars", + hook_command_format_args_description ( + "raw[list]: list all bars")); + + WEE_TEST_STR(" list: list all bars\n" + "sub-command2: some other sub-command", + hook_command_format_args_description ( + WEECHAT_HOOK_COMMAND_STR_FORMATTED "\n" + "raw[list]: list all bars\n" + "raw[sub-command2]: some other sub-command")); + + WEE_TEST_STR(" list: list all things\n" + "listfull: list all things (verbose)\n" + " name: name of the thing\n" + " type: the type:\n" + " type1: first type\n" + " type2: second type\n" + "\n" + "This is another line: test.", + hook_command_format_args_description ( + WEECHAT_HOOK_COMMAND_STR_FORMATTED "\n" + "raw[list]: list all things\n" + "raw[listfull]: list all things (verbose)\n" + "name: name of the thing\n" + "type: the type:\n" + "> raw[type1]: first type\n" + "> raw[type2]: second type\n" + "\n" + "This is another line: test.")); +} + +/* + * Tests functions: + * hook_command + */ + +TEST(HookCommand, Command) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_exec + */ + +TEST(HookCommand, CommandExec) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_similar_get_relevance + */ + +TEST(HookCommand, CommandSimilarGetRelevance) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_similar_cmp_cb + */ + +TEST(HookCommand, CommandSimilarCmpCb) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_similar_free_cb + */ + +TEST(HookCommand, CommandSimilarFreeCb) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_build_list_similar_commands + */ + +TEST(HookCommand, CommandBuildListSimilarCommands) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_display_error_unknown + */ + +TEST(HookCommand, CommandDisplayErrorUnknown) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_free_data + */ + +TEST(HookCommand, CommandFreeData) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_add_to_infolist + */ + +TEST(HookCommand, CommandAddToInfolist) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * hook_command_print_log + */ + +TEST(HookCommand, CommandPrintLog) +{ + /* TODO: write tests */ +}