diff options
Diffstat (limited to 'src/core/wee-doc.c')
-rw-r--r-- | src/core/wee-doc.c | 1643 |
1 files changed, 1643 insertions, 0 deletions
diff --git a/src/core/wee-doc.c b/src/core/wee-doc.c new file mode 100644 index 000000000..fa1e3ac28 --- /dev/null +++ b/src/core/wee-doc.c @@ -0,0 +1,1643 @@ +/* + * wee-doc.c - documentation generator + * + * Copyright (C) 2023 Sébastien Helleu <flashcode@flashtux.org> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <libintl.h> +#include <locale.h> + +#include "weechat.h" +#include "wee-arraylist.h" +#include "wee-command.h" +#include "wee-config-file.h" +#include "wee-dir.h" +#include "wee-hashtable.h" +#include "wee-hdata.h" +#include "wee-hook.h" +#include "wee-infolist.h" +#include "wee-string.h" +#include "wee-url.h" +#include "wee-utf8.h" +#include "../plugins/plugin.h" + +#define ESCAPE(msg) (doc_gen_escape (msg)) +#define TRANS(msg) ((msg && msg[0]) ? _(msg) : msg) +#define TRANS_DEF(msg, def) ((msg && msg[0]) ? _(msg) : def) +#define PLUGIN(plugin) ((plugin) ? plugin->name : "weechat") + +typedef int (t_doc_gen_func)(const char *path, const char *lang); + +int index_string_escaped; +char *string_escaped[32]; + + +/* + * Escapes a string to display in a table: replace "|" by "\|". + */ + +char * +doc_gen_escape (const char *message) +{ + index_string_escaped = (index_string_escaped + 1) % 32; + + if (string_escaped[index_string_escaped]) + free (string_escaped[index_string_escaped]); + + string_escaped[index_string_escaped] = string_replace (message, "|", "\\|"); + + return string_escaped[index_string_escaped]; +} + +/* + * Opens a file for write using: + * - path + * - doc: "api" or "user" + * - name + * - language (eg: "fr") + * + * Returns the file opened, NULL if error. + */ + +FILE * +doc_gen_open_file (const char *path, const char *doc, const char *name, + const char *lang) +{ + char filename[PATH_MAX]; + FILE *file; + + snprintf (filename, sizeof (filename), + "%s%s" "autogen_%s_%s.%s.adoc", + path, DIR_SEPARATOR, doc, name, lang); + + if (weechat_debug_core >= 1) + printf ("Writing: %s\n", filename); + + file = fopen (filename, "wb"); + if (!file) + { + string_fprintf (stderr, + "doc generator: ERROR: unable to write file \"%s\"", + filename); + return NULL; + } + + string_fprintf ( + file, + "//\n" + "// This file is auto-generated by WeeChat.\n" + "// DO NOT EDIT BY HAND!\n" + "//\n" + "\n"); + + return file; +} + +/* + * Checks if a command must be documented or not. + * + * All commands whose name == plugin name are documented, and all commands for + * these plugins are documented as well: + * - weechat (core) + * - irc + * - xfer + */ + +int +doc_gen_check_command (const char *plugin, const char *command) +{ + /* command name is the same as plugin: to document! */ + if (strcmp (plugin, command) == 0) + return 1; + + /* document other options only for weechat, irc, xfer */ + return((strcmp (plugin, "weechat") == 0) + || (strcmp (plugin, "irc") == 0) + || (strcmp (plugin, "xfer") == 0)) ? + 1 : 0; +} + +/* + * Compares two hooks "command" to sort by plugin / command. + */ + +int +doc_gen_hook_command_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_COMMAND(ptr_hook1, command), + HOOK_COMMAND(ptr_hook2, command)); +} + +/* + * Generates files with commands. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_user_commands (const char *path, const char *lang) +{ + FILE *file; + 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; + const char *ptr_args, *pos_pipes, *pos_next; + + file = doc_gen_open_file (path, "user", "commands", lang); + if (!file) + return 0; + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_command_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_COMMAND]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + if (doc_gen_check_command (PLUGIN(ptr_hook->plugin), + HOOK_COMMAND(ptr_hook, command))) + { + arraylist_add (list_hooks, ptr_hook); + } + } + + old_plugin[0] = '\0'; + first_cmd_plugin = 0; + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + if (strcmp (PLUGIN(ptr_hook->plugin), old_plugin) != 0) + { + if (i > 0) + { + string_fprintf ( + file, + "----\n" + "// end::%s_commands[]\n" + "\n", + old_plugin); + } + string_fprintf ( + file, + "// tag::%s_commands[]\n", + PLUGIN(ptr_hook->plugin)); + strcpy (old_plugin, PLUGIN(ptr_hook->plugin)); + first_cmd_plugin = 1; + } + else + { + first_cmd_plugin = 0; + } + if (!first_cmd_plugin) + string_fprintf (file, "----\n\n"); + string_fprintf ( + file, + "[[command_%s_%s]]\n" + "* `+%s+`: %s\n" + "\n" + "----\n", + PLUGIN(ptr_hook->plugin), + HOOK_COMMAND(ptr_hook, command), + HOOK_COMMAND(ptr_hook, command), + TRANS(HOOK_COMMAND(ptr_hook, description))); + + length = 1 + utf8_strlen (HOOK_COMMAND(ptr_hook, command)) + 2; + snprintf (format, sizeof (format), "%%-%ds%%s\n", length); + ptr_args = TRANS(HOOK_COMMAND(ptr_hook, args)); + first_line = 1; + while (ptr_args && ptr_args[0]) + { + value = NULL; + pos_pipes = strstr (ptr_args, "||"); + if (pos_pipes) + { + pos_next = pos_pipes + 2; + while (pos_next[0] == ' ') + { + pos_next++; + } + if (pos_pipes > ptr_args) + { + pos_pipes--; + while ((pos_pipes > ptr_args) && (pos_pipes[0] == ' ')) + { + pos_pipes--; + } + value = strndup (ptr_args, pos_pipes - ptr_args + 1); + } + } + else + { + value = strdup (ptr_args); + pos_next = NULL; + } + if (value) + { + if (first_line) + { + string_fprintf (file, + "/%s %s\n", + HOOK_COMMAND(ptr_hook, command), + value); + } + else + { + string_fprintf (file, format, " ", value); + } + first_line = 0; + free (value); + } + ptr_args = pos_next; + } + if (HOOK_COMMAND(ptr_hook, args_description) + && HOOK_COMMAND(ptr_hook, args_description[0])) + { + string_fprintf (file, + "\n%s\n", + TRANS(HOOK_COMMAND(ptr_hook, args_description))); + } + } + + string_fprintf ( + file, + "----\n" + "// end::%s_commands[]\n", + old_plugin); + + arraylist_free (list_hooks); + + fclose (file); + + return 1; +} + +/* + * Checks if an option must be documented or not. + */ + +int +doc_gen_check_option (struct t_config_option *option) +{ + if (option->config_file->plugin + && (strcmp (option->config_file->plugin->name, "alias") == 0)) + { + return 0; + } + + if (option->config_file->plugin + && (strcmp (option->config_file->plugin->name, "trigger") == 0) + && (strcmp (option->section->name, "trigger") == 0)) + { + return 0; + } + + if (!option->config_file->plugin + && (strcmp (option->section->name, "bar") == 0)) + { + return 0; + } + + return 1; +} + +/* + * Compares two options to sort by plugin / command. + */ + +int +doc_gen_option_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_config_option *ptr_option1, *ptr_option2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_option1 = (struct t_config_option *)pointer1; + ptr_option2 = (struct t_config_option *)pointer2; + + rc = strcmp (ptr_option1->config_file->name, + ptr_option2->config_file->name); + if (rc != 0) + return rc; + + rc = strcmp (ptr_option1->section->name, ptr_option2->section->name); + if (rc != 0) + return rc; + + return strcmp (ptr_option1->name, ptr_option2->name); +} + +/* + * Generates files with commands. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_user_options (const char *path, const char *lang) +{ + FILE *file; + struct t_config_file *ptr_config, *old_config; + struct t_config_section *ptr_section; + struct t_config_option *ptr_option; + struct t_arraylist *list_options; + int i, list_size, index_option; + char *name_escaped, *desc_escaped, *values, str_values[256]; + char *default_value, *tmp; + + file = doc_gen_open_file (path, "user", "options", lang); + if (!file) + return 0; + + list_options = arraylist_new (64, 1, 0, + &doc_gen_option_cmp_cb, NULL, + NULL, NULL); + for (ptr_config = config_files; ptr_config; + ptr_config = ptr_config->next_config) + { + for (ptr_section = ptr_config->sections; ptr_section; + ptr_section = ptr_section->next_section) + { + for (ptr_option = ptr_section->options; ptr_option; + ptr_option = ptr_option->next_option) + { + if (doc_gen_check_option (ptr_option)) + arraylist_add (list_options, ptr_option); + } + } + } + + old_config = NULL; + index_option = 0; + list_size = arraylist_size (list_options); + for (i = 0; i < list_size; i++) + { + ptr_option = (struct t_config_option *)arraylist_get (list_options, i); + if (ptr_option->config_file != old_config) + { + if (old_config) + { + string_fprintf ( + file, + "// end::%s_options[]\n" + "\n", + old_config->name); + } + string_fprintf ( + file, + "// tag::%s_options[]\n", + ptr_option->config_file->name); + old_config = ptr_option->config_file; + index_option = 0; + } + else + { + index_option++; + } + if (index_option > 0) + string_fprintf (file, "\n"); + name_escaped = string_replace (ptr_option->name, ",", "_"); + desc_escaped = (ptr_option->description) ? + string_replace (TRANS(ptr_option->description), "]", "\\]") : + strdup (""); + string_fprintf (file, + "* [[option_%s.%s.%s]] *%s.%s.%s*\n", + ptr_option->config_file->name, + ptr_option->section->name, + name_escaped, + ptr_option->config_file->name, + ptr_option->section->name, + ptr_option->name); + string_fprintf (file, + "** %s: pass:none[%s]\n", + _("description"), + desc_escaped); + string_fprintf (file, + "** %s: %s\n", + _("type"), + TRANS(config_option_type_string[ptr_option->type])); + switch (ptr_option->type) + { + case CONFIG_OPTION_TYPE_BOOLEAN: + values = strdup ("on, off"); + break; + case CONFIG_OPTION_TYPE_INTEGER: + if (ptr_option->string_values) + { + values = string_rebuild_split_string ( + (const char **)ptr_option->string_values, ", ", 0, -1); + } + else + { + snprintf (str_values, sizeof (str_values), + "%d .. %d", + ptr_option->min, + ptr_option->max); + values = strdup (str_values); + } + break; + case CONFIG_OPTION_TYPE_STRING: + if (ptr_option->max <= 0) + values = strdup (_("any string")); + else if (ptr_option->max == 1) + values = strdup (_("any char")); + else + { + snprintf (str_values, sizeof (str_values), + "%s (%s: %d)", + _("any string"), + _("max chars"), + ptr_option->max); + values = strdup (str_values); + } + break; + case CONFIG_OPTION_TYPE_COLOR: + values = strdup (command_help_option_color_values ()); + break; + default: + values = NULL; + break; + } + string_fprintf (file, "** %s: %s\n", _("values"), values); + default_value = config_file_option_value_to_string (ptr_option, + 1, 0, 0); + if (ptr_option->type == CONFIG_OPTION_TYPE_STRING) + { + tmp = string_replace (default_value, "\"", "\\\""); + if (default_value) + free (default_value); + default_value = tmp; + } + string_fprintf ( + file, + "** %s: `+%s%s%s+`\n", + _("default value"), + (ptr_option->type == CONFIG_OPTION_TYPE_STRING) ? "\"" : "", + default_value, + (ptr_option->type == CONFIG_OPTION_TYPE_STRING) ? "\"" : ""); + if (name_escaped) + free (name_escaped); + if (desc_escaped) + free (desc_escaped); + if (values) + free (values); + if (default_value) + free (default_value); + } + + string_fprintf ( + file, + "// end::%s_options[]\n", + old_config->name); + + arraylist_free (list_options); + + fclose (file); + + return 1; +} + +/* + * Generates files with default aliases. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_user_default_aliases (const char *path, const char *lang) +{ + FILE *file; + struct t_infolist *ptr_infolist; + const char *ptr_completion; + + file = doc_gen_open_file (path, "user", "default_aliases", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::default_aliases[]\n" + "[width=\"100%\",cols=\"2m,5m,5\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s\n" + "\n", + ESCAPE(_("Alias")), + ESCAPE(_("Command")), + ESCAPE(_("Completion"))); + + ptr_infolist = hook_infolist_get (NULL, "alias_default", NULL, NULL); + while (infolist_next (ptr_infolist)) + { + ptr_completion = infolist_string(ptr_infolist, "completion"); + string_fprintf (file, + "| /%s | /%s | %s\n", + ESCAPE(infolist_string(ptr_infolist, "name")), + ESCAPE(infolist_string(ptr_infolist, "command")), + (ptr_completion && ptr_completion[0]) ? + ESCAPE(ptr_completion) : "-"); + } + infolist_free (ptr_infolist); + + string_fprintf (file, + "|===\n" + "// end::default_aliases[]\n"); + + fclose (file); + + return 1; +} + +/* + * Generates files with IRC colors. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_user_irc_colors (const char *path, const char *lang) +{ + FILE *file; + struct t_infolist *ptr_infolist; + + file = doc_gen_open_file (path, "user", "irc_colors", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::irc_colors[]\n" + "[width=\"50%\",cols=\"^2m,3\",options=\"header\"]\n" + "|===\n" + "| %s | %s\n" + "\n", + ESCAPE(_("IRC color")), + ESCAPE(_("WeeChat color"))); + + ptr_infolist = hook_infolist_get (NULL, "irc_color_weechat", NULL, NULL); + while (infolist_next (ptr_infolist)) + { + string_fprintf ( + file, + "| %s | %s\n", + ESCAPE(infolist_string(ptr_infolist, "color_irc")), + ESCAPE(infolist_string(ptr_infolist, "color_weechat"))); + } + infolist_free (ptr_infolist); + + string_fprintf (file, + "|===\n" + "// end::irc_colors[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two hooks "info" to sort by plugin / info. + */ + +int +doc_gen_hook_info_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_INFO(ptr_hook1, info_name), + HOOK_INFO(ptr_hook2, info_name)); +} + +/* + * Generates files with infos. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_infos (const char *path, const char *lang) +{ + FILE *file; + struct t_hook *ptr_hook; + struct t_arraylist *list_hooks; + int i, list_size; + + file = doc_gen_open_file (path, "api", "infos", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::infos[]\n" + "[width=\"100%\",cols=\"^1,^2,6,6\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s | %s\n\n", + ESCAPE(_("Plugin")), + ESCAPE(_("Name")), + ESCAPE(_("Description")), + ESCAPE(_("Arguments"))); + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_info_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_INFO]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + arraylist_add (list_hooks, ptr_hook); + } + + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + string_fprintf ( + file, + "| %s | %s | %s | %s\n\n", + ESCAPE(PLUGIN(ptr_hook->plugin)), + ESCAPE(HOOK_INFO(ptr_hook, info_name)), + ESCAPE(TRANS(HOOK_INFO(ptr_hook, description))), + ESCAPE(TRANS_DEF(HOOK_INFO(ptr_hook, args_description), "-"))); + } + + arraylist_free (list_hooks); + + string_fprintf (file, + "|===\n" + "// end::infos[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two hooks "info_hashtable" to sort by plugin / info. + */ + +int +doc_gen_hook_info_hashtable_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_INFO_HASHTABLE(ptr_hook1, info_name), + HOOK_INFO_HASHTABLE(ptr_hook2, info_name)); +} + +/* + * Generates files with infos_hashtable. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_infos_hashtable (const char *path, const char *lang) +{ + FILE *file; + struct t_hook *ptr_hook; + struct t_arraylist *list_hooks; + int i, list_size; + + file = doc_gen_open_file (path, "api", "infos_hashtable", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::infos_hashtable[]\n" + "[width=\"100%\",cols=\"^1,^2,6,6,8\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s | %s | %s\n\n", + ESCAPE(_("Plugin")), + ESCAPE(_("Name")), + ESCAPE(_("Description")), + ESCAPE(_("Hashtable (input)")), + ESCAPE(_("Hashtable (output)"))); + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_info_hashtable_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_INFO_HASHTABLE]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + arraylist_add (list_hooks, ptr_hook); + } + + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + string_fprintf ( + file, + "| %s | %s | %s | %s | %s\n\n", + ESCAPE(PLUGIN(ptr_hook->plugin)), + ESCAPE(HOOK_INFO(ptr_hook, info_name)), + ESCAPE(TRANS(HOOK_INFO_HASHTABLE(ptr_hook, description))), + ESCAPE(TRANS_DEF(HOOK_INFO_HASHTABLE(ptr_hook, args_description), "-")), + TRANS_DEF(HOOK_INFO_HASHTABLE(ptr_hook, output_description), "-")); + } + + arraylist_free (list_hooks); + + string_fprintf (file, + "|===\n" + "// end::infos_hashtable[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two hooks "infolist" to sort by plugin / infolist. + */ + +int +doc_gen_hook_infolist_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_INFOLIST(ptr_hook1, infolist_name), + HOOK_INFOLIST(ptr_hook2, infolist_name)); +} + +/* + * Generates files with infolists. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_infolists (const char *path, const char *lang) +{ + FILE *file; + struct t_hook *ptr_hook; + struct t_arraylist *list_hooks; + int i, list_size; + + file = doc_gen_open_file (path, "api", "infolists", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::infolists[]\n" + "[width=\"100%\",cols=\"^1,^2,5,5,5\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s | %s | %s\n\n", + ESCAPE(_("Plugin")), + ESCAPE(_("Name")), + ESCAPE(_("Description")), + ESCAPE(_("Pointer")), + ESCAPE(_("Arguments"))); + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_infolist_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_INFOLIST]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + arraylist_add (list_hooks, ptr_hook); + } + + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + string_fprintf ( + file, + "| %s | %s | %s | %s | %s\n\n", + ESCAPE(PLUGIN(ptr_hook->plugin)), + ESCAPE(HOOK_INFOLIST(ptr_hook, infolist_name)), + ESCAPE(TRANS(HOOK_INFOLIST(ptr_hook, description))), + ESCAPE(TRANS_DEF(HOOK_INFOLIST(ptr_hook, pointer_description), "-")), + ESCAPE(TRANS_DEF(HOOK_INFOLIST(ptr_hook, args_description), "-"))); + } + + arraylist_free (list_hooks); + + string_fprintf (file, + "|===\n" + "// end::infolists[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two hooks "hdata" to sort by plugin / hdata. + */ + +int +doc_gen_hook_hdata_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_HDATA(ptr_hook1, hdata_name), + HOOK_HDATA(ptr_hook2, hdata_name)); +} + +/* + * Compares two hooks lists to sort by name (and lists beginning with "last_" + * at the end). + */ + +int +doc_gen_hdata_list_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + /* make C compiler happy */ + (void) data; + (void) arraylist; + + if ((strncmp ((const char *)pointer1, "last_", 5) != 0) + && (strncmp ((const char *)pointer2, "last_", 5) == 0)) + { + return -1; + } + + if ((strncmp ((const char *)pointer1, "last_", 5) == 0) + && (strncmp ((const char *)pointer2, "last_", 5) != 0)) + { + return 1; + } + + return strcmp ((const char *)pointer1, (const char *)pointer2); +} + +/* + * Compares two hooks hdata keys to sort by offset. + */ + +int +doc_gen_hdata_key_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + int offset1, offset2; + + /* make C compiler happy */ + (void) arraylist; + + offset1 = hdata_get_var_offset ((struct t_hdata *)data, + (const char *)pointer1); + offset2 = hdata_get_var_offset ((struct t_hdata *)data, + (const char *)pointer2); + return (offset1 < offset2) ? + -1 : ((offset1 > offset2) ? 1 : 0); +} + +/* + * Generates content of a hdata. + */ + +void +doc_gen_api_hdata_content (FILE *file, struct t_hdata *hdata) +{ + const char *ptr_lists, *ptr_keys, *var_hdata, *var_array_size, *ptr_key; + const char *ptr_list; + char **lists, **keys, str_var_hdata[1024], str_var_array_size[1024]; + int i, num_lists, num_keys, list_size; + struct t_arraylist *list_lists, *list_keys, *list_vars_update; + struct t_hashtable *hashtable; + + ptr_lists = hdata_get_string (hdata, "list_keys"); + if (ptr_lists) + { + lists = string_split (ptr_lists, ",", NULL, 0, 0, &num_lists); + if (lists) + { + string_fprintf (file, "| "); + list_lists = arraylist_new (64, 1, 0, + &doc_gen_hdata_list_cmp_cb, hdata, + NULL, NULL); + for (i = 0; i < num_lists; i++) + { + arraylist_add (list_lists, lists[i]); + } + list_size = arraylist_size (list_lists); + for (i = 0; i < list_size; i++) + { + ptr_list = (const char *)arraylist_get (list_lists, i); + string_fprintf (file, "_%s_ +\n", ptr_list); + } + arraylist_free (list_lists); + string_free_split (lists); + string_fprintf (file, "\n"); + } + } + else + { + string_fprintf (file, "| -\n"); + } + + list_vars_update = arraylist_new (64, 0, 1, NULL, NULL, NULL, NULL); + hashtable = hashtable_new (16, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + + ptr_keys = hdata_get_string (hdata, "var_keys"); + if (ptr_keys) + { + keys = string_split (ptr_keys, ",", NULL, 0, 0, &num_keys); + if (keys) + { + string_fprintf (file, "| "); + list_keys = arraylist_new (64, 1, 0, + &doc_gen_hdata_key_cmp_cb, hdata, + NULL, NULL); + for (i = 0; i < num_keys; i++) + { + arraylist_add (list_keys, keys[i]); + } + list_size = arraylist_size (list_keys); + for (i = 0; i < list_size; i++) + { + ptr_key = (const char *)arraylist_get (list_keys, i); + hashtable_set (hashtable, "__update_allowed", ptr_key); + if (hdata_update (hdata, NULL, hashtable)) + arraylist_add (list_vars_update, (void *)ptr_key); + var_array_size = hdata_get_var_array_size_string ( + hdata, NULL, ptr_key); + if (var_array_size) + { + snprintf (str_var_array_size, sizeof (str_var_array_size), + ", array_size: \"%s\"", + var_array_size); + } + else + { + str_var_array_size[0] = '\0'; + } + var_hdata = hdata_get_var_hdata (hdata, ptr_key); + if (var_hdata) + { + snprintf (str_var_hdata, sizeof (str_var_hdata), + ", hdata: \"%s\"", + var_hdata); + } + else + { + str_var_hdata[0] = '\0'; + } + string_fprintf (file, + "_%s_ (%s%s%s) +\n", + ptr_key, + hdata_get_var_type_string (hdata, ptr_key), + str_var_array_size, + str_var_hdata); + } + hashtable_remove_all (hashtable); + hashtable_set (hashtable, "__create_allowed", ""); + if (hdata_update(hdata, NULL, hashtable)) + arraylist_add (list_vars_update, "{hdata_update_create}"); + hashtable_remove_all (hashtable); + hashtable_set (hashtable, "__delete_allowed", ""); + if (hdata_update(hdata, NULL, hashtable)) + arraylist_add (list_vars_update, "{hdata_update_delete}"); + list_size = arraylist_size (list_vars_update); + if (list_size > 0) + { + string_fprintf (file, "\n"); + string_fprintf (file, "*%s* +\n", _("Update allowed:")); + for (i = 0; i < list_size; i++) + { + ptr_key = (const char *)arraylist_get (list_vars_update, i); + if (ptr_key[0] == '{') + { + string_fprintf (file, " _%s_ +\n", ptr_key); + } + else + { + string_fprintf ( + file, + " _%s_ (%s) +\n", + ptr_key, + hdata_get_var_type_string (hdata, ptr_key)); + } + } + } + else + { + string_fprintf (file, "\n"); + } + arraylist_free (list_keys); + string_free_split (keys); + } + } + + hashtable_free (hashtable); + arraylist_free (list_vars_update); + + string_fprintf (file, "\n"); +} + +/* + * Generates files with hdata. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_hdata (const char *path, const char *lang) +{ + FILE *file; + struct t_hook *ptr_hook; + struct t_arraylist *list_hooks; + struct t_hdata *ptr_hdata; + int i, list_size; + char str_anchor[256]; + + file = doc_gen_open_file (path, "api", "hdata", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::hdata[]\n" + ":hdata_update_create: __create\n" + ":hdata_update_delete: __delete\n" + "[width=\"100%\",cols=\"^1,^2,2,2,5\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s | %s | %s\n\n", + ESCAPE(_("Plugin")), + ESCAPE(_("Name")), + ESCAPE(_("Description")), + ESCAPE(_("Lists")), + ESCAPE(_("Variables"))); + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_hdata_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_HDATA]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + arraylist_add (list_hooks, ptr_hook); + } + + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + snprintf (str_anchor, sizeof (str_anchor), + "hdata_%s", + HOOK_HDATA(ptr_hook, hdata_name)); + string_fprintf (file, + "| %s\n", + ESCAPE(PLUGIN(ptr_hook->plugin))); + string_fprintf (file, + "| [[%s]]<<%s,%s>>\n", + ESCAPE(str_anchor), + ESCAPE(str_anchor), + ESCAPE(HOOK_HDATA(ptr_hook, hdata_name))); + string_fprintf (file, + "| %s\n", + ESCAPE(TRANS(HOOK_HDATA(ptr_hook, description)))); + ptr_hdata = hook_hdata_get (NULL, HOOK_HDATA(ptr_hook, hdata_name)); + if (ptr_hdata) + doc_gen_api_hdata_content (file, ptr_hdata); + } + + arraylist_free (list_hooks); + + string_fprintf (file, + "|===\n" + "// end::hdata[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two hooks "completion" to sort by plugin / completion. + */ + +int +doc_gen_hook_completion_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_hook *ptr_hook1, *ptr_hook2; + int rc; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_hook1 = (struct t_hook *)pointer1; + ptr_hook2 = (struct t_hook *)pointer2; + + rc = strcmp (PLUGIN(ptr_hook1->plugin), PLUGIN(ptr_hook2->plugin)); + if (rc != 0) + return rc; + + return strcmp (HOOK_COMPLETION(ptr_hook1, completion_item), + HOOK_COMPLETION(ptr_hook2, completion_item)); +} + +/* + * Generates files with completions. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_completions (const char *path, const char *lang) +{ + FILE *file; + struct t_hook *ptr_hook; + struct t_arraylist *list_hooks; + int i, list_size; + + file = doc_gen_open_file (path, "api", "completions", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::completions[]\n" + "[width=\"100%\",cols=\"^1,^2,7\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s\n\n", + ESCAPE(_("Plugin")), + ESCAPE(_("Name")), + ESCAPE(_("Description"))); + + list_hooks = arraylist_new (64, 1, 0, + &doc_gen_hook_completion_cmp_cb, NULL, + NULL, NULL); + for (ptr_hook = weechat_hooks[HOOK_TYPE_COMPLETION]; ptr_hook; + ptr_hook = ptr_hook->next_hook) + { + arraylist_add (list_hooks, ptr_hook); + } + + list_size = arraylist_size (list_hooks); + for (i = 0; i < list_size; i++) + { + ptr_hook = (struct t_hook *)arraylist_get (list_hooks, i); + string_fprintf ( + file, + "| %s | %s | %s\n\n", + ESCAPE(PLUGIN(ptr_hook->plugin)), + ESCAPE(HOOK_COMPLETION(ptr_hook, completion_item)), + ESCAPE(TRANS(HOOK_COMPLETION(ptr_hook, description)))); + } + + arraylist_free (list_hooks); + + string_fprintf (file, + "|===\n" + "// end::completions[]\n"); + + fclose (file); + + return 1; +} + +/* + * Generates files with URL options. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_url_options (const char *path, const char *lang) +{ + FILE *file; + int i, j; + char *name, *constant; + + file = doc_gen_open_file (path, "api", "url_options", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::url_options[]\n" + "[width=\"100%\",cols=\"2,^1,7\",options=\"header\"]\n" + "|===\n" + "| %s | %s ^(1)^ | %s ^(2)^\n", + ESCAPE(_("Option")), + ESCAPE(_("Type")), + ESCAPE(_("Constants"))); + + for (i = 0; url_options[i].name; i++) + { + name = string_tolower (url_options[i].name); + string_fprintf ( + file, + "\n" + "| %s | %s |", + ESCAPE(name), + ESCAPE(url_type_string[url_options[i].type])); + if (name) + free (name); + if (url_options[i].constants) + { + for (j = 0; url_options[i].constants[j].name; j++) + { + if (j > 0) + string_fprintf (file, ","); + constant = string_tolower (url_options[i].constants[j].name); + string_fprintf (file, " %s", constant); + if (constant) + free (constant); + } + } + string_fprintf (file, "\n"); + } + + string_fprintf (file, + "\n" + "|===\n" + "// end::url_options[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two plugins to sort by priority (descending). + */ + +int +doc_gen_plugin_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_weechat_plugin *ptr_plugin1, *ptr_plugin2; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_plugin1 = (struct t_weechat_plugin *)pointer1; + ptr_plugin2 = (struct t_weechat_plugin *)pointer2; + + if (ptr_plugin1->priority != ptr_plugin2->priority) + return (ptr_plugin1->priority > ptr_plugin2->priority) ? + -1 : ((ptr_plugin1->priority < ptr_plugin2->priority) ? 1 : 0); + + return strcmp (ptr_plugin1->name, ptr_plugin2->name); +} + +/* + * Generates files with plugins priority. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_plugins_priority (const char *path, const char *lang) +{ + FILE *file; + struct t_weechat_plugin *ptr_plugin; + struct t_arraylist *list_plugins; + int i, index, list_size; + + file = doc_gen_open_file (path, "api", "plugins_priority", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::plugins_priority[]\n" + "[width=\"30%\",cols=\"1,3,2\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s\n", + ESCAPE(_("Rank")), + ESCAPE(_("Plugin")), + ESCAPE(_("Priority"))); + + list_plugins = arraylist_new (64, 1, 0, + &doc_gen_plugin_cmp_cb, NULL, + NULL, NULL); + for (ptr_plugin = weechat_plugins; ptr_plugin; + ptr_plugin = ptr_plugin->next_plugin) + { + arraylist_add (list_plugins, ptr_plugin); + } + + index = 1; + list_size = arraylist_size (list_plugins); + for (i = 0; i < list_size; i++) + { + ptr_plugin = (struct t_weechat_plugin *)arraylist_get (list_plugins, i); + string_fprintf (file, + "| %d | %s | %d\n", + index, + ptr_plugin->name, + ptr_plugin->priority); + index++; + } + + arraylist_free (list_plugins); + + string_fprintf (file, + "|===\n" + "// end::plugins_priority[]\n"); + + fclose (file); + + return 1; +} + +/* + * Compares two configurations to sort by priority (descending). + */ + +int +doc_gen_config_cmp_cb (void *data, struct t_arraylist *arraylist, + void *pointer1, void *pointer2) +{ + struct t_config_file *ptr_config1, *ptr_config2; + + /* make C compiler happy */ + (void) data; + (void) arraylist; + + ptr_config1 = (struct t_config_file *)pointer1; + ptr_config2 = (struct t_config_file *)pointer2; + + if (ptr_config1->priority != ptr_config2->priority) + return (ptr_config1->priority > ptr_config2->priority) ? + -1 : ((ptr_config1->priority < ptr_config2->priority) ? 1 : 0); + + return strcmp (ptr_config1->name, ptr_config2->name); +} + +/* + * Generates files with config priority. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_gen_api_config_priority (const char *path, const char *lang) +{ + FILE *file; + struct t_config_file *ptr_config; + struct t_arraylist *list_configs; + int i, index, list_size; + + file = doc_gen_open_file (path, "api", "config_priority", lang); + if (!file) + return 0; + + string_fprintf ( + file, + "// tag::config_priority[]\n" + "[width=\"30%\",cols=\"1,3,2\",options=\"header\"]\n" + "|===\n" + "| %s | %s | %s\n", + ESCAPE(_("Rank")), + ESCAPE(_("File")), + ESCAPE(_("Priority"))); + + list_configs = arraylist_new (64, 1, 0, + &doc_gen_config_cmp_cb, NULL, + NULL, NULL); + for (ptr_config = config_files; ptr_config; + ptr_config = ptr_config->next_config) + { + arraylist_add (list_configs, ptr_config); + } + + index = 1; + list_size = arraylist_size (list_configs); + for (i = 0; i < list_size; i++) + { + ptr_config = (struct t_config_file *)arraylist_get (list_configs, i); + string_fprintf (file, + "| %d | %s.conf | %d\n", + index, + ptr_config->name, + ptr_config->priority); + index++; + } + + arraylist_free (list_configs); + + string_fprintf (file, + "|===\n" + "// end::config_priority[]\n"); + + fclose (file); + + return 1; +} + +/* + * Generates WeeChat files used to build documentation. + * + * Returns: + * 1: OK + * 0: error + */ + +int +doc_generate (const char *path) +{ + int i, j, rc_doc_gen, rc, num_files; + char *locales[] = { + "de_DE.UTF-8", + "en_US.UTF-8", + "fr_FR.UTF-8", + "it_IT.UTF-8", + "ja_JP.UTF-8", + "pl_PL.UTF-8", + "sr_RS.UTF-8", + NULL, + }; + t_doc_gen_func *doc_gen_functions[] = { + doc_gen_user_commands, + doc_gen_user_options, + doc_gen_user_default_aliases, + doc_gen_user_irc_colors, + doc_gen_api_infos, + doc_gen_api_infos_hashtable, + doc_gen_api_infolists, + doc_gen_api_hdata, + doc_gen_api_completions, + doc_gen_api_url_options, + doc_gen_api_plugins_priority, + doc_gen_api_config_priority, + NULL, + }; + char lang[3], *localedir; + + rc_doc_gen = 0; + num_files = 0; + + index_string_escaped = 0; + memset (string_escaped, 0, sizeof (string_escaped)); + + if (!weechat_plugins) + { + string_fprintf (stderr, + "doc generator: ERROR: plugins are not loaded\n"); + goto end; + } + + if (!dir_mkdir_parents (path, 0755)) + { + string_fprintf ( + stderr, + "doc generator: ERROR: failed to create directory \"%s\")", + path); + goto end; + } + + /* + * set a specific localedir to find .mo files + * (this is used to generate documentation without installing WeeChat, + * that means no need to run `make install`) + */ + localedir = getenv ("WEECHAT_DOCGEN_LOCALEDIR"); + if (localedir && localedir[0]) + bindtextdomain (PACKAGE, localedir); + + for (i = 0; locales[i]; i++) + { + setenv ("LANGUAGE", locales[i], 1); + setlocale (LC_ALL, locales[i]); + memcpy (lang, locales[i], 2); + lang[2] = '\0'; + for (j = 0; doc_gen_functions[j]; j++) + { + rc = (int) (doc_gen_functions[j] (path, lang)); + if (!rc) + goto end; + num_files++; + } + } + + printf ("doc generator: build OK: %d files written in %s\n", + num_files, path); + + rc_doc_gen = 1; + +end: + for (i = 0; i < 32; i++) + { + if (string_escaped[i]) + free (string_escaped[i]); + } + return rc_doc_gen; +} |