/* * core-doc.c - documentation generator * * Copyright (C) 2023-2024 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "weechat.h" #include "core-arraylist.h" #include "core-command.h" #include "core-config-file.h" #include "core-crypto.h" #include "core-dir.h" #include "core-hashtable.h" #include "core-hdata.h" #include "core-hook.h" #include "core-infolist.h" #include "core-string.h" #include "core-url.h" #include "core-utf8.h" #include "../plugins/plugin.h" #define ESCAPE_TABLE(msg) (doc_gen_escape_table (msg)) #define ESCAPE_ANCHOR(msg) (doc_gen_escape_anchor_link (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_table (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]; } /* * Escapes a string to be used as anchor link: replace ",", "@" and "*" by "-". */ char * doc_gen_escape_anchor_link (const char *message) { regex_t regex; if (string_regcomp (®ex, "[,@*():&|]+", REG_EXTENDED) != 0) return NULL; 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_regex ( message, ®ex, "-", '$', NULL, NULL); regfree (®ex); 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.temp", path, DIR_SEPARATOR, doc, name, lang); file = fopen (filename, "wb"); if (!file) { string_fprintf (stderr, "doc generator: ERROR: unable to write file \"%s\"\n", 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; } /* * Closes the file and renames it without ".temp" suffix, if the target name * does not exist or if it exists with a different (obsolete) content. * * If the target name exists with same content it's kept as-is (so the * timestamp does not change) and the temporary file is just deleted. * * Returns: * 1: target file has been updated * 0: target file unchanged * -1: error */ int doc_gen_close_file (const char *path, const char *doc, const char *name, const char *lang, FILE *file) { char filename_temp[PATH_MAX], filename[PATH_MAX]; char hash_temp[512 / 8], hash[512 / 8]; int rc_temp, rc; fclose (file); snprintf (filename_temp, sizeof (filename_temp), "%s%s" "autogen_%s_%s.%s.adoc.temp", path, DIR_SEPARATOR, doc, name, lang); snprintf (filename, sizeof (filename), "%s%s" "autogen_%s_%s.%s.adoc", path, DIR_SEPARATOR, doc, name, lang); rc_temp = weecrypto_hash_file (filename_temp, GCRY_MD_SHA512, hash_temp, NULL); if (!rc_temp) return -1; rc = weecrypto_hash_file (filename, GCRY_MD_SHA512, hash, NULL); if (!rc || (memcmp (hash_temp, hash, sizeof (hash)) != 0)) { rename (filename_temp, filename); return 1; } unlink (filename_temp); return 0; } /* * Checks if a command must be documented or not: all commands are documented * except the default aliases (that create commands). */ int doc_gen_check_command (const char *plugin, const char *command) { return ((strcmp (plugin, "alias") != 0) || (strcmp (plugin, command) == 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, target file updated * 0: OK, target file unchanged * -1: 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, *args_desc; const char *ptr_args, *pos_pipes, *pos_next; file = doc_gen_open_file (path, "user", "commands", lang); if (!file) return -1; 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_screen (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; } args_desc = hook_command_format_args_description ( HOOK_COMMAND(ptr_hook, args_description)); if (args_desc) { string_fprintf (file, "\n%s\n", args_desc); free (args_desc); } } string_fprintf ( file, "----\n" "// end::%s_commands[]\n", old_plugin); arraylist_free (list_hooks); return doc_gen_close_file (path, "user", "commands", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 *desc_escaped, *values, str_values[256]; char *default_value, *tmp; file = doc_gen_open_file (path, "user", "options", lang); if (!file) return -1; 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"); desc_escaped = (ptr_option->description) ? string_replace (TRANS(ptr_option->description), "]", "\\]") : strdup (""); string_fprintf (file, "* [[option_%s.%s.%s]] *pass:none[%s.%s.%s]*\n", ptr_option->config_file->name, ptr_option->section->name, ESCAPE_ANCHOR(ptr_option->name), 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: 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; case CONFIG_OPTION_TYPE_ENUM: values = string_rebuild_split_string ( (const char **)ptr_option->string_values, ", ", 0, -1); 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 (desc_escaped) free (desc_escaped); if (values) free (values); if (default_value) free (default_value); } if (old_config) { string_fprintf ( file, "// end::%s_options[]\n", old_config->name); } arraylist_free (list_options); return doc_gen_close_file (path, "user", "options", lang, file); } /* * Generates files with default aliases. * * Returns: * 1: OK, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::default_aliases[]\n" "[width=\"100%\",cols=\"2m,5m,5\",options=\"header\"]\n" "|===\n" "| %s | %s | %s\n", ESCAPE_TABLE(_("Alias")), ESCAPE_TABLE(_("Command")), ESCAPE_TABLE(_("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_TABLE(infolist_string (ptr_infolist, "name")), ESCAPE_TABLE(infolist_string (ptr_infolist, "command")), (ptr_completion && ptr_completion[0]) ? ESCAPE_TABLE(ptr_completion) : "-"); } infolist_free (ptr_infolist); string_fprintf (file, "|===\n" "// end::default_aliases[]\n"); return doc_gen_close_file (path, "user", "default_aliases", lang, file); } /* * Generates files with IRC colors. * * Returns: * 1: OK, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::irc_colors[]\n" "[width=\"50%\",cols=\"^2m,3\",options=\"header\"]\n" "|===\n" "| %s | %s\n", ESCAPE_TABLE(_("IRC color")), ESCAPE_TABLE(_("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_TABLE(infolist_string (ptr_infolist, "color_irc")), ESCAPE_TABLE(infolist_string (ptr_infolist, "color_weechat"))); } infolist_free (ptr_infolist); string_fprintf (file, "|===\n" "// end::irc_colors[]\n"); return doc_gen_close_file (path, "user", "irc_colors", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::infos[]\n" "[width=\"100%\",cols=\"^1,^2,6,6\",options=\"header\"]\n" "|===\n" "| %s | %s | %s | %s\n", ESCAPE_TABLE(_("Plugin")), ESCAPE_TABLE(_("Name")), ESCAPE_TABLE(_("Description")), ESCAPE_TABLE(_("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", ESCAPE_TABLE(PLUGIN(ptr_hook->plugin)), ESCAPE_TABLE(HOOK_INFO(ptr_hook, info_name)), ESCAPE_TABLE(TRANS(HOOK_INFO(ptr_hook, description))), ESCAPE_TABLE(TRANS_DEF(HOOK_INFO(ptr_hook, args_description), "-"))); } arraylist_free (list_hooks); string_fprintf (file, "|===\n" "// end::infos[]\n"); return doc_gen_close_file (path, "api", "infos", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; 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", ESCAPE_TABLE(_("Plugin")), ESCAPE_TABLE(_("Name")), ESCAPE_TABLE(_("Description")), ESCAPE_TABLE(_("Hashtable (input)")), ESCAPE_TABLE(_("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", ESCAPE_TABLE(PLUGIN(ptr_hook->plugin)), ESCAPE_TABLE(HOOK_INFO(ptr_hook, info_name)), ESCAPE_TABLE(TRANS(HOOK_INFO_HASHTABLE(ptr_hook, description))), ESCAPE_TABLE(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"); return doc_gen_close_file (path, "api", "infos_hashtable", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::infolists[]\n" "[width=\"100%\",cols=\"^1,^2,5,5,5\",options=\"header\"]\n" "|===\n" "| %s | %s | %s | %s | %s\n", ESCAPE_TABLE(_("Plugin")), ESCAPE_TABLE(_("Name")), ESCAPE_TABLE(_("Description")), ESCAPE_TABLE(_("Pointer")), ESCAPE_TABLE(_("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", ESCAPE_TABLE(PLUGIN(ptr_hook->plugin)), ESCAPE_TABLE(HOOK_INFOLIST(ptr_hook, infolist_name)), ESCAPE_TABLE(TRANS(HOOK_INFOLIST(ptr_hook, description))), ESCAPE_TABLE(TRANS_DEF(HOOK_INFOLIST(ptr_hook, pointer_description), "-")), ESCAPE_TABLE(TRANS_DEF(HOOK_INFOLIST(ptr_hook, args_description), "-"))); } arraylist_free (list_hooks); string_fprintf (file, "|===\n" "// end::infolists[]\n"); return doc_gen_close_file (path, "api", "infolists", lang, file); } /* * 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)); } } } 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; 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_TABLE(_("Plugin")), ESCAPE_TABLE(_("Name")), ESCAPE_TABLE(_("Description")), ESCAPE_TABLE(_("Lists")), ESCAPE_TABLE(_("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_TABLE(PLUGIN(ptr_hook->plugin))); string_fprintf (file, "| [[%s]]<<%s,%s>>\n", ESCAPE_TABLE(str_anchor), ESCAPE_TABLE(str_anchor), ESCAPE_TABLE(HOOK_HDATA(ptr_hook, hdata_name))); string_fprintf (file, "| %s\n", ESCAPE_TABLE(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"); return doc_gen_close_file (path, "api", "hdata", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::completions[]\n" "[width=\"100%\",cols=\"^1,^2,7\",options=\"header\"]\n" "|===\n" "| %s | %s | %s\n", ESCAPE_TABLE(_("Plugin")), ESCAPE_TABLE(_("Name")), ESCAPE_TABLE(_("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", ESCAPE_TABLE(PLUGIN(ptr_hook->plugin)), ESCAPE_TABLE(HOOK_COMPLETION(ptr_hook, completion_item)), ESCAPE_TABLE(TRANS(HOOK_COMPLETION(ptr_hook, description)))); } arraylist_free (list_hooks); string_fprintf (file, "|===\n" "// end::completions[]\n"); return doc_gen_close_file (path, "api", "completions", lang, file); } /* * Generates files with URL options. * * Returns: * 1: OK, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::url_options[]\n" "[width=\"100%\",cols=\"2,^1,7\",options=\"header\"]\n" "|===\n" "| %s | %s ^(1)^ | %s ^(2)^\n", ESCAPE_TABLE(_("Option")), ESCAPE_TABLE(_("Type")), ESCAPE_TABLE(_("Constants"))); for (i = 0; url_options[i].name; i++) { name = string_tolower (url_options[i].name); string_fprintf ( file, "| %s | %s |", ESCAPE_TABLE(name), ESCAPE_TABLE(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" "// end::url_options[]\n"); return doc_gen_close_file (path, "api", "url_options", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::plugins_priority[]\n" "[width=\"30%\",cols=\"1,3,2\",options=\"header\"]\n" "|===\n" "| %s | %s | %s\n", ESCAPE_TABLE(_("Rank")), ESCAPE_TABLE(_("Plugin")), ESCAPE_TABLE(_("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"); return doc_gen_close_file (path, "api", "plugins_priority", lang, file); } /* * 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, target file updated * 0: OK, target file unchanged * -1: 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 -1; string_fprintf ( file, "// tag::config_priority[]\n" "[width=\"30%\",cols=\"1,3,2\",options=\"header\"]\n" "|===\n" "| %s | %s | %s\n", ESCAPE_TABLE(_("Rank")), ESCAPE_TABLE(_("File")), ESCAPE_TABLE(_("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"); return doc_gen_close_file (path, "api", "config_priority", lang, file); } /* * Generates files with scripting API functions. * * Returns: * 1: OK, target file updated * 0: OK, target file unchanged * -1: error */ int doc_gen_scripting_functions (const char *path, const char *lang) { FILE *file; struct t_infolist *ptr_infolist; file = doc_gen_open_file (path, "scripting", "functions", lang); if (!file) return -1; string_fprintf (file, "// tag::functions[]\n"); ptr_infolist = hook_infolist_get (NULL, "python_function", NULL, NULL); while (infolist_next (ptr_infolist)) { string_fprintf (file, "* %s\n", infolist_string (ptr_infolist, "name")); } infolist_free (ptr_infolist); string_fprintf (file, "// end::functions[]\n"); return doc_gen_close_file (path, "scripting", "functions", lang, file); } /* * Generates files with scripting API constants. * * Returns: * 1: OK, target file updated * 0: OK, target file unchanged * -1: error */ int doc_gen_scripting_constants (const char *path, const char *lang) { FILE *file; struct t_infolist *ptr_infolist; const char *ptr_type; file = doc_gen_open_file (path, "scripting", "constants", lang); if (!file) return -1; string_fprintf ( file, "// tag::constants[]\n" "[width=\"60%\",cols=\"8,1,3m\",options=\"header\"]\n" "|===\n" "| %s | %s | %s\n", ESCAPE_TABLE(_("Constant")), ESCAPE_TABLE(_("Type")), ESCAPE_TABLE(_("Value"))); ptr_infolist = hook_infolist_get (NULL, "python_constant", NULL, NULL); while (infolist_next (ptr_infolist)) { ptr_type = infolist_string (ptr_infolist, "type"); if (!ptr_type) continue; if (strcmp (ptr_type, "integer") == 0) { string_fprintf (file, "| %s | %s | %d\n", ESCAPE_TABLE(infolist_string (ptr_infolist, "name")), ESCAPE_TABLE(_("integer")), infolist_integer (ptr_infolist, "value_integer")); } else if (strcmp (ptr_type, "string") == 0) { string_fprintf (file, "| %s | %s | %s\n", ESCAPE_TABLE(infolist_string (ptr_infolist, "name")), ESCAPE_TABLE(_("string")), ESCAPE_TABLE(infolist_string (ptr_infolist, "value_string"))); } } infolist_free (ptr_infolist); string_fprintf (file, "|===\n" "// end::constants[]\n"); return doc_gen_close_file (path, "scripting", "constants", lang, file); } /* * 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, doc_gen_scripting_functions, doc_gen_scripting_constants, 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: WARNING: no plugins loaded, docs will be " "incomplete!\n"); } if (!dir_mkdir_parents (path, 0755)) { string_fprintf ( stderr, "doc generator: ERROR: failed to create directory \"%s\")\n", 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`) */ #ifdef ENABLE_NLS localedir = getenv ("WEECHAT_DOCGEN_LOCALEDIR"); if (localedir && localedir[0]) bindtextdomain (PACKAGE, localedir); #endif /* ENABLE_NLS */ for (i = 0; locales[i]; i++) { setenv ("LANGUAGE", locales[i], 1); if (!setlocale (LC_ALL, locales[i])) { /* warning on missing locale */ string_fprintf ( stderr, "doc generator: WARNING: failed to set locale \"%s\", " "docs will include auto-generated English content\n", locales[i]); /* fallback to English */ setlocale (LC_ALL, "C"); } 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 < 0) goto end; num_files += rc; } } if (num_files > 0) printf ("doc generator: OK, %d files updated\n", num_files); else printf ("doc generator: OK, no changes\n"); rc_doc_gen = 1; end: for (i = 0; i < 32; i++) { if (string_escaped[i]) free (string_escaped[i]); } return rc_doc_gen; }